disable broken tests on net_4_0
[mcs.git] / class / Npgsql / Npgsql / NpgsqlConnectorPool.cs
blob4690c8e654ff91c135ee703151021711ea7c4cbf
1 // Copyright (C) 2002 The Npgsql Development Team
2 // npgsql-general@gborg.postgresql.org
3 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 // ConnectorPool.cs
20 // ------------------------------------------------------------------
21 // Status
22 // 0.00.0000 - 06/17/2002 - ulrich sprick - creation
23 // - 05/??/2004 - Glen Parker<glenebob@nwlink.com> rewritten using
24 // System.Queue.
26 using System;
27 using System.Collections;
28 using System.Threading;
29 using System.Timers;
31 namespace Npgsql
33 /// <summary>
34 /// This class manages all connector objects, pooled AND non-pooled.
35 /// </summary>
36 internal class NpgsqlConnectorPool
38 /// <summary>
39 /// A queue with an extra Int32 for keeping track of busy connections.
40 /// </summary>
41 private class ConnectorQueue : System.Collections.Queue
43 /// <summary>
44 /// The number of pooled Connectors that belong to this queue but
45 /// are currently in use.
46 /// </summary>
47 public Int32 UseCount = 0;
48 public Int32 ConnectionLifeTime;
49 public Int32 InactiveTime = 0;
50 public Int32 MinPoolSize;
54 /// <value>Unique static instance of the connector pool
55 /// mamager.</value>
56 internal static NpgsqlConnectorPool ConnectorPoolMgr = new NpgsqlConnectorPool();
58 public NpgsqlConnectorPool()
60 PooledConnectors = new Hashtable();
61 Timer = new System.Timers.Timer(1000);
62 Timer.AutoReset = true;
63 Timer.Elapsed += new ElapsedEventHandler(TimerElapsedHandler);
64 Timer.Start();
69 ~NpgsqlConnectorPool()
71 Timer.Stop();
74 private void TimerElapsedHandler(object sender, ElapsedEventArgs e)
76 NpgsqlConnector Connector;
77 lock (this)
79 foreach (ConnectorQueue Queue in PooledConnectors.Values)
81 if (Queue.Count > 0)
83 if (Queue.Count + Queue.UseCount > Queue.MinPoolSize)
85 if (Queue.InactiveTime >= Queue.ConnectionLifeTime)
87 Int32 diff = Queue.Count + Queue.UseCount - Queue.MinPoolSize;
88 Int32 toBeClosed = (diff + 1) / 2;
89 if (diff < 2)
90 diff = 2;
91 Queue.InactiveTime -= Queue.ConnectionLifeTime / (int)(Math.Log(diff) / Math.Log(2));
92 for (Int32 i = 0; i < toBeClosed; ++i)
94 Connector = (NpgsqlConnector)Queue.Dequeue();
95 Connector.Close();
98 else
100 Queue.InactiveTime++;
103 else
105 Queue.InactiveTime = 0;
108 else
110 Queue.InactiveTime = 0;
118 /// <value>Map of index to unused pooled connectors, avaliable to the
119 /// next RequestConnector() call.</value>
120 /// <remarks>This hashmap will be indexed by connection string.
121 /// This key will hold a list of queues of pooled connectors available to be used.</remarks>
122 private Hashtable PooledConnectors;
124 /// <value>Map of shared connectors, avaliable to the
125 /// next RequestConnector() call.</value>
126 /// <remarks>This hashmap will be indexed by connection string.
127 /// This key will hold a list of shared connectors available to be used.</remarks>
128 // To be implemented
129 //private Hashtable SharedConnectors;
132 /// <value>Timer for tracking unused connections in pools.</value>
133 // I used System.Timers.Timer because of bad experience with System.Threading.Timer
134 // on Windows - it's going mad sometimes and don't respect interval was set.
135 private System.Timers.Timer Timer;
137 /// <summary>
138 /// Searches the shared and pooled connector lists for a
139 /// matching connector object or creates a new one.
140 /// </summary>
141 /// <param name="Connection">The NpgsqlConnection that is requesting
142 /// the connector. Its ConnectionString will be used to search the
143 /// pool for available connectors.</param>
144 /// <returns>A connector object.</returns>
145 public NpgsqlConnector RequestConnector (NpgsqlConnection Connection)
147 NpgsqlConnector Connector;
149 if (Connection.Pooling)
151 Connector = RequestPooledConnector(Connection);
153 else
155 Connector = GetNonPooledConnector(Connection);
158 return Connector;
161 /// <summary>
162 /// Find a pooled connector. Handle locking and timeout here.
163 /// </summary>
164 private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
166 NpgsqlConnector Connector;
167 Int32 timeoutMilliseconds = Connection.Timeout * 1000;
169 lock(this)
171 Connector = RequestPooledConnectorInternal(Connection);
174 while (Connector == null && timeoutMilliseconds > 0)
176 Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
178 Thread.Sleep(ST);
179 timeoutMilliseconds -= ST;
181 lock(this)
183 Connector = RequestPooledConnectorInternal(Connection);
187 if (Connector == null)
189 if (Connection.Timeout > 0)
191 throw new Exception("Timeout while getting a connection from pool.");
193 else
195 throw new Exception("Connection pool exceeds maximum size.");
199 return Connector;
202 /// <summary>
203 /// Find a pooled connector. Handle shared/non-shared here.
204 /// </summary>
205 private NpgsqlConnector RequestPooledConnectorInternal (NpgsqlConnection Connection)
207 NpgsqlConnector Connector = null;
208 Boolean Shared = false;
210 // If sharing were implemented, I suppose Shared would be set based
211 // on some property on the Connection.
213 if (! Shared)
215 Connector = GetPooledConnector(Connection);
217 else
219 // Connection sharing? What's that?
220 throw new NotImplementedException("Internal: Shared pooling not implemented");
223 return Connector;
226 /// <summary>
227 /// Releases a connector, possibly back to the pool for future use.
228 /// </summary>
229 /// <remarks>
230 /// Pooled connectors will be put back into the pool if there is room.
231 /// Shared connectors should just have their use count decremented
232 /// since they always stay in the shared pool.
233 /// </remarks>
234 /// <param name="Connector">The connector to release.</param>
235 /// <param name="ForceClose">Force the connector to close, even if it is pooled.</param>
236 public void ReleaseConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
238 if (Connector.Pooled)
240 ReleasePooledConnector(Connection, Connector);
242 else
244 UngetNonPooledConnector(Connection, Connector);
248 /// <summary>
249 /// Release a pooled connector. Handle locking here.
250 /// </summary>
251 private void ReleasePooledConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
253 lock(this)
255 ReleasePooledConnectorInternal(Connection, Connector);
259 /// <summary>
260 /// Release a pooled connector. Handle shared/non-shared here.
261 /// </summary>
262 private void ReleasePooledConnectorInternal (NpgsqlConnection Connection, NpgsqlConnector Connector)
264 if (! Connector.Shared)
266 UngetPooledConnector(Connection, Connector);
268 else
270 // Connection sharing? What's that?
271 throw new NotImplementedException("Internal: Shared pooling not implemented");
275 /// <summary>
276 /// Create a connector without any pooling functionality.
277 /// </summary>
278 private NpgsqlConnector GetNonPooledConnector(NpgsqlConnection Connection)
280 NpgsqlConnector Connector;
282 Connector = CreateConnector(Connection);
284 Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
285 Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
286 Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
288 Connector.Open();
290 return Connector;
293 /// <summary>
294 /// Find an available pooled connector in the non-shared pool, or create
295 /// a new one if none found.
296 /// </summary>
297 private NpgsqlConnector GetPooledConnector(NpgsqlConnection Connection)
299 ConnectorQueue Queue;
300 NpgsqlConnector Connector = null;
302 // Try to find a queue.
303 Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
305 if (Queue == null)
307 Queue = new ConnectorQueue();
308 Queue.ConnectionLifeTime = Connection.ConnectionLifeTime;
309 Queue.MinPoolSize = Connection.MinPoolSize;
310 PooledConnectors[Connection.ConnectionString.ToString()] = Queue;
313 // Fix queue use count. Use count may be dropped below zero if Queue was cleared and there were connections open.
314 if (Queue.UseCount < 0)
315 Queue.UseCount = 0;
318 if (Queue.Count > 0)
320 // Found a queue with connectors. Grab the top one.
322 // Check if the connector is still valid.
324 Connector = (NpgsqlConnector)Queue.Dequeue();
325 Queue.UseCount++;
330 else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
332 Connector = CreateConnector(Connection);
334 Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
335 Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
336 Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
340 Connector.Open();
342 catch {
345 Connector.Close();
347 catch {}
349 throw;
353 Queue.UseCount++;
356 // Meet the MinPoolSize requirement if needed.
357 if (Connection.MinPoolSize > 0)
359 while (Queue.Count + Queue.UseCount < Connection.MinPoolSize)
361 NpgsqlConnector Spare = CreateConnector(Connection);
363 Spare.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
364 Spare.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
365 Spare.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
367 Spare.Open();
369 Spare.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
370 Spare.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
371 Spare.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
373 Queue.Enqueue(Spare);
377 return Connector;
380 /// <summary>
381 /// Find an available shared connector in the shared pool, or create
382 /// a new one if none found.
383 /// </summary>
384 private NpgsqlConnector GetSharedConnector(NpgsqlConnection Connection)
386 // To be implemented
388 return null;
391 private NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
393 return new NpgsqlConnector(
394 Connection.ConnectionStringValues.Clone(),
395 Connection.Pooling,
396 false
401 /// <summary>
402 /// This method is only called when NpgsqlConnection.Dispose(false) is called which means a
403 /// finalization. This also means, an NpgsqlConnection was leak. We clear pool count so that
404 /// client doesn't end running out of connections from pool. When the connection is finalized, its underlying
405 /// socket is closed.
406 /// </summary
407 public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
409 ConnectorQueue Queue;
411 // Prevent multithread access to connection pool count.
412 lock(this)
414 // Try to find a queue.
415 Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
417 if (Queue != null)
418 Queue.UseCount--;
423 /// <summary>
424 /// Close the connector.
425 /// </summary>
426 /// <param name="Connector">Connector to release</param>
427 private void UngetNonPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
429 Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
430 Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
431 Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
433 if (Connector.Transaction != null)
435 Connector.Transaction.Cancel();
438 Connector.Close();
441 /// <summary>
442 /// Put a pooled connector into the pool queue.
443 /// </summary>
444 /// <param name="Connector">Connector to pool</param>
445 private void UngetPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
447 ConnectorQueue Queue;
449 // Find the queue.
450 Queue = (ConnectorQueue)PooledConnectors[Connector.ConnectionString.ToString()];
452 if (Queue == null)
453 return; // Queue may be emptied by connection problems. See ClearPool below.
455 Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
456 Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
457 Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
459 Queue.UseCount--;
461 if (! Connector.IsInitialized)
463 if (Connector.Transaction != null)
465 Connector.Transaction.Cancel();
468 Connector.Close();
470 else
472 if (Connector.Transaction != null)
476 Connector.Transaction.Rollback();
478 catch {
479 Connector.Close()
485 if (Connector.State == System.Data.ConnectionState.Open)
487 // Release all resources associated with this connector.
488 Connector.ReleaseResources();
490 Queue.Enqueue(Connector);
494 /// <summary>
495 /// Stop sharing a shared connector.
496 /// </summary>
497 /// <param name="Connector">Connector to unshare</param>
498 private void UngetSharedConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
500 // To be implemented
503 private void ClearQueue(ConnectorQueue Queue)
505 if (Queue == null)
506 return;
508 while (Queue.Count > 0)
510 NpgsqlConnector connector = (NpgsqlConnector)Queue.Dequeue();
514 connector.Close();
516 catch {
517 // Maybe we should log something here to say we got an exception while closing connector?
526 internal void ClearPool(NpgsqlConnection Connection)
528 // Prevent multithread access to connection pool count.
529 lock(this)
531 // Try to find a queue.
532 ConnectorQueue queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
534 ClearQueue(queue);
536 PooledConnectors[Connection.ConnectionString.ToString()] = null;
543 internal void ClearAllPools()
546 lock (this)
548 foreach (ConnectorQueue Queue in PooledConnectors.Values)
549 ClearQueue(Queue);