SQLite: do not clear the rollback hook through a handle that is closing - #38880
Open
HuzaifaChaudary wants to merge 1 commit into
Open
SQLite: do not clear the rollback hook through a handle that is closing#38880HuzaifaChaudary wants to merge 1 commit into
HuzaifaChaudary wants to merge 1 commit into
Conversation
sqlite calls the rollback hook while it rolls back, and that includes the rollback it does for you inside sqlite3_close_v2. by that point the handle is already inside ReleaseHandle, so RollbackExternal asking it to clear the hook hits DangerousAddRef on a closed handle and throws the throw comes out of a native callback so there is nowhere for it to go. when the close happens on the pool prune timer it is an unhandled exception on a thread pool thread and the process dies. that is the crash in the report the hook is being torn down with the connection anyway so skipping it when the handle is already closed loses nothing. did the same in RollbackInternal since the second stack in the report goes through there before this the test suite aborts with exit 134 partway through. after it runs to the end
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #38854
the crash
PruneCallbackdisposes a pooled connection, and the reporter's stack ends like this:reading it from the bottom, the pool disposes the connection,
sqlite3_close_v2rolls back the transaction that was still open, and that fires the rollback hook.RollbackExternalthen asks the same handle to clear the hook:the handle is inside
ReleaseHandleat that moment, soDangerousAddRefthrowsObjectDisposedException. it comes out of a native callback, so there is nothing to catch it, and on the prune timer that means an unhandled exception on a thread pool thread. the process is gone.#38574 guarded
SqliteConnectionInternal.Deactivatefor the same underlying reason, but as the reporter says, that is not the frame that kills the process.the fix
skip clearing the hook when the handle is already closed. it is being torn down with the connection anyway, so there is nothing to unregister:
that is the same shape as the guard #38574 put in
Deactivate, so the two read alike.i put the same guard in
RollbackInternal, because the second stack in the report goes through there:that one is caught by EF and only logged, so it is not fatal, but it is the same call into a dead handle. when the handle is gone there is no transaction left to roll back either, so the
ROLLBACK;is skipped with it. happy to drop that half if you would rather keep this to the fatal path only, it is the one part of the change i could not write a test for.reproducing it
the reporter's snippet, as a test:
this does not fail on
main, it takes the test host down with it:which is why the before column below is short. the run does not finish.
verification
Microsoft.Data.Sqlite.sqlite3.Testserror: 1Microsoft.Data.Sqlite.sqlite3mc.TestsEFCore.Sqlite.Testsone thing i did not change
SqliteConnectionPool.PruneCallbackandSqliteConnectionFactory.PruneCallbackare bothTimercallbacks with notry. anything that throws inside either one still ends the process, so this fix removes the cause that was reported rather than the class of failure. i did not add a blanket catch because swallowing errors on a background timer is a call for you to make, not something to slip into a bug fix. tell me if you want it and i will add it here.disclaimer: this contribution was prepared with the assistance of an ai agent. i reproduced the crash from the report first, confirmed the exact frame, and ran the three suites above locally before opening this.