Index: Doc/Extra/version.html
==================================================================
--- Doc/Extra/version.html
+++ Doc/Extra/version.html
@@ -47,10 +47,11 @@
- Updated to SQLite 3.7.16.
- Skip checking loaded assemblies for types tagged with the SQLiteFunction attribute when the No_SQLiteFunctions environment variable is set. Pursuant to [e4c8121f7b].
- Add HexPassword connection string property to work around the inability to include a literal semicolon in a connection string property value. Pursuant to [1c456ae75f].
- Add static Execute method to the SQLiteCommand class.
+ - Support custom connection pool implementations by adding the ISQLiteConnectionPool interface, the static SQLiteConnection.ConnectionPool property, and the static CreateHandle method in addition to modifying the SQLiteConnectionPool class. Pursuant to [393d954be0].
- Add public constructor to the SQLiteDataAdapter class that allows passing the parseViaFramework parameter to the SQLiteConnection constructor.
- When built with the CHECK_STATE compile-time option, skip throwing exceptions from the SQLiteDataReader class when the object is being disposed.
- Support automatic value conversions for columns with a declared type of BIGUINT, INTEGER8, INTEGER16, INTEGER32, INTEGER64, SMALLUINT, TINYSINT, UNSIGNEDINTEGER, UNSIGNEDINTEGER8, UNSIGNEDINTEGER16, UNSIGNEDINTEGER32, UNSIGNEDINTEGER64, INT8, INT16, INT32, INT64, UINT, UINT8, UINT16, UINT32, UINT64, or ULONG.
- Add BindUInt32AsInt64 connection flag to force binding of UInt32 values as Int64 instead. Pursuant to [c010fa6584].
- Remove AUTOINCREMENT from the column type name map. ** Potentially Incompatible Change **
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -197,14 +197,14 @@
/// -
/// Pooling
///
/// True - Use connection pooling.
/// False - Do not use connection pooling.
- /// WARNING: Setting this property to True should be avoided by
- /// applications that make use of WPF (either directly or indirectly) due
- /// to possible deadlocks that can occur during the finalization of some
- /// WPF objects.
+ /// WARNING: When using the default connection pool implementation,
+ /// setting this property to True should be avoided by applications that make
+ /// use of COM (either directly or indirectly) due to possible deadlocks that
+ /// can occur during the finalization of some COM objects.
///
/// N
/// False
///
/// -
@@ -636,10 +636,48 @@
{
_handlers -= value;
}
}
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This property is used to obtain or set the custom connection pool
+ /// implementation to use, if any. Setting this property to null will
+ /// cause the default connection pool implementation to be used.
+ ///
+ public static ISQLiteConnectionPool ConnectionPool
+ {
+ get { return SQLiteConnectionPool.GetConnectionPool(); }
+ set { SQLiteConnectionPool.SetConnectionPool(value); }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Creates and returns a new managed database connection handle. This
+ /// method is intended to be used by implementations of the
+ /// interface only. In theory, it
+ /// could be used by other classes; however, that usage is not supported.
+ ///
+ ///
+ /// This must be a native database connection handle returned by the
+ /// SQLite core library and it must remain valid and open during the
+ /// entire duration of the calling method.
+ ///
+ ///
+ /// The new managed database connection handle or null if it cannot be
+ /// created.
+ ///
+ public static object CreateHandle(
+ IntPtr nativeHandle
+ )
+ {
+ if (nativeHandle == IntPtr.Zero) return null;
+ return new SQLiteConnectionHandle(nativeHandle);
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
#region Backup API Members
///
@@ -1175,14 +1213,14 @@
///
-
/// Pooling
///
/// True - Use connection pooling.
/// False - Do not use connection pooling.
- /// WARNING: Setting this property to True should be avoided by
- /// applications that make use of WPF (either directly or indirectly) due
- /// to possible deadlocks that can occur during the finalization of some
- /// WPF objects.
+ /// WARNING: When using the default connection pool implementation,
+ /// setting this property to True should be avoided by applications that
+ /// make use of COM (either directly or indirectly) due to possible
+ /// deadlocks that can occur during the finalization of some COM objects.
///
/// N
/// False
///
/// -
Index: System.Data.SQLite/SQLiteConnectionPool.cs
==================================================================
--- System.Data.SQLite/SQLiteConnectionPool.cs
+++ System.Data.SQLite/SQLiteConnectionPool.cs
@@ -5,411 +5,1024 @@
* Released to the public domain, use at your own risk!
********************************************************/
namespace System.Data.SQLite
{
- using System;
- using System.Collections.Generic;
- using System.Threading;
-
- ///
- /// This class should not be used by applications that make use of WPF
- /// (either directly or indirectly) due to possible deadlocks that can occur
- /// during finalization of some WPF objects.
- ///
- 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)
- {
- 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 (_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 = localVersion = queue.PoolVersion;
- queue.MaxPoolSize = maxPoolSize;
-
- ResizePool(queue, false);
-
- // 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.
- //
- _connections.Remove(fileName);
- poolQueue = new Queue(poolQueue);
- }
-
- try
- {
- 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.IsInvalid && !hdl.IsClosed)
- {
- Interlocked.Increment(ref _poolOpened);
- return hdl;
- }
- }
- 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(hdl);
- }
-
- GC.KeepAlive(hdl);
- }
- }
- finally
- {
- //
- // BUGFIX: For ticket [996d13cd87], step #5. Finally, commit any
- // changes to the pool/queue for this database file.
- //
- lock (_connections)
- {
- //
- // 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).
- //
- Pool queue;
- Queue newPoolQueue;
- bool addPool;
-
- if (_connections.TryGetValue(fileName, out queue))
- {
- addPool = false;
- }
- else
- {
- addPool = true;
- queue = new Pool(localVersion, maxPoolSize);
- }
-
- newPoolQueue = queue.Queue;
-
- while (poolQueue.Count > 0)
- newPoolQueue.Enqueue(poolQueue.Dequeue());
-
- ResizePool(queue, false);
-
- if (addPool)
- _connections.Add(fileName, queue);
- }
- }
-
- 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);
- }
- }
- }
+ 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
}
Index: Tests/common.eagle
==================================================================
--- Tests/common.eagle
+++ Tests/common.eagle
@@ -1785,11 +1785,11 @@
#
tputs $::test_channel \
"---- checking for System.Data.SQLite build configuration... "
set configuration [getBuildConfiguration]
- addConstraint [appendArgs buildConfiguration $configuration]
+ addConstraint [appendArgs buildConfiguration. $configuration]
tputs $::test_channel [appendArgs \" $configuration \"\n]
#
# NOTE: Try to setup an interrupt callback using the script debugger
# that will cancel all SQL queries in progress for all database
ADDED Tests/tkt-393d954be0.eagle
Index: Tests/tkt-393d954be0.eagle
==================================================================
--- /dev/null
+++ Tests/tkt-393d954be0.eagle
@@ -0,0 +1,70 @@
+###############################################################################
+#
+# tkt-393d954be0.eagle --
+#
+# Written by Joe Mistachkin.
+# Released to the public domain, use at your own risk!
+#
+###############################################################################
+
+package require Eagle
+package require Eagle.Library
+package require Eagle.Test
+
+runTestPrologue
+
+###############################################################################
+
+package require System.Data.SQLite.Test
+runSQLiteTestPrologue
+
+###############################################################################
+
+runTest {test tkt-393d954be0-1.1 {custom connection pool} -body {
+ set nullPool [object create -flags +NonPublic -alias \
+ System.Data.SQLite.NullConnectionPool true]
+
+ object invoke System.Data.SQLite.SQLiteConnection ConnectionPool $nullPool
+
+ setupDb [set fileName tkt-393d954be0-1.1.db] "" "" "" "" "Pooling=True;"
+
+ set exists(0) [file exists [file join [getDatabaseDirectory] [file tail \
+ $fileName]]]
+
+ cleanupDb $fileName
+
+ set exists(1) [file exists [file join [getDatabaseDirectory] [file tail \
+ $fileName]]]
+
+ set counts null; set openCount 0; set closeCount 0; set totalCount 0
+ object invoke -flags +NonPublic System.Data.SQLite.SQLiteConnectionPool \
+ GetCounts $fileName counts openCount closeCount totalCount
+
+ object invoke -flags +NonPublic System.Data.SQLite.SQLiteConnectionPool \
+ ClearPool $fileName
+
+ object invoke -flags +NonPublic System.Data.SQLite.SQLiteConnectionPool \
+ ClearAllPools
+
+ list $exists(0) $exists(1) $counts $openCount $closeCount $totalCount \
+ [object invoke $nullPool ToString]
+} -cleanup {
+ cleanupDb $fileName
+
+ unset -nocomplain db fileName exists counts openCount closeCount totalCount \
+ nullPool
+} -constraints {eagle monoBug28 buildConfiguration.Debug command.sql\
+compile.DATA SQLite System.Data.SQLite} -match regexp -result [string map \
+[list \n \r\n] {^True False \{\} 0 0 0\
+\{Remove\(".*?\\tkt-393d954be0-1\.1\.db",\
+100, 0\)
+Add\(".*?\\tkt-393d954be0-1\.1\.db", -?\d+, 0\)
+GetCounts\("tkt-393d954be0-1\.1\.db", , 0, 0, 0\)
+ClearPool\("tkt-393d954be0-1\.1\.db"\)
+ClearAllPools\(\)
+\}$}]}
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -192,10 +192,11 @@
- Updated to SQLite 3.7.16.
- Skip checking loaded assemblies for types tagged with the SQLiteFunction attribute when the No_SQLiteFunctions environment variable is set. Pursuant to [e4c8121f7b].
- Add HexPassword connection string property to work around the inability to include a literal semicolon in a connection string property value. Pursuant to [1c456ae75f].
- Add static Execute method to the SQLiteCommand class.
+ - Support custom connection pool implementations by adding the ISQLiteConnectionPool interface, the static SQLiteConnection.ConnectionPool property, and the static CreateHandle method in addition to modifying the SQLiteConnectionPool class. Pursuant to [393d954be0].
- Add public constructor to the SQLiteDataAdapter class that allows passing the parseViaFramework parameter to the SQLiteConnection constructor.
- When built with the CHECK_STATE compile-time option, skip throwing exceptions from the SQLiteDataReader class when the object is being disposed.
- Support automatic value conversions for columns with a declared type of BIGUINT, INTEGER8, INTEGER16, INTEGER32, INTEGER64, SMALLUINT, TINYSINT, UNSIGNEDINTEGER, UNSIGNEDINTEGER8, UNSIGNEDINTEGER16, UNSIGNEDINTEGER32, UNSIGNEDINTEGER64, INT8, INT16, INT32, INT64, UINT, UINT8, UINT16, UINT32, UINT64, or ULONG.
- Add BindUInt32AsInt64 connection flag to force binding of UInt32 values as Int64 instead. Pursuant to [c010fa6584].
- Remove AUTOINCREMENT from the column type name map. ** Potentially Incompatible Change **
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -8,10 +8,11 @@
- Updated to [http://www.sqlite.org/src/info/trunk|SQLite 3.7.16].
- Skip checking loaded assemblies for types tagged with the SQLiteFunction attribute when the No_SQLiteFunctions environment variable is set. Pursuant to [e4c8121f7b].
- Add HexPassword connection string property to work around the inability to include a literal semicolon in a connection string property value. Pursuant to [1c456ae75f].
- Add static Execute method to the SQLiteCommand class.
+ - Support custom connection pool implementations by adding the ISQLiteConnectionPool interface, the static SQLiteConnection.ConnectionPool property, and the static CreateHandle method in addition to modifying the SQLiteConnectionPool class. Pursuant to [393d954be0].
- Add public constructor to the SQLiteDataAdapter class that allows passing the parseViaFramework parameter to the SQLiteConnection constructor.
- When built with the CHECK_STATE compile-time option, skip throwing exceptions from the SQLiteDataReader class when the object is being disposed.
- Support automatic value conversions for columns with a declared type of BIGUINT, INTEGER8, INTEGER16, INTEGER32, INTEGER64, SMALLUINT, TINYSINT, UNSIGNEDINTEGER, UNSIGNEDINTEGER8, UNSIGNEDINTEGER16, UNSIGNEDINTEGER32, UNSIGNEDINTEGER64, INT8, INT16, INT32, INT64, UINT, UINT8, UINT16, UINT32, UINT64, or ULONG.
- Add BindUInt32AsInt64 connection flag to force binding of UInt32 values as Int64 instead. Pursuant to [c010fa6584].
- Remove AUTOINCREMENT from the column type name map. ** Potentially Incompatible Change **