Index: System.Data.SQLite/SQLite3.cs
==================================================================
--- System.Data.SQLite/SQLite3.cs
+++ System.Data.SQLite/SQLite3.cs
@@ -65,10 +65,11 @@
/// The opaque pointer returned to us by the sqlite provider
///
protected internal SQLiteConnectionHandle _sql;
protected string _fileName;
protected SQLiteConnectionFlags _flags;
+ private bool _setLogCallback;
protected bool _usePool;
protected int _poolVersion;
private int _cancelCount;
#if (NET_35 || NET_40 || NET_45 || NET_451 || NET_452 || NET_46 || NET_461 || NET_462 || NET_47) && !PLATFORM_COMPACTFRAMEWORK
@@ -190,11 +191,11 @@
#if INTEROP_VIRTUAL_TABLE
DisposeModules();
#endif
- Close(false); /* Disposing, cannot throw. */
+ Close(true); /* Disposing, cannot throw. */
}
}
finally
{
base.Dispose(disposing);
@@ -243,11 +244,11 @@
// It isn't necessary to cleanup any functions we've registered. If the connection
// goes to the pool and is resurrected later, re-registered functions will overwrite the
// previous functions. The SQLiteFunctionCookieHandle will take care of freeing unmanaged
// resources belonging to the previously-registered functions.
- internal override void Close(bool canThrow)
+ internal override void Close(bool disposing)
{
if (_sql != null)
{
if (!_sql.OwnHandle)
{
@@ -255,14 +256,17 @@
return;
}
bool unbindFunctions = ((_flags & SQLiteConnectionFlags.UnbindFunctionsOnClose)
== SQLiteConnectionFlags.UnbindFunctionsOnClose);
+
+ retry:
if (_usePool)
{
- if (SQLiteBase.ResetConnection(_sql, _sql, canThrow))
+ if (SQLiteBase.ResetConnection(_sql, _sql, !disposing) &&
+ UnhookNativeCallbacks(true, !disposing))
{
if (unbindFunctions)
{
if (SQLiteFunction.UnbindAllFunctions(this, _flags, false))
{
@@ -291,31 +295,42 @@
SQLiteConnectionPool.Add(_fileName, _sql, _poolVersion);
SQLiteConnection.OnChanged(null, new ConnectionEventArgs(
SQLiteConnectionEventType.ClosedToPool, null, null,
null, null, _sql, _fileName, new object[] {
- typeof(SQLite3), canThrow, _fileName, _poolVersion }));
+ typeof(SQLite3), !disposing, _fileName, _poolVersion }));
#if !NET_COMPACT_20 && TRACE_CONNECTION
Trace.WriteLine(HelperMethods.StringFormat(
CultureInfo.CurrentCulture,
"Close (Pool) Success: {0}",
HandleToString()));
#endif
}
-#if !NET_COMPACT_20 && TRACE_CONNECTION
else
{
+#if !NET_COMPACT_20 && TRACE_CONNECTION
Trace.WriteLine(HelperMethods.StringFormat(
CultureInfo.CurrentCulture,
"Close (Pool) Failure: {0}",
HandleToString()));
+#endif
+
+ //
+ // NOTE: This connection cannot be added to the pool;
+ // therefore, just use the normal disposal
+ // procedure on it.
+ //
+ _usePool = false;
+ goto retry;
}
-#endif
}
else
{
+ /* IGNORED */
+ UnhookNativeCallbacks(disposing, !disposing);
+
if (unbindFunctions)
{
if (SQLiteFunction.UnbindAllFunctions(this, _flags, false))
{
#if !NET_COMPACT_20 && TRACE_CONNECTION
@@ -945,11 +960,11 @@
// NOTE: If the database connection is currently open, attempt to
// close it now. This must be done because the file name or
// other parameters that may impact the underlying database
// connection may have changed.
//
- if (_sql != null) Close(true);
+ if (_sql != null) Close(false);
//
// NOTE: If the connection was not closed successfully, throw an
// exception now.
//
@@ -2942,12 +2957,342 @@
internal override SQLiteErrorCode SetLogCallback(SQLiteLogCallback func)
{
SQLiteErrorCode rc = UnsafeNativeMethods.sqlite3_config_log(
SQLiteConfigOpsEnum.SQLITE_CONFIG_LOG, func, IntPtr.Zero);
+ if (rc == SQLiteErrorCode.Ok)
+ _setLogCallback = (func != null);
+
return rc;
}
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Appends an error message and an appropriate line-ending to a
+ /// instance. This is useful because the .NET Compact Framework has a slightly different set
+ /// of supported methods for the class.
+ ///
+ ///
+ /// The instance to append to.
+ ///
+ ///
+ /// The message to append. It will be followed by an appropriate line-ending.
+ ///
+ private static void AppendError(
+ StringBuilder builder,
+ string message
+ )
+ {
+ if (builder == null)
+ return;
+
+#if !PLATFORM_COMPACTFRAMEWORK
+ builder.AppendLine(message);
+#else
+ builder.Append(message);
+ builder.Append("\r\n");
+#endif
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This method attempts to cause the SQLite native library to invalidate
+ /// its function pointers that refer to this instance. This is necessary
+ /// to prevent calls from native code into delegates that may have been
+ /// garbage collected. Normally, these types of issues can only arise for
+ /// connections that are added to the pool; howver, it is good practice to
+ /// unconditionally invalidate function pointers that may refer to objects
+ /// being disposed.
+ ///
+ /// Non-zero to also invalidate global function pointers (i.e. those that
+ /// are not directly associated with this connection on the native side).
+ ///
+ ///
+ /// Non-zero if this method is being executed within a context where it can
+ /// throw an exception in the event of failure; otherwise, zero.
+ ///
+ ///
+ ///
+ /// Non-zero if this method was successful; otherwise, zero.
+ ///
+ private bool UnhookNativeCallbacks(
+ bool includeGlobal,
+ bool canThrow
+ )
+ {
+ //
+ // NOTE: Initially, this method assumes success. Then, if any attempt
+ // to invalidate a function pointer fails, the overall result is
+ // set to failure. However, this will not prevent further
+ // attempts, if any, to invalidate subsequent function pointers.
+ //
+ bool result = true;
+ SQLiteErrorCode rc = SQLiteErrorCode.Ok;
+ StringBuilder builder = new StringBuilder();
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Rollback Hook (Per-Connection)
+ try
+ {
+ SetRollbackHook(null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset rollback hook: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset rollback hook");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Trace Callback (Per-Connection)
+ try
+ {
+ SetTraceCallback(null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset trace callback: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset trace callback");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Commit Hook (Per-Connection)
+ try
+ {
+ SetCommitHook(null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset commit hook: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset commit hook");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Update Hook (Per-Connection)
+ try
+ {
+ SetUpdateHook(null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset update hook: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset update hook");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Authorizer Hook (Per-Connection)
+ try
+ {
+ SetAuthorizerHook(null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset authorizer hook: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset authorizer hook");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Progress Hook (Per-Connection)
+ try
+ {
+ SetProgressHook(0, null); /* throw */
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset progress hook: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset progress hook");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Log Callback (Global)
+ //
+ // NOTE: We have to be careful here because the log callback
+ // is not per-connection on the native side. It should
+ // only be unset by this method if this instance was
+ // responsible for setting it.
+ //
+ if (includeGlobal && _setLogCallback)
+ {
+ try
+ {
+ SQLiteErrorCode rc2 = SetLogCallback(null); /* throw */
+
+ if (rc2 != SQLiteErrorCode.Ok)
+ {
+ rc = rc2;
+ result = false;
+ }
+ }
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ catch (Exception e)
+#else
+ catch (Exception)
+#endif
+ {
+#if !NET_COMPACT_20 && TRACE_CONNECTION
+ try
+ {
+ Trace.WriteLine(HelperMethods.StringFormat(
+ CultureInfo.CurrentCulture,
+ "Failed to unset log callback: {0}",
+ e)); /* throw */
+ }
+ catch
+ {
+ // do nothing.
+ }
+#endif
+
+ AppendError(builder, "failed to unset log callback");
+ rc = SQLiteErrorCode.Error;
+
+ result = false;
+ }
+ }
+ #endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////
+
+ if (!result && canThrow)
+ throw new SQLiteException(rc, builder.ToString());
+
+ return result;
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Creates a new SQLite backup object based on the provided destination
Index: System.Data.SQLite/SQLite3_UTF16.cs
==================================================================
--- System.Data.SQLite/SQLite3_UTF16.cs
+++ System.Data.SQLite/SQLite3_UTF16.cs
@@ -136,11 +136,11 @@
// NOTE: If the database connection is currently open, attempt to
// close it now. This must be done because the file name or
// other parameters that may impact the underlying database
// connection may have changed.
//
- if (_sql != null) Close(true);
+ if (_sql != null) Close(false);
//
// NOTE: If the connection was not closed successfully, throw an
// exception now.
//
Index: System.Data.SQLite/SQLiteBase.cs
==================================================================
--- System.Data.SQLite/SQLiteBase.cs
+++ System.Data.SQLite/SQLiteBase.cs
@@ -127,12 +127,12 @@
///
///
/// After the database has been closed implemeters should call SQLiteFunction.UnbindFunctions() to deallocate all interop allocated
/// memory associated with the user-defined functions and collating sequences tied to the closed connection.
///
- /// Non-zero if the operation is allowed to throw exceptions, zero otherwise.
- internal abstract void Close(bool canThrow);
+ /// Non-zero if connection is being disposed, zero otherwise.
+ internal abstract void Close(bool disposing);
///
/// Sets the busy timeout on the connection. SQLiteCommand will call this before executing any command.
///
/// The number of milliseconds to wait before returning SQLITE_BUSY
internal abstract void SetTimeout(int nTimeoutMS);
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -2919,11 +2919,11 @@
_enlistment = null;
}
#endif
if (_sql != null)
{
- _sql.Close(!_disposing);
+ _sql.Close(_disposing);
_sql = null;
}
_transactionLevel = 0;
_transactionSequence = 0;
}
@@ -4845,11 +4845,11 @@
CheckDisposed();
if (_sql == null)
throw new InvalidOperationException("Database connection not valid for shutdown.");
- _sql.Close(true); /* NOTE: MUST be closed before shutdown. */
+ _sql.Close(false); /* NOTE: MUST be closed before shutdown. */
SQLiteErrorCode rc = _sql.Shutdown();
#if !NET_COMPACT_20 && TRACE_CONNECTION
if (rc != SQLiteErrorCode.Ok)
System.Diagnostics.Trace.WriteLine(HelperMethods.StringFormat(