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