2004-11-25 Francisco Figueiredo Jr. <fxjrlists@yahoo.com.br>
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlConnectorPool.cs
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.
14 //
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
18 //
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.
25
26 using System;
27 using System.Collections;
28 using System.Threading;
29
30 namespace Npgsql
31 {
32     /// <summary>
33     /// This class manages all connector objects, pooled AND non-pooled.
34     /// </summary>
35     internal class NpgsqlConnectorPool
36     {
37         /// <summary>
38         /// A queue with an extra Int32 for keeping track of busy connections.
39         /// </summary>
40     private class ConnectorQueue : System.Collections.Queue
41         {
42             /// <summary>
43             /// The number of pooled Connectors that belong to this queue but
44             /// are currently in use.
45             /// </summary>
46             public Int32            UseCount = 0;
47         }
48
49         /// <value>Unique static instance of the connector pool
50         /// mamager.</value>
51         internal static NpgsqlConnectorPool ConnectorPoolMgr = new NpgsqlConnectorPool();
52
53         public NpgsqlConnectorPool()
54         {
55             PooledConnectors = new Hashtable();
56         }
57
58
59         /// <value>Map of index to unused pooled connectors, avaliable to the
60         /// next RequestConnector() call.</value>
61         /// <remarks>This hashmap will be indexed by connection string.
62         /// This key will hold a list of queues of pooled connectors available to be used.</remarks>
63         private Hashtable PooledConnectors;
64
65         /// <value>Map of shared connectors, avaliable to the
66         /// next RequestConnector() call.</value>
67         /// <remarks>This hashmap will be indexed by connection string.
68         /// This key will hold a list of shared connectors available to be used.</remarks>
69         // To be implemented
70         //private Hashtable SharedConnectors;
71
72         /// <summary>
73         /// Searches the shared and pooled connector lists for a
74         /// matching connector object or creates a new one.
75         /// </summary>
76         /// <param name="Connection">The NpgsqlConnection that is requesting
77         /// the connector. Its ConnectionString will be used to search the
78         /// pool for available connectors.</param>
79         /// <returns>A connector object.</returns>
80         public NpgsqlConnector RequestConnector (NpgsqlConnection Connection)
81         {
82             NpgsqlConnector     Connector;
83
84             if (Connection.Pooling)
85             {
86                 Connector = RequestPooledConnector(Connection);
87             }
88             else
89             {
90                 Connector = GetNonPooledConnector(Connection);
91             }
92
93             return Connector;
94         }
95
96         /// <summary>
97         /// Find a pooled connector.  Handle locking and timeout here.
98         /// </summary>
99         private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
100         {
101             NpgsqlConnector     Connector;
102             Int32               timeoutMilliseconds = Connection.Timeout * 1000;
103
104             lock(this)
105             {
106                 Connector = RequestPooledConnectorInternal(Connection);
107             }
108
109             while (Connector == null && timeoutMilliseconds > 0)
110             {
111                 Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
112
113                 Thread.Sleep(ST);
114                 timeoutMilliseconds -= ST;
115
116                 lock(this)
117                 {
118                     Connector = RequestPooledConnectorInternal(Connection);
119                 }
120             }
121
122             if (Connector == null)
123             {
124                 if (Connection.Timeout > 0)
125                 {
126                     throw new Exception("Timeout while getting a connection from pool.");
127                 }
128                 else
129                 {
130                     throw new Exception("Connection pool exceeds maximum size.");
131                 }
132             }
133
134             return Connector;
135         }
136
137         /// <summary>
138         /// Find a pooled connector.  Handle shared/non-shared here.
139         /// </summary>
140         private NpgsqlConnector RequestPooledConnectorInternal (NpgsqlConnection Connection)
141         {
142             NpgsqlConnector       Connector = null;
143             Boolean               Shared = false;
144
145             // If sharing were implemented, I suppose Shared would be set based
146             // on some property on the Connection.
147
148             if (! Shared)
149             {
150                 Connector = GetPooledConnector(Connection);
151             }
152             else
153             {
154                 // Connection sharing? What's that?
155                 throw new NotImplementedException("Internal: Shared pooling not implemented");
156             }
157
158             return Connector;
159         }
160
161         /// <summary>
162         /// Releases a connector, possibly back to the pool for future use.
163         /// </summary>
164         /// <remarks>
165         /// Pooled connectors will be put back into the pool if there is room.
166         /// Shared connectors should just have their use count decremented
167         /// since they always stay in the shared pool.
168         /// </remarks>
169         /// <param name="Connector">The connector to release.</param>
170         /// <param name="ForceClose">Force the connector to close, even if it is pooled.</param>
171         public void ReleaseConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
172         {
173             if (Connector.Pooled)
174             {
175                 ReleasePooledConnector(Connection, Connector);
176             }
177             else
178             {
179                 UngetNonPooledConnector(Connection, Connector);
180             }
181         }
182
183         /// <summary>
184         /// Release a pooled connector.  Handle locking here.
185         /// </summary>
186         private void ReleasePooledConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
187         {
188             lock(this)
189             {
190                 ReleasePooledConnectorInternal(Connection, Connector);
191             }
192         }
193
194         /// <summary>
195         /// Release a pooled connector.  Handle shared/non-shared here.
196         /// </summary>
197         private void ReleasePooledConnectorInternal (NpgsqlConnection Connection, NpgsqlConnector Connector)
198         {
199             if (! Connector.Shared)
200             {
201                 UngetPooledConnector(Connection, Connector);
202             }
203             else
204             {
205                 // Connection sharing? What's that?
206                 throw new NotImplementedException("Internal: Shared pooling not implemented");
207             }
208         }
209
210         /// <summary>
211         /// Create a connector without any pooling functionality.
212         /// </summary>
213         private NpgsqlConnector GetNonPooledConnector(NpgsqlConnection Connection)
214         {
215             NpgsqlConnector       Connector;
216
217             Connector = CreateConnector(Connection);
218
219             Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
220             Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
221             Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
222
223             Connector.Open();
224
225             return Connector;
226         }
227
228         /// <summary>
229         /// Find an available pooled connector in the non-shared pool, or create
230         /// a new one if none found.
231         /// </summary>
232         private NpgsqlConnector GetPooledConnector(NpgsqlConnection Connection)
233         {
234             ConnectorQueue        Queue;
235             NpgsqlConnector       Connector = null;
236
237             // Try to find a queue.
238             Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
239
240             if (Queue == null)
241             {
242                 Queue = new ConnectorQueue();
243                 PooledConnectors[Connection.ConnectionString.ToString()] = Queue;
244             }
245
246             if (Queue.Count > 0)
247             {
248                 // Found a queue with connectors.  Grab the top one.
249
250                 // Check if the connector is still valid.
251
252                 while (true)
253                 {
254                     Connector = (NpgsqlConnector)Queue.Dequeue();
255                     if (Connector.IsValid())
256                     {
257                         Queue.UseCount++;
258                         break;
259                     }
260
261                     Queue.UseCount--;
262
263                     if (Queue.Count <= 0)
264                         return GetPooledConnector(Connection);
265
266
267                 }
268
269
270
271             }
272             else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
273             {
274                 Connector = CreateConnector(Connection);
275
276                 Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
277                 Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
278                 Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
279
280                 try
281                 {
282                     Connector.Open();
283                 }
284                 catch {
285                     try
286                     {
287                         Connector.Close();
288                         }
289                         catch {}
290
291                         throw;
292                     }
293
294
295                     Queue.UseCount++;
296         }
297
298         // Meet the MinPoolSize requirement if needed.
299         if (Connection.MinPoolSize > 0)
300             {
301                 while (Queue.Count + Queue.UseCount < Connection.MinPoolSize)
302                 {
303                     NpgsqlConnector Spare = CreateConnector(Connection);
304
305                     Spare.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
306                     Spare.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
307                     Spare.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
308
309                     Spare.Open();
310
311                     Spare.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
312                     Spare.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
313                     Spare.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
314
315                     Queue.Enqueue(Connector);
316                 }
317             }
318
319             return Connector;
320         }
321
322         /// <summary>
323         /// Find an available shared connector in the shared pool, or create
324         /// a new one if none found.
325         /// </summary>
326         private NpgsqlConnector GetSharedConnector(NpgsqlConnection Connection)
327         {
328             // To be implemented
329
330             return null;
331         }
332
333         private NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
334         {
335             return new NpgsqlConnector(
336                        Connection.ConnectionStringValues.Clone(),
337                        Connection.Pooling,
338                        false
339                    );
340         }
341
342
343         /// <summary>
344         /// This method is only called when NpgsqlConnection.Dispose(false) is called which means a
345         /// finalization. This also means, an NpgsqlConnection was leak. We clear pool count so that
346         /// client doesn't end running out of connections from pool. When the connection is finalized, its underlying
347         /// socket is closed.
348         /// </summary
349         public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
350         {
351             ConnectorQueue           Queue;
352
353             // Prevent multithread access to connection pool count.
354             lock(this)
355             {
356                 // Try to find a queue.
357                 Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
358
359                 if (Queue != null)
360                     Queue.UseCount--;
361
362             }
363         }
364
365         /// <summary>
366         /// Close the connector.
367         /// </summary>
368         /// <param name="Connector">Connector to release</param>
369         private void UngetNonPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
370         {
371             Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
372             Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
373             Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
374
375             if (Connector.Transaction != null)
376             {
377                 Connector.Transaction.Cancel();
378             }
379
380             Connector.Close();
381         }
382
383         /// <summary>
384         /// Put a pooled connector into the pool queue.
385         /// </summary>
386         /// <param name="Connector">Connector to pool</param>
387         private void UngetPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
388         {
389             ConnectorQueue           Queue;
390
391             // Find the queue.
392             Queue = (ConnectorQueue)PooledConnectors[Connector.ConnectionString.ToString()];
393
394             if (Queue == null)
395             {
396                 throw new InvalidOperationException("Internal: No connector queue found for existing connector.");
397             }
398
399             Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
400             Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
401             Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
402
403             Queue.UseCount--;
404
405             if (! Connector.IsInitialized)
406             {
407                 if (Connector.Transaction != null)
408                 {
409                     Connector.Transaction.Cancel();
410                 }
411
412                 Connector.Close();
413             }
414             else
415             {
416                 if (Connector.Transaction != null)
417                 {
418                     try
419                     {
420                         Connector.Transaction.Rollback();
421                     }
422                     catch {
423                         Connector.Close()
424                         ;
425                     }
426                 }
427         }
428
429         if (Connector.State == System.Data.ConnectionState.Open)
430             {
431                 // Release all plans and portals associated with this connector.
432                 Connector.ReleasePlansPortals();
433
434                 Queue.Enqueue(Connector);
435             }
436         }
437
438         /// <summary>
439         /// Stop sharing a shared connector.
440         /// </summary>
441         /// <param name="Connector">Connector to unshare</param>
442         private void UngetSharedConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
443         {
444             // To be implemented
445         }
446     }
447 }