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
;
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)
320 // Found a queue with connectors. Grab the top one.
322 // Check if the connector is still valid.
324 Connector
= (NpgsqlConnector
)Queue
.Dequeue();
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
;
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
;
369 Spare
.CertificateSelectionCallback
-= Connection
.CertificateSelectionCallbackDelegate
;
370 Spare
.CertificateValidationCallback
-= Connection
.CertificateValidationCallbackDelegate
;
371 Spare
.PrivateKeySelectionCallback
-= Connection
.PrivateKeySelectionCallbackDelegate
;
373 Queue
.Enqueue(Spare
);
381 /// Find an available shared connector in the shared pool, or create
382 /// a new one if none found.
384 private NpgsqlConnector
GetSharedConnector(NpgsqlConnection Connection
)
391 private NpgsqlConnector
CreateConnector(NpgsqlConnection Connection
)
393 return new NpgsqlConnector(
394 Connection
.ConnectionStringValues
.Clone(),
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.
407 public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection
)
409 ConnectorQueue Queue
;
411 // Prevent multithread access to connection pool count.
414 // Try to find a queue.
415 Queue
= (ConnectorQueue
)PooledConnectors
[Connection
.ConnectionString
.ToString()];
424 /// Close the connector.
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();
442 /// Put a pooled connector into the pool queue.
444 /// <param name="Connector">Connector to pool</param>
445 private void UngetPooledConnector(NpgsqlConnection Connection
, NpgsqlConnector Connector
)
447 ConnectorQueue Queue
;
450 Queue
= (ConnectorQueue
)PooledConnectors
[Connector
.ConnectionString
.ToString()];
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
;
461 if (! Connector
.IsInitialized
)
463 if (Connector
.Transaction
!= null)
465 Connector
.Transaction
.Cancel();
472 if (Connector
.Transaction
!= null)
476 Connector
.Transaction
.Rollback();
485 if (Connector
.State
== System
.Data
.ConnectionState
.Open
)
487 // Release all resources associated with this connector.
488 Connector
.ReleaseResources();
490 Queue
.Enqueue(Connector
);
495 /// Stop sharing a shared connector.
497 /// <param name="Connector">Connector to unshare</param>
498 private void UngetSharedConnector(NpgsqlConnection Connection
, NpgsqlConnector Connector
)
503 private void ClearQueue(ConnectorQueue Queue
)
508 while (Queue
.Count
> 0)
510 NpgsqlConnector connector
= (NpgsqlConnector
)Queue
.Dequeue();
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.
531 // Try to find a queue.
532 ConnectorQueue queue
= (ConnectorQueue
)PooledConnectors
[Connection
.ConnectionString
.ToString()];
536 PooledConnectors
[Connection
.ConnectionString
.ToString()] = null;
543 internal void ClearAllPools()
548 foreach (ConnectorQueue Queue
in PooledConnectors
.Values
)