* roottypes.cs: Rename from tree.cs.
[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 using System.Timers;
30
31 namespace Npgsql
32 {
33     /// <summary>
34     /// This class manages all connector objects, pooled AND non-pooled.
35     /// </summary>
36     internal class NpgsqlConnectorPool
37     {
38         /// <summary>
39         /// A queue with an extra Int32 for keeping track of busy connections.
40         /// </summary>
41     private class ConnectorQueue : System.Collections.Queue
42         {
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;
51
52         }
53
54         /// <value>Unique static instance of the connector pool
55         /// mamager.</value>
56         internal static NpgsqlConnectorPool ConnectorPoolMgr = new NpgsqlConnectorPool();
57
58         public NpgsqlConnectorPool()
59         {
60             PooledConnectors = new Hashtable();
61             Timer = new System.Timers.Timer(1000);
62             Timer.AutoReset = true;
63             Timer.Elapsed += new ElapsedEventHandler(TimerElapsedHandler);
64             Timer.Start();
65
66         }
67         
68         
69         ~NpgsqlConnectorPool()
70         {
71             Timer.Stop();
72         }
73         
74         private void TimerElapsedHandler(object sender, ElapsedEventArgs e)
75         {
76             NpgsqlConnector     Connector;
77             lock (this)
78             {
79                 foreach (ConnectorQueue Queue in PooledConnectors.Values)
80                 {
81                     if (Queue.Count > 0)
82                     {
83                         if (Queue.Count + Queue.UseCount > Queue.MinPoolSize)
84                         {
85                             if (Queue.InactiveTime >= Queue.ConnectionLifeTime)
86                             {
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)
93                                 {
94                                     Connector = (NpgsqlConnector)Queue.Dequeue();
95                                     Connector.Close();
96                                 }
97                             }
98                             else
99                             {
100                                 Queue.InactiveTime++;
101                             }
102                         }
103                         else
104                         {
105                             Queue.InactiveTime = 0;
106                         }
107                     }
108                     else
109                     {
110                         Queue.InactiveTime = 0;
111                     }
112                 }
113             }
114         }
115
116
117
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;
123
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;
130
131                     
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;
136
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)
146         {
147             NpgsqlConnector     Connector;
148
149             if (Connection.Pooling)
150             {
151                 Connector = RequestPooledConnector(Connection);
152             }
153             else
154             {
155                 Connector = GetNonPooledConnector(Connection);
156             }
157
158             return Connector;
159         }
160
161         /// <summary>
162         /// Find a pooled connector.  Handle locking and timeout here.
163         /// </summary>
164         private NpgsqlConnector RequestPooledConnector (NpgsqlConnection Connection)
165         {
166             NpgsqlConnector     Connector;
167             Int32               timeoutMilliseconds = Connection.Timeout * 1000;
168
169             lock(this)
170             {
171                 Connector = RequestPooledConnectorInternal(Connection);
172             }
173
174             while (Connector == null && timeoutMilliseconds > 0)
175             {
176                 Int32 ST = timeoutMilliseconds > 1000 ? 1000 : timeoutMilliseconds;
177
178                 Thread.Sleep(ST);
179                 timeoutMilliseconds -= ST;
180
181                 lock(this)
182                 {
183                     Connector = RequestPooledConnectorInternal(Connection);
184                 }
185             }
186
187             if (Connector == null)
188             {
189                 if (Connection.Timeout > 0)
190                 {
191                     throw new Exception("Timeout while getting a connection from pool.");
192                 }
193                 else
194                 {
195                     throw new Exception("Connection pool exceeds maximum size.");
196                 }
197             }
198
199             return Connector;
200         }
201
202         /// <summary>
203         /// Find a pooled connector.  Handle shared/non-shared here.
204         /// </summary>
205         private NpgsqlConnector RequestPooledConnectorInternal (NpgsqlConnection Connection)
206         {
207             NpgsqlConnector       Connector = null;
208             Boolean               Shared = false;
209
210             // If sharing were implemented, I suppose Shared would be set based
211             // on some property on the Connection.
212
213             if (! Shared)
214             {
215                 Connector = GetPooledConnector(Connection);
216             }
217             else
218             {
219                 // Connection sharing? What's that?
220                 throw new NotImplementedException("Internal: Shared pooling not implemented");
221             }
222
223             return Connector;
224         }
225
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)
237         {
238             if (Connector.Pooled)
239             {
240                 ReleasePooledConnector(Connection, Connector);
241             }
242             else
243             {
244                 UngetNonPooledConnector(Connection, Connector);
245             }
246         }
247
248         /// <summary>
249         /// Release a pooled connector.  Handle locking here.
250         /// </summary>
251         private void ReleasePooledConnector (NpgsqlConnection Connection, NpgsqlConnector Connector)
252         {
253             lock(this)
254             {
255                 ReleasePooledConnectorInternal(Connection, Connector);
256             }
257         }
258
259         /// <summary>
260         /// Release a pooled connector.  Handle shared/non-shared here.
261         /// </summary>
262         private void ReleasePooledConnectorInternal (NpgsqlConnection Connection, NpgsqlConnector Connector)
263         {
264             if (! Connector.Shared)
265             {
266                 UngetPooledConnector(Connection, Connector);
267             }
268             else
269             {
270                 // Connection sharing? What's that?
271                 throw new NotImplementedException("Internal: Shared pooling not implemented");
272             }
273         }
274
275         /// <summary>
276         /// Create a connector without any pooling functionality.
277         /// </summary>
278         private NpgsqlConnector GetNonPooledConnector(NpgsqlConnection Connection)
279         {
280             NpgsqlConnector       Connector;
281
282             Connector = CreateConnector(Connection);
283
284             Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
285             Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
286             Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
287
288             Connector.Open();
289
290             return Connector;
291         }
292
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)
298         {
299             ConnectorQueue        Queue;
300             NpgsqlConnector       Connector = null;
301
302             // Try to find a queue.
303             Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
304
305             if (Queue == null)
306             {
307                 Queue = new ConnectorQueue();
308                 Queue.ConnectionLifeTime = Connection.ConnectionLifeTime;
309                 Queue.MinPoolSize = Connection.MinPoolSize;
310                 PooledConnectors[Connection.ConnectionString.ToString()] = Queue;
311             }
312
313             if (Queue.Count > 0)
314             {
315                 // Found a queue with connectors.  Grab the top one.
316
317                 // Check if the connector is still valid.
318
319                 while (true)
320                 {
321                     Connector = (NpgsqlConnector)Queue.Dequeue();
322                     if (Connector.IsValid())
323                     {
324                         Queue.UseCount++;
325                         break;
326                     }
327
328                     // Don't need - we dequeue connector = decrease Queue.Count.
329                     //Queue.UseCount--;
330
331                     if (Queue.Count <= 0)
332                         return GetPooledConnector(Connection);
333
334
335                 }
336
337
338
339             }
340             else if (Queue.Count + Queue.UseCount < Connection.MaxPoolSize)
341             {
342                 Connector = CreateConnector(Connection);
343
344                 Connector.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
345                 Connector.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
346                 Connector.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
347
348                 try
349                 {
350                     Connector.Open();
351                 }
352                 catch {
353                     try
354                     {
355                         Connector.Close();
356                         }
357                         catch {}
358
359                         throw;
360                     }
361
362
363                     Queue.UseCount++;
364         }
365
366         // Meet the MinPoolSize requirement if needed.
367         if (Connection.MinPoolSize > 0)
368             {
369                 while (Queue.Count + Queue.UseCount < Connection.MinPoolSize)
370                 {
371                     NpgsqlConnector Spare = CreateConnector(Connection);
372
373                     Spare.CertificateSelectionCallback += Connection.CertificateSelectionCallbackDelegate;
374                     Spare.CertificateValidationCallback += Connection.CertificateValidationCallbackDelegate;
375                     Spare.PrivateKeySelectionCallback += Connection.PrivateKeySelectionCallbackDelegate;
376
377                     Spare.Open();
378
379                     Spare.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
380                     Spare.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
381                     Spare.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
382
383                     Queue.Enqueue(Spare);
384                 }
385             }
386
387             return Connector;
388         }
389
390         /// <summary>
391         /// Find an available shared connector in the shared pool, or create
392         /// a new one if none found.
393         /// </summary>
394         private NpgsqlConnector GetSharedConnector(NpgsqlConnection Connection)
395         {
396             // To be implemented
397
398             return null;
399         }
400
401         private NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
402         {
403             return new NpgsqlConnector(
404                        Connection.ConnectionStringValues.Clone(),
405                        Connection.Pooling,
406                        false
407                    );
408         }
409
410
411         /// <summary>
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.
416         /// </summary
417         public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
418         {
419             ConnectorQueue           Queue;
420
421             // Prevent multithread access to connection pool count.
422             lock(this)
423             {
424                 // Try to find a queue.
425                 Queue = (ConnectorQueue)PooledConnectors[Connection.ConnectionString.ToString()];
426
427                 if (Queue != null)
428                     Queue.UseCount--;
429
430             }
431         }
432
433         /// <summary>
434         /// Close the connector.
435         /// </summary>
436         /// <param name="Connector">Connector to release</param>
437         private void UngetNonPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
438         {
439             Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
440             Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
441             Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
442
443             if (Connector.Transaction != null)
444             {
445                 Connector.Transaction.Cancel();
446             }
447
448             Connector.Close();
449         }
450
451         /// <summary>
452         /// Put a pooled connector into the pool queue.
453         /// </summary>
454         /// <param name="Connector">Connector to pool</param>
455         private void UngetPooledConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
456         {
457             ConnectorQueue           Queue;
458
459             // Find the queue.
460             Queue = (ConnectorQueue)PooledConnectors[Connector.ConnectionString.ToString()];
461
462             if (Queue == null)
463             {
464                 throw new InvalidOperationException("Internal: No connector queue found for existing connector.");
465             }
466
467             Connector.CertificateSelectionCallback -= Connection.CertificateSelectionCallbackDelegate;
468             Connector.CertificateValidationCallback -= Connection.CertificateValidationCallbackDelegate;
469             Connector.PrivateKeySelectionCallback -= Connection.PrivateKeySelectionCallbackDelegate;
470
471             Queue.UseCount--;
472
473             if (! Connector.IsInitialized)
474             {
475                 if (Connector.Transaction != null)
476                 {
477                     Connector.Transaction.Cancel();
478                 }
479
480                 Connector.Close();
481             }
482             else
483             {
484                 if (Connector.Transaction != null)
485                 {
486                     try
487                     {
488                         Connector.Transaction.Rollback();
489                     }
490                     catch {
491                         Connector.Close()
492                         ;
493                     }
494                 }
495         }
496
497         if (Connector.State == System.Data.ConnectionState.Open)
498             {
499                 // Release all plans and portals associated with this connector.
500                 Connector.ReleasePlansPortals();
501
502                 Queue.Enqueue(Connector);
503             }
504         }
505
506         /// <summary>
507         /// Stop sharing a shared connector.
508         /// </summary>
509         /// <param name="Connector">Connector to unshare</param>
510         private void UngetSharedConnector(NpgsqlConnection Connection, NpgsqlConnector Connector)
511         {
512             // To be implemented
513         }
514     }
515 }