/******************************************************** * 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; #if !PLATFORM_COMPACTFRAMEWORK && DEBUG using System.Text; #endif using System.Threading; /////////////////////////////////////////////////////////////////////////// #region Null Connection Pool Class #if !PLATFORM_COMPACTFRAMEWORK && DEBUG /// /// This class implements a connection pool where all methods of the /// interface are NOPs. This class /// is used for testing purposes only. /// internal sealed class NullConnectionPool : ISQLiteConnectionPool { #region Private Data /// /// This field keeps track of all method calls made into the /// interface methods of this /// class. /// private StringBuilder log; /////////////////////////////////////////////////////////////////////// /// /// Non-zero to dispose of database connection handles received via the /// method. /// private bool dispose; #endregion /////////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs a connection pool object where all methods of the /// interface are NOPs. This /// class is used for testing purposes only. /// private NullConnectionPool() { log = new StringBuilder(); } #endregion /////////////////////////////////////////////////////////////////////// #region Public Constructors /// /// Constructs a connection pool object where all methods of the /// interface are NOPs. This /// class is used for testing purposes only. /// /// /// Non-zero to dispose of database connection handles received via the /// method. /// public NullConnectionPool( bool dispose ) : this() { this.dispose = dispose; } #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteConnectionPool Members /// /// 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. /// public void GetCounts( string fileName, ref Dictionary counts, ref int openCount, ref int closeCount, ref int totalCount ) { if (log != null) { log.AppendFormat( "GetCounts(\"{0}\", {1}, {2}, {3}, {4}){5}", fileName, counts, openCount, closeCount, totalCount, Environment.NewLine); } } /////////////////////////////////////////////////////////////////////// /// /// Disposes of all pooled connections associated with the specified /// database file name. /// /// /// The database file name. /// public void ClearPool( string fileName ) { if (log != null) { log.AppendFormat( "ClearPool(\"{0}\"){1}", fileName, Environment.NewLine); } } /////////////////////////////////////////////////////////////////////// /// /// Disposes of all pooled connections. /// public void ClearAllPools() { if (log != null) { log.AppendFormat( "ClearAllPools(){0}", Environment.NewLine); } } /////////////////////////////////////////////////////////////////////// /// /// Adds a connection to the pool of those associated with the /// specified database file name. /// /// /// The database file name. /// /// /// The database connection handle. /// /// /// The connection pool version at the point the database connection /// handle was received from the connection pool. This is also the /// connection pool version that the database connection handle was /// created under. /// public void Add( string fileName, object handle, int version ) { if (log != null) { log.AppendFormat( "Add(\"{0}\", {1}, {2}){3}", fileName, handle, version, Environment.NewLine); } // // NOTE: If configured to do so, dispose of the received connection // handle now. // if (dispose) { IDisposable disposable = handle as IDisposable; if (disposable != null) disposable.Dispose(); } } /////////////////////////////////////////////////////////////////////// /// /// Removes a connection from the pool of those associated with the /// specified database file name with the intent of using it to /// interact with the database. /// /// /// The database file name. /// /// /// The new maximum size of the connection pool for the specified /// database file name. /// /// /// The connection pool version associated with the returned database /// connection handle, if any. /// /// /// The database connection handle associated with the specified /// database file name or null if it cannot be obtained. /// public object Remove( string fileName, int maxPoolSize, out int version ) { version = 0; if (log != null) { log.AppendFormat( "Remove(\"{0}\", {1}, {2}){3}", fileName, maxPoolSize, version, Environment.NewLine); } return null; } #endregion /////////////////////////////////////////////////////////////////////// #region System.Object Overrides /// /// Overrides the default method /// to provide a log of all methods called on the /// interface. /// /// /// A string containing a log of all method calls into the /// interface, along with their /// parameters, delimited by . /// public override string ToString() { return (log != null) ? log.ToString() : String.Empty; } #endregion } #endif #endregion /////////////////////////////////////////////////////////////////////////// #region Public Connection Pool Interface /// /// This interface represents a custom connection pool implementation /// usable by System.Data.SQLite. /// public interface ISQLiteConnectionPool { /// /// 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. /// void GetCounts(string fileName, ref Dictionary counts, ref int openCount, ref int closeCount, ref int totalCount); /// /// Disposes of all pooled connections associated with the specified /// database file name. /// /// /// The database file name. /// void ClearPool(string fileName); /// /// Disposes of all pooled connections. /// void ClearAllPools(); /// /// Adds a connection to the pool of those associated with the /// specified database file name. /// /// /// The database file name. /// /// /// The database connection handle. /// /// /// The connection pool version at the point the database connection /// handle was received from the connection pool. This is also the /// connection pool version that the database connection handle was /// created under. /// void Add(string fileName, object handle, int version); /// /// Removes a connection from the pool of those associated with the /// specified database file name with the intent of using it to /// interact with the database. /// /// /// The database file name. /// /// /// The new maximum size of the connection pool for the specified /// database file name. /// /// /// The connection pool version associated with the returned database /// connection handle, if any. /// /// /// The database connection handle associated with the specified /// database file name or null if it cannot be obtained. /// object Remove(string fileName, int maxPoolSize, out int version); } #endregion /////////////////////////////////////////////////////////////////////////// #region Connection Pool Subsystem & Default Implementation /// /// This default method implementations in this class should not be used by /// applications that make use of COM (either directly or indirectly) due /// to possible deadlocks that can occur during finalization of some COM /// objects. /// internal static class SQLiteConnectionPool { #region Private Pool Class /// /// 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. /// private sealed class PoolQueue { #region Private Data /// /// The queue of weak references to the actual database connection /// handles. /// internal readonly Queue Queue = new Queue(); /////////////////////////////////////////////////////////////////// /// /// This pool version associated with the database connection /// handles in this pool queue. /// internal int PoolVersion; /////////////////////////////////////////////////////////////////// /// /// The maximum size of this pool queue. /// internal int MaxPoolSize; #endregion /////////////////////////////////////////////////////////////////// #region Private Constructors /// /// Constructs a connection pool queue using the specified version /// and maximum size. Normally, all the database connection /// handles in this pool are associated with a single database file /// name. /// /// /// The initial pool version for this connection pool queue. /// /// /// The initial maximum size for this connection pool queue. /// internal PoolQueue( int version, int maxSize ) { PoolVersion = version; MaxPoolSize = maxSize; } #endregion } #endregion /////////////////////////////////////////////////////////////////////// #region Private Static Data /// /// This field is used to synchronize access to the private static data /// in this class. /// private static readonly object _syncRoot = new object(); /////////////////////////////////////////////////////////////////////// /// /// When this field is non-null, it will be used to provide the /// implementation of all the connection pool methods; otherwise, /// the default method implementations will be used. /// private static ISQLiteConnectionPool _connectionPool = null; /////////////////////////////////////////////////////////////////////// /// /// The dictionary of connection pools, based on the normalized file /// name of the SQLite database. /// private static SortedList _queueList = 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; #endregion /////////////////////////////////////////////////////////////////////// #region ISQLiteConnectionPool Members (Static, Non-Formal) /// /// 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 ) { ISQLiteConnectionPool connectionPool = GetConnectionPool(); if (connectionPool != null) { connectionPool.GetCounts( fileName, ref counts, ref openCount, ref closeCount, ref totalCount); } else { lock (_syncRoot) { openCount = _poolOpened; closeCount = _poolClosed; if (counts == null) { counts = new Dictionary( StringComparer.OrdinalIgnoreCase); } if (fileName != null) { PoolQueue queue; if (_queueList.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 _queueList) { if (pair.Value == null) continue; Queue poolQueue = pair.Value.Queue; int count = (poolQueue != null) ? poolQueue.Count : 0; counts.Add(pair.Key, count); totalCount += count; } } } } } /////////////////////////////////////////////////////////////////////// /// /// Disposes of all pooled connections associated with the specified /// database file name. /// /// /// The database file name. /// internal static void ClearPool(string fileName) { ISQLiteConnectionPool connectionPool = GetConnectionPool(); if (connectionPool != null) { connectionPool.ClearPool(fileName); } else { lock (_syncRoot) { PoolQueue queue; if (_queueList.TryGetValue(fileName, out queue)) { queue.PoolVersion++; Queue poolQueue = queue.Queue; if (poolQueue == null) return; while (poolQueue.Count > 0) { WeakReference connection = poolQueue.Dequeue(); if (connection == null) continue; SQLiteConnectionHandle handle = connection.Target as SQLiteConnectionHandle; if (handle != null) handle.Dispose(); GC.KeepAlive(handle); } } } } } /////////////////////////////////////////////////////////////////////// /// /// Disposes of all pooled connections. /// internal static void ClearAllPools() { ISQLiteConnectionPool connectionPool = GetConnectionPool(); if (connectionPool != null) { connectionPool.ClearAllPools(); } else { lock (_syncRoot) { foreach (KeyValuePair pair in _queueList) { if (pair.Value == null) continue; Queue poolQueue = pair.Value.Queue; while (poolQueue.Count > 0) { WeakReference connection = poolQueue.Dequeue(); if (connection == null) continue; SQLiteConnectionHandle handle = connection.Target as SQLiteConnectionHandle; if (handle != null) handle.Dispose(); GC.KeepAlive(handle); } // // NOTE: Keep track of the highest revision so we can // go one higher when we are finished. // if (_poolVersion <= pair.Value.PoolVersion) _poolVersion = pair.Value.PoolVersion + 1; } // // NOTE: 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 have pumped up the pool // version out of range of all active connections, // so they will all get discarded when they try to // put themselves back into their pools. // _queueList.Clear(); } } } /////////////////////////////////////////////////////////////////////// /// /// Adds a connection to the pool of those associated with the /// specified database file name. /// /// /// The database file name. /// /// /// The database connection handle. /// /// /// The connection pool version at the point the database connection /// handle was received from the connection pool. This is also the /// connection pool version that the database connection handle was /// created under. /// internal static void Add( string fileName, SQLiteConnectionHandle handle, int version ) { ISQLiteConnectionPool connectionPool = GetConnectionPool(); if (connectionPool != null) { connectionPool.Add(fileName, handle, version); } else { lock (_syncRoot) { // // NOTE: If the queue does not exist in the pool, then it // must have been cleared sometime after the // connection was created. // PoolQueue queue; if (_queueList.TryGetValue(fileName, out queue) && (version == queue.PoolVersion)) { ResizePool(queue, true); Queue poolQueue = queue.Queue; if (poolQueue == null) return; poolQueue.Enqueue(new WeakReference(handle, false)); Interlocked.Increment(ref _poolClosed); } else { handle.Close(); } GC.KeepAlive(handle); } } } /////////////////////////////////////////////////////////////////////// /// /// Removes a connection from the pool of those associated with the /// specified database file name with the intent of using it to /// interact with the database. /// /// /// The database file name. /// /// /// The new maximum size of the connection pool for the specified /// database file name. /// /// /// The connection pool version associated with the returned database /// connection handle, if any. /// /// /// The database connection handle associated with the specified /// database file name or null if it cannot be obtained. /// internal static SQLiteConnectionHandle Remove( string fileName, int maxPoolSize, out int version ) { ISQLiteConnectionPool connectionPool = GetConnectionPool(); if (connectionPool != null) { return connectionPool.Remove(fileName, maxPoolSize, out version) as SQLiteConnectionHandle; } else { int localVersion; 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. Instead, this lock // is held only while a temporary copy of the queue is // created, and if necessary, when committing changes // back to that original queue prior to returning from // this method. // lock (_syncRoot) { PoolQueue queue; // // NOTE: Default to the highest pool version. // version = _poolVersion; // // NOTE: 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 (!_queueList.TryGetValue(fileName, out queue)) { queue = new PoolQueue(_poolVersion, maxPoolSize); _queueList.Add(fileName, queue); return null; } // // NOTE: We found a pool for this file, so use its version // number. // version = localVersion = queue.PoolVersion; queue.MaxPoolSize = maxPoolSize; // // NOTE: Now, resize the pool to the new maximum size, if // necessary. // ResizePool(queue, false); // // NOTE: Try and get a pooled connection from the queue. // poolQueue = queue.Queue; if (poolQueue == null) return null; // // NOTE: Temporarily tranfer the queue for this file into // a local variable. The queue for this file will // be modified and then committed back to the real // pool list (below) prior to returning from this // method. // _queueList.Remove(fileName); poolQueue = new Queue(poolQueue); } try { while (poolQueue.Count > 0) { WeakReference connection = poolQueue.Dequeue(); if (connection == null) continue; SQLiteConnectionHandle handle = connection.Target as SQLiteConnectionHandle; if (handle == 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(handle); 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 (!handle.IsInvalid && !handle.IsClosed) { Interlocked.Increment(ref _poolOpened); return handle; } } finally { // // BUGFIX: For ticket [996d13cd87], step #4. Next, // we must re-register the connection // handle for finalization now that we have // a strong reference to it (i.e. the // finalizer will not run at least until // the connection is subsequently closed). // GC.ReRegisterForFinalize(handle); } GC.KeepAlive(handle); } } finally { // // BUGFIX: For ticket [996d13cd87], step #5. Finally, // commit any changes to the pool/queue for this // database file. // lock (_syncRoot) { // // NOTE: We must check [again] if a pool exists for // this file because one may have been added // while the search for an available connection // was in progress (above). // PoolQueue queue; Queue newPoolQueue; bool addPool; if (_queueList.TryGetValue(fileName, out queue)) { addPool = false; } else { addPool = true; queue = new PoolQueue(localVersion, maxPoolSize); } newPoolQueue = queue.Queue; while (poolQueue.Count > 0) newPoolQueue.Enqueue(poolQueue.Dequeue()); ResizePool(queue, false); if (addPool) _queueList.Add(fileName, queue); } } return null; } } #endregion /////////////////////////////////////////////////////////////////////// #region Private Helper Methods /// /// This method is used to obtain a reference to the custom connection /// pool implementation currently in use, if any. /// /// /// The custom connection pool implementation or null if the default /// connection pool implementation should be used. /// internal static ISQLiteConnectionPool GetConnectionPool() { lock (_syncRoot) { return _connectionPool; } } /////////////////////////////////////////////////////////////////////// /// /// This method is used to set the reference to the custom connection /// pool implementation to use, if any. /// /// /// The custom connection pool implementation to use or null if the /// default connection pool implementation should be used. /// internal static void SetConnectionPool( ISQLiteConnectionPool connectionPool ) { lock (_syncRoot) { _connectionPool = connectionPool; } } /////////////////////////////////////////////////////////////////////// /// /// We do not have to thread-lock anything in this function, because it /// is only called by other functions above which already take the lock. /// /// /// The pool 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( PoolQueue queue, bool add ) { int target = queue.MaxPoolSize; if (add && target > 0) target--; Queue poolQueue = queue.Queue; if (poolQueue == null) return; while (poolQueue.Count > target) { WeakReference connection = poolQueue.Dequeue(); if (connection == null) continue; SQLiteConnectionHandle handle = connection.Target as SQLiteConnectionHandle; if (handle != null) handle.Dispose(); GC.KeepAlive(handle); } } #endregion } #endregion }