1 // Copyright (C) 2002 The Npgsql Development Team
2 // npgsql-general@gborg.postgresql.org
3 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
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.
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
20 // ------------------------------------------------------------------
22 // 0.00.0000 - 06/17/2002 - ulrich sprick - creation
23 // - 05/??/2004 - Glen Parker<glenebob@nwlink.com> rewritten using
27 using System.Collections;
28 using System.Threading;
34 /// This class manages all connector objects, pooled AND non-pooled.
36 internal class NpgsqlConnectorPool
39 /// A queue with an extra Int32 for keeping track of busy connections.
41 private class ConnectorQueue : System.Collections.Queue
44 /// The number of pooled Connectors that belong to this queue but
45 /// are currently in use.
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
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);
69 ~NpgsqlConnectorPool()
74 private void TimerElapsedHandler(object sender, ElapsedEventArgs e)
76 NpgsqlConnector Connector;
79 foreach (ConnectorQueue Queue in PooledConnectors.Values)
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;
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();
100 Queue.InactiveTime++;
105 Queue.InactiveTime = 0;
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>
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;
138 /// Searches the shared and pooled connector lists for a
139 /// matching connector object or creates a new one.
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);
155 Connector = GetNonPooledConnector(Connection);
162 /// Find a pooled connector. Handle locking and timeout here.
164 private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
166 NpgsqlConnector Connector;
167 Int32 timeoutMilliseconds = Connection.Timeout * 1000;
171 Connector = RequestPooledConnectorInternal(Connection);
174 while (Connector == null && timeoutMilliseconds > 0)
176 Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
179 timeoutMilliseconds -= ST;
183 Connector = RequestPooledConnectorInternal(Connection);
187 if (Connector == null)
189 if (Connection.Timeout > 0)
191 throw new Exception("Timeout while getting a connection from pool.");
195 throw new Exception("Connection pool exceeds maximum size.");
203 /// Find a pooled connector. Handle shared/non-shared here.
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.
215 Connector = GetPooledConnector(Connection);
219 // Connection sharing? What's that?
220 throw new NotImplementedException("Internal: Shared pooling not implemented");
227 /// Releases a connector, possibly back to the pool for future use.
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.
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);
244 UngetNonPooledConnector(Connection, Connector);
249 /// Release a pooled connector. Handle locking here.
251 private void ReleasePooledConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
255 ReleasePooledConnectorInternal(Connection, Connector);
260 /// Release a pooled connector. Handle shared/non-shared here.
262 private void ReleasePooledConnectorInternal (NpgsqlConnection Connection, NpgsqlConnector Connector)
264 if (! Connector.Shared)
266 UngetPooledConnector(Connection, Connector);
270 // Connection sharing? What's that?
271 throw new NotImplementedException("Internal: Shared pooling not implemented");
276 /// Create a connector without any pooling functionality.
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;
294 /// Find an available pooled connector in the non-shared pool, or create
295 /// a new one if none found.
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()];
307 Queue = new ConnectorQueue();
308 Queue.ConnectionLifeTime = Connection.ConnectionLifeTime;
309 Queue.MinPoolSize = Connection.MinPoolSize;
310 PooledConnectors[Connection.ConnectionString.ToString()] = Queue;
315 // Found a queue with connectors. Grab the top one.
317 // Check if the connector is still valid.
321 Connector = (NpgsqlConnector)Queue.Dequeue();
322 if (Connector.IsValid())
328 // Don't need - we dequeue connector = decrease Queue.Count.
331 if (Queue.Count <= 0)
332 return GetPooledConnector(Connection);
340 else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
342 Connector = CreateConnector(Connection);
344 Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
345 Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
346 Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
366 // Meet the MinPoolSize requirement if needed.
367 if (Connection.MinPoolSize > 0)
369 while (Queue.Count + Queue.UseCount < Connection.MinPoolSize)
371 NpgsqlConnector Spare = CreateConnector(Connection);
373 Spare.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
374 Spare.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
375 Spare.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
379 Spare.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
380 Spare.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
381 Spare.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
383 Queue.Enqueue(Spare);
391 /// Find an available shared connector in the shared pool, or create
392 /// a new one if none found.
394 private NpgsqlConnector GetSharedConnector(NpgsqlConnection Connection)
401 private NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
403 return new NpgsqlConnector(
404 Connection.ConnectionStringValues.Clone(),
412 /// This method is only called when NpgsqlConnection.Dispose(false) is called which means a
413 /// finalization. This also means, an NpgsqlConnection was leak. We clear pool count so that
414 /// client doesn't end running out of connections from pool. When the connection is finalized, its underlying
415 /// socket is closed.
417 public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
419 ConnectorQueue Queue;
421 // Prevent multithread access to connection pool count.
424 // Try to find a queue.
425 Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
434 /// Close the connector.
436 /// <param name="Connector">Connector to release</param>
437 private void UngetNonPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
439 Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
440 Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
441 Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
443 if (Connector.Transaction != null)
445 Connector.Transaction.Cancel();
452 /// Put a pooled connector into the pool queue.
454 /// <param name="Connector">Connector to pool</param>
455 private void UngetPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
457 ConnectorQueue Queue;
460 Queue = (ConnectorQueue)PooledConnectors[Connector.ConnectionString.ToString()];
464 throw new InvalidOperationException("Internal: No connector queue found for existing connector.");
467 Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
468 Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
469 Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
473 if (! Connector.IsInitialized)
475 if (Connector.Transaction != null)
477 Connector.Transaction.Cancel();
484 if (Connector.Transaction != null)
488 Connector.Transaction.Rollback();
497 if (Connector.State == System.Data.ConnectionState.Open)
499 // Release all plans and portals associated with this connector.
500 Connector.ReleasePlansPortals();
502 Queue.Enqueue(Connector);
507 /// Stop sharing a shared connector.
509 /// <param name="Connector">Connector to unshare</param>
510 private void UngetSharedConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)