2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / FirebirdSql.Data.Firebird / FirebirdSql.Data.Firebird / FbConnectionPool.cs
1 /*
2  *      Firebird ADO.NET Data provider for .NET and     Mono 
3  * 
4  *         The contents of this file are subject to     the     Initial 
5  *         Developer's Public License Version 1.0 (the "License"); 
6  *         you may not use this file except     in compliance with the 
7  *         License.     You     may     obtain a copy of the License at 
8  *         http://www.firebirdsql.org/index.php?op=doc&id=idpl
9  *
10  *         Software     distributed     under the License is distributed on     
11  *         an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
12  *         express or implied.  See     the     License for     the     specific 
13  *         language     governing rights and limitations under the License.
14  * 
15  *      Copyright (c) 2002,     2004 Carlos     Guzman Alvarez
16  *      All     Rights Reserved.
17  */
18
19 using System;
20 using System.Data;
21 using System.Collections;
22 using System.Threading;
23
24 using FirebirdSql.Data.Common;
25
26 namespace FirebirdSql.Data.Firebird
27 {
28         internal sealed class FbPoolManager
29         {
30                 #region Static fields
31
32                 private static FbPoolManager instance;
33
34                 #endregion
35
36                 #region Fields
37
38                 private Hashtable pools;
39                 private Hashtable handlers;
40
41                 #endregion
42
43                 #region Static Properties
44
45                 public static FbPoolManager     Instance
46                 {
47                         get
48                         {
49                                 if (FbPoolManager.instance == null)
50                                 {
51                                         FbPoolManager.instance = new FbPoolManager();
52                                 }
53
54                                 return FbPoolManager.instance;
55                         }
56                 }
57
58                 #endregion
59
60                 #region Priperties
61
62                 public int PoolsCount
63                 {
64                         get     
65                         {
66                                 if (this.pools != null)
67                                 {
68                                         return this.pools.Count;
69                                 }
70                                 return 0;
71                         }
72                 }
73
74                 #endregion
75
76                 #region Constructors
77
78                 private FbPoolManager()
79                 {
80                         this.pools              = Hashtable.Synchronized(new Hashtable());
81                         this.handlers   = Hashtable.Synchronized(new Hashtable());
82                 }
83
84                 #endregion
85
86                 #region Methods
87
88                 public FbConnectionPool FindPool(string connectionString)
89                 {
90                         FbConnectionPool pool = null;
91
92                         lock (this)
93                         {
94                                 if (this.pools.ContainsKey(connectionString.GetHashCode()))
95                                 {
96                                         pool = (FbConnectionPool)pools[connectionString.GetHashCode()];
97                                 }
98                         }
99
100                         return pool;
101                 }
102
103                 public FbConnectionPool CreatePool(string connectionString)
104                 {
105                         FbConnectionPool pool = null;
106
107                         lock (this)
108                         {
109                                 pool = this.FindPool(connectionString);
110
111                                 if (pool ==     null)
112                                 {
113                                         lock (this.pools.SyncRoot)
114                                         {
115                                                 int     hashcode = connectionString.GetHashCode();
116
117                                                 // Create an empty pool handler
118                                                 EmptyPoolEventHandler handler = new     EmptyPoolEventHandler(this.OnEmptyPool);
119
120                                                 this.handlers.Add(hashcode,     handler);
121
122                                                 // Create the new connection pool
123                                                 pool = new FbConnectionPool(connectionString);
124
125                                                 this.pools.Add(hashcode, pool);
126
127                                                 pool.EmptyPool += handler;
128                                         }
129                                 }
130                         }
131
132                         return pool;
133                 }
134
135                 public void     ClearAllPools()
136                 {
137                         lock (this)
138                         {
139                                 lock (this.pools.SyncRoot)
140                                 {
141                                         FbConnectionPool[] tempPools = new FbConnectionPool[this.pools.Count];
142
143                                         this.pools.Values.CopyTo(tempPools,     0);
144
145                                         foreach (FbConnectionPool pool in tempPools)
146                                         {
147                                                 // Clear pool
148                                                 pool.Clear();
149                                         }
150
151                                         // Clear Hashtables
152                                         this.pools.Clear();
153                                         this.handlers.Clear();
154                                 }
155                         }
156                 }
157
158                 public void     ClearPool(string connectionString)
159                 {
160                         lock (this)
161                         {
162                                 lock (this.pools.SyncRoot)
163                                 {
164                                         int     hashCode = connectionString.GetHashCode();
165
166                                         if (this.pools.ContainsKey(hashCode))
167                                         {
168                                                 FbConnectionPool pool = (FbConnectionPool)this.pools[hashCode];
169
170                                                 // Clear pool
171                                                 pool.Clear();
172                                         }
173                                 }
174                         }
175                 }
176
177                 #endregion
178
179                 #region Private Methods
180
181                 private void OnEmptyPool(object sender, EventArgs e)
182                 {
183                         lock (this.pools.SyncRoot)
184                         {
185                                 int     hashCode = (int)sender;
186
187                                 if (this.pools.ContainsKey(hashCode))
188                                 {
189                                         FbConnectionPool pool = (FbConnectionPool)this.pools[hashCode];
190                                         EmptyPoolEventHandler handler = (EmptyPoolEventHandler)this.handlers[hashCode];
191
192                                         pool.EmptyPool -= handler;
193
194                                         this.pools.Remove(hashCode);
195                                         this.handlers.Remove(hashCode);
196
197                                         pool    = null;
198                                         handler = null;
199                                 }
200                         }
201                 }
202
203                 #endregion
204         }
205         
206         internal delegate void EmptyPoolEventHandler(object     sender, EventArgs e);
207
208         internal class FbConnectionPool : MarshalByRefObject
209         {
210                 #region Fields
211
212                 private string                          connectionString;
213                 private FbConnectionString      options;
214                 private ArrayList                       locked;
215                 private ArrayList                       unlocked;
216                 private Thread                          cleanUpThread;          
217                 private bool                            isRunning;
218                 private long                            lifeTime;
219
220                 #endregion
221
222                 #region Events
223
224                 public event EmptyPoolEventHandler EmptyPool;
225
226                 #endregion
227
228                 #region Properties
229
230                 public int Count
231                 {
232                         get     { return this.unlocked.Count + this.locked.Count; }
233                 }
234
235                 #endregion
236
237                 #region Constructors
238
239                 public FbConnectionPool(string connectionString)
240                 {
241                         this.connectionString   = connectionString;
242                         this.options                    = new FbConnectionString(connectionString);
243                         this.lifeTime                   = this.options.ConnectionLifeTime *     TimeSpan.TicksPerSecond;
244
245                         if (this.options.MaxPoolSize == 0)
246                         {
247                                 this.locked             = ArrayList.Synchronized(new ArrayList());
248                                 this.unlocked   = ArrayList.Synchronized(new ArrayList());
249                         }
250                         else
251                         {
252                                 this.locked             = ArrayList.Synchronized(new ArrayList(this.options.MaxPoolSize));
253                                 this.unlocked   = ArrayList.Synchronized(new ArrayList(this.options.MaxPoolSize));
254                         }
255
256                         // If a minimun number of connections is requested
257                         // initialize the pool
258                         this.Initialize();
259
260                         // Start the cleanup thread     only if needed
261                         if (this.lifeTime != 0)
262                         {
263                                 this.isRunning = true;
264
265                                 this.cleanUpThread = new Thread(new     ThreadStart(this.RunCleanup));
266                                 this.cleanUpThread.Name = "Cleanup Thread";                     
267                                 this.cleanUpThread.Start();
268                                 this.cleanUpThread.IsBackground = true;
269                         }
270                 }
271
272                 #endregion
273
274                 #region Methods
275
276                 public void     CheckIn(FbConnectionInternal connection)
277                 {
278                         connection.OwningConnection     = null;
279                         connection.Created                      = System.DateTime.Now.Ticks;
280
281                         this.locked.Remove(connection);
282                         this.unlocked.Add(connection);
283                 }
284
285                 public FbConnectionInternal     CheckOut()
286                 {
287                         FbConnectionInternal newConnection = null;
288
289                         lock (this)
290                         {
291                                 this.CheckMaxPoolSize();
292
293                                 lock (this.unlocked.SyncRoot)
294                                 {
295                                         newConnection = this.GetConnection();
296                                         if (newConnection != null)
297                                         {
298                                                 return newConnection;
299                                         }
300                                 }
301
302                                 newConnection = this.Create();
303
304                                 // Set connection pooling settings to the new connection
305                                 newConnection.Lifetime  = this.options.ConnectionLifeTime;
306                                 newConnection.Pooled    = true;
307
308                                 // Added to     the     locked connections list.
309                                 this.locked.Add(newConnection);
310                         }
311                         
312                         return newConnection;
313                 }       
314
315                 public void     Clear()
316                 {
317                         lock (this)
318                         {
319                                 // Stop cleanup thread
320                                 if (this.cleanUpThread != null)
321                                 {
322                                         this.cleanUpThread.Abort();
323                                         this.cleanUpThread.Join();
324                                 }
325
326                                 // Close all unlocked connections
327                                 FbConnectionInternal[] list     = (FbConnectionInternal[])this.unlocked.ToArray(typeof(FbConnectionInternal));
328
329                                 foreach (FbConnectionInternal connection in     list)
330                                 {
331                                         connection.Disconnect();
332                                 }
333
334                                 // Close all locked     connections
335                                 list = (FbConnectionInternal[])this.locked.ToArray(typeof(FbConnectionInternal));
336
337                                 foreach (FbConnectionInternal connection in     list)
338                                 {
339                                         connection.Disconnect();
340                                 }
341
342                                 // Clear lists
343                                 this.unlocked.Clear();
344                                 this.locked.Clear();
345
346                                 // Raise EmptyPool event
347                                 if (this.EmptyPool != null)
348                                 {
349                                         this.EmptyPool(this.connectionString.GetHashCode(),     null);
350                                 }
351
352                                 // Reset fields
353                                 this.unlocked                   = null;
354                                 this.locked                             = null;
355                                 this.connectionString   = null;
356                                 this.cleanUpThread              = null;
357                                 this.EmptyPool                  = null;
358                         }
359                 }
360
361                 #endregion
362
363                 #region Private Methods
364
365                 private bool CheckMinPoolSize()
366                 {
367                         if (this.options.MinPoolSize > 0 &&     this.Count == this.options.MinPoolSize)
368                         {
369                                 return false;
370                         }
371                         else
372                         {
373                                 return true;
374                         }
375                 }
376
377                 private void CheckMaxPoolSize()
378                 {
379                         lock (this)
380                         {
381                                 if (this.options.MaxPoolSize > 0 &&     
382                                         (this.Count     + 1) >= this.options.MaxPoolSize)
383                                 {
384                                         long timeout    = this.options.ConnectionTimeout * TimeSpan.TicksPerSecond;
385                                         long start              = DateTime.Now.Ticks;
386
387                                         while (true)
388                                         {
389                                                 if ((this.Count + 1) >= this.options.MaxPoolSize)
390                                                 {
391                                                         if ((DateTime.Now.Ticks - start) > timeout)
392                                                         {
393                                                                 throw new SystemException("Timeout exceeded.");
394                                                         }
395
396                                                         Thread.Sleep(100);
397                                                 }
398                                                 else
399                                                 {
400                                                         break;
401                                                 }
402                                         }
403                                 }
404                         }
405                 }
406
407                 private void Initialize()
408                 {
409                         lock (this)
410                         {
411                                 for     (int i = 0;     i <     this.options.MinPoolSize; i++)
412                                 {
413                                         this.unlocked.Add(this.Create());
414                                 }
415                         }
416                 }
417
418                 private FbConnectionInternal Create()
419                 {
420                         FbConnectionInternal connection = new FbConnectionInternal(this.options);
421                         connection.Connect();
422
423                         connection.Pooled       = true;
424                         connection.Created      = DateTime.Now.Ticks;
425
426                         return connection;
427                 }
428
429                 private FbConnectionInternal GetConnection()
430                 {
431                         FbConnectionInternal[]  list    = (FbConnectionInternal[])this.unlocked.ToArray(typeof(FbConnectionInternal));
432                         FbConnectionInternal    result  = null;
433                         long                                    check   = -1;
434
435                         Array.Reverse(list);
436
437                         foreach (FbConnectionInternal connection in     list)
438                         {
439                                 if (connection.Verify())
440                                 {
441                                         if (this.lifeTime != 0)
442                                         {
443                                                 long now        = DateTime.Now.Ticks;
444                                                 long expire     = connection.Created + this.lifeTime;
445
446                                                 if (now >= expire)
447                                                 {
448                                                         if (this.CheckMinPoolSize())
449                                                         {
450                                                                 this.unlocked.Remove(connection);
451                                                                 this.Expire(connection);
452                                                         }
453                                                 }
454                                                 else
455                                                 {
456                                                         if (expire > check)
457                                                         {
458                                                                 check   = expire;
459                                                                 result  = connection;
460                                                         }
461                                                 }
462                                         }
463                                         else
464                                         {
465                                                 result = connection;
466                                                 break;
467                                         }
468                                 }
469                                 else
470                                 {
471                                         this.unlocked.Remove(connection);
472                                         this.Expire(connection);
473                                 }
474                         }
475
476                         if (result != null)
477                         {
478                                 this.unlocked.Remove(result);
479                                 this.locked.Add(result);
480                         }
481
482                         return result;
483                 }
484
485                 private void RunCleanup()
486                 {
487                         int     interval = Convert.ToInt32(TimeSpan.FromTicks(this.lifeTime).TotalMilliseconds);
488
489                         if (interval > 60000)
490                         {
491                                 interval = 60000;
492                         }
493
494                         try
495                         {
496                                 while (this.isRunning)
497                                 {
498                                         Thread.Sleep(interval);
499
500                                         this.Cleanup();
501                                 
502                                         if (this.Count == 0)
503                                         {
504                                                 lock (this)
505                                                 {
506                                                         // Empty pool
507                                                         if (this.EmptyPool != null)
508                                                         {
509                                                                 this.EmptyPool(this.connectionString.GetHashCode(),     null);
510                                                         }
511
512                                                         // Stop running
513                                                         this.isRunning = false;
514                                                 }
515                                         }
516                                 }
517                         }
518                         catch (ThreadAbortException)
519                         {
520                                 this.isRunning = false;
521                         }
522                 }
523
524                 private void Expire(FbConnectionInternal connection)
525                 {
526                         try     
527                         {
528                                 if (connection.Verify())
529                                 {
530                                         connection.Disconnect();
531                                 }
532                         }
533                         catch (Exception)
534                         {
535                                 throw new FbException("Error closing database connection.");
536                         }
537                 }
538                 
539                 private void Cleanup()
540                 {
541                         lock (this.unlocked.SyncRoot)
542                         {
543                                 if (this.unlocked.Count > 0     && this.lifeTime !=     0)
544                                 {
545                                         FbConnectionInternal[] list     = (FbConnectionInternal[])this.unlocked.ToArray(typeof(FbConnectionInternal));
546
547                                         foreach (FbConnectionInternal connection in     list)
548                                         {
549                                                 long now = DateTime.Now.Ticks;
550                                                 long expire     = connection.Created + this.lifeTime;
551
552                                                 if (now >= expire)
553                                                 {
554                                                         if (this.CheckMinPoolSize())
555                                                         {
556                                                                 this.unlocked.Remove(connection);
557                                                                 this.Expire(connection);
558                                                         }
559                                                 }
560                                         }
561                                 }
562                         }
563                 }
564
565                 #endregion
566         }
567 }