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(