/******************************************************** * ADO.NET 2.0 Data Provider for SQLite Version 3.X * Written by Robert Simpson (robert@blackcastlesoft.com) * * Released to the public domain, use at your own risk! ********************************************************/ namespace System.Data.SQLite { using System; using System.Collections.Generic; using System.Threading; internal static class SQLiteConnectionPool { /// /// Keeps track of connections made on a specified file. The PoolVersion dictates whether old objects get /// returned to the pool or discarded when no longer in use. /// internal class Pool { internal readonly Queue Queue = new Queue(); internal int PoolVersion; internal int MaxPoolSize; internal Pool(int version, int maxSize) { PoolVersion = version; MaxPoolSize = maxSize; } } /// /// The connection pool object /// private static SortedList _connections = new SortedList(StringComparer.OrdinalIgnoreCase); /// /// The default version number new pools will get /// private static int _poolVersion = 1; /// /// The number of connections successfully opened from any pool. /// This value is incremented by the Remove method. /// private static int _poolOpened = 0; /// /// The number of connections successfully closed from any pool. /// This value is incremented by the Add method. /// private static int _poolClosed = 0; /// /// Counts the number of pool entries matching the specified file name. /// /// The file name to match or null to match all files. /// The pool entry counts for each matching file. /// The total number of connections successfully opened from any pool. /// The total number of connections successfully closed from any pool. /// The total number of pool entries for all matching files. internal static void GetCounts( string fileName, ref Dictionary counts, ref int openCount, ref int closeCount, ref int totalCount ) { lock (_connections) { openCount = _poolOpened; closeCount = _poolClosed; if (counts == null) { counts = new Dictionary( StringComparer.OrdinalIgnoreCase); } if (fileName != null) { Pool queue; if (_connections.TryGetValue(fileName, out queue)) { Queue poolQueue = queue.Queue; int count = (poolQueue != null) ? poolQueue.Count : 0; counts.Add(fileName, count); totalCount += count; } } else { foreach (KeyValuePair pair in _connections) { if (pair.Value == null) continue; Queue poolQueue = pair.Value.Queue; int count = (poolQueue != null) ? poolQueue.Count : 0; counts.Add(pair.Key, count); totalCount += count; } } } } /// /// Attempt to pull a pooled connection out of the queue for active duty /// /// The filename for a desired connection /// The maximum size the connection pool for the filename can be /// The pool version the returned connection will belong to /// Returns NULL if no connections were available. Even if none are, the poolversion will still be a valid pool version internal static SQLiteConnectionHandle Remove(string fileName, int maxPoolSize, out int version) { Queue poolQueue; // // NOTE: This lock cannot be held while checking the queue for available // connections because other methods of this class are called from // the GC finalizer thread and we use the WaitForPendingFinalizers // method (below). Holding this lock while calling that method // would therefore result in a deadlock. This lock is held while // a temporary copy of the queue is created. // lock (_connections) { Pool queue; // Default to the highest pool version version = _poolVersion; // If we didn't find a pool for this file, create one even though it will be empty. // We have to do this here because otherwise calling ClearPool() on the file will not work for active connections // that have never seen the pool yet. if (_connections.TryGetValue(fileName, out queue) == false) { queue = new Pool(_poolVersion, maxPoolSize); _connections.Add(fileName, queue); return null; } // We found a pool for this file, so use its version number version = queue.PoolVersion; queue.MaxPoolSize = maxPoolSize; ResizePool(queue, false); // Try and get a pooled connection from the queue poolQueue = new Queue(queue.Queue); if (poolQueue == null) return null; } while (poolQueue.Count > 0) { WeakReference cnn = poolQueue.Dequeue(); if (cnn == null) continue; SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle; if (hdl == null) continue; // // BUGFIX: For ticket [996d13cd87], step #1. After this point, // make sure that the finalizer for the connection handle // just obtained from the queue cannot START running (i.e. // it may still be pending but it will no longer start // after this point). // GC.SuppressFinalize(hdl); try { // // BUGFIX: For ticket [996d13cd87], step #2. Now, we must wait // for all pending finalizers which have STARTED running // and have not yet COMPLETED. This must be done just // in case the finalizer for the connection handle just // obtained from the queue has STARTED running at some // point before SuppressFinalize was called on it. // // After this point, checking properties of the // connection handle (e.g. IsClosed) should work // reliably without having to worry that they will // (due to the finalizer) change out from under us. // GC.WaitForPendingFinalizers(); // // BUGFIX: For ticket [996d13cd87], step #3. Next, verify that // the connection handle is actually valid and [still?] // not closed prior to actually returning it to our // caller. // if (!hdl.IsClosed && !hdl.IsInvalid) { Interlocked.Increment(ref _poolOpened); return hdl; } } finally { // // BUGFIX: For ticket [996d13cd87], step #4. Finally, we must // re-register the connection handle for finalization // now that we have a strong reference to it (i.e. the // finalizer run at least until the connection is // subsequently closed). // GC.ReRegisterForFinalize(hdl); } #pragma warning disable 162 GC.KeepAlive(hdl); /* NOTE: Unreachable code. */ #pragma warning restore 162 } return null; } /// /// Clears out all pooled connections and rev's up the default pool version to force all old active objects /// not in the pool to get discarded rather than returned to their pools. /// internal static void ClearAllPools() { lock (_connections) { foreach (KeyValuePair pair in _connections) { if (pair.Value == null) continue; Queue poolQueue = pair.Value.Queue; while (poolQueue.Count > 0) { WeakReference cnn = poolQueue.Dequeue(); if (cnn == null) continue; SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle; if (hdl != null) { hdl.Dispose(); } GC.KeepAlive(hdl); } // Keep track of the highest revision so we can go one higher when we're finished if (_poolVersion <= pair.Value.PoolVersion) _poolVersion = pair.Value.PoolVersion + 1; } // All pools are cleared and we have a new highest version number to force all old version active items to get discarded // instead of going back to the queue when they are closed. // We can get away with this because we've pumped up the _poolVersion out of range of all active connections, so they // will all get discarded when they try to put themselves back in their pool. _connections.Clear(); } } /// /// Clear a given pool for a given filename. Discards anything in the pool for the given file, and revs the pool /// version so current active objects on the old version of the pool will get discarded rather than be returned to the pool. /// /// The filename of the pool to clear internal static void ClearPool(string fileName) { lock (_connections) { Pool queue; if (_connections.TryGetValue(fileName, out queue) == true) { queue.PoolVersion++; Queue poolQueue = queue.Queue; if (poolQueue == null) return; while (poolQueue.Count > 0) { WeakReference cnn = poolQueue.Dequeue(); if (cnn == null) continue; SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle; if (hdl != null) { hdl.Dispose(); } GC.KeepAlive(hdl); } } } } /// /// Return a connection to the pool for someone else to use. /// /// The filename of the pool to use /// The connection handle to pool /// The pool version the handle was created under /// /// If the version numbers don't match between the connection and the pool, then the handle is discarded. /// internal static void Add(string fileName, SQLiteConnectionHandle hdl, int version) { lock (_connections) { // If the queue doesn't exist in the pool, then it must've been cleared sometime after the connection was created. Pool queue; if (_connections.TryGetValue(fileName, out queue) == true && version == queue.PoolVersion) { ResizePool(queue, true); Queue poolQueue = queue.Queue; if (poolQueue == null) return; poolQueue.Enqueue(new WeakReference(hdl, false)); Interlocked.Increment(ref _poolClosed); } else { hdl.Close(); } GC.KeepAlive(hdl); } } /// /// We don't have to thread-lock anything in this function, because it's only called by other functions above /// which already have a thread-safe lock. /// /// The queue to resize /// If a function intends to add to the pool, this is true, which forces the resize /// to take one more than it needs from the pool private static void ResizePool(Pool queue, bool forAdding) { int target = queue.MaxPoolSize; if (forAdding && target > 0) target--; Queue poolQueue = queue.Queue; if (poolQueue == null) return; while (poolQueue.Count > target) { WeakReference cnn = poolQueue.Dequeue(); if (cnn == null) continue; SQLiteConnectionHandle hdl = cnn.Target as SQLiteConnectionHandle; if (hdl != null) { hdl.Dispose(); } GC.KeepAlive(hdl); } } } }