Skip to content

Commit 1b24a10

Browse files
committed
Call interrupt before close in disconnect to fix deadlock
When DBConnection times out a long-running query, it calls disconnect/2 on the connection process. Previously, disconnect only called Sqlite3.close(), which blocks on conn->mutex held by the still-running dirty NIF — deadlocking the pool permanently. Now disconnect calls Sqlite3.interrupt(db) first, which sets a flag checked at every VDBE loop iteration (OP_Next, OP_Prev, etc.). The running query aborts with SQLITE_INTERRUPT, releases the mutex, and close() proceeds. This is safe because PR #342 (v0.35.0) added interrupt_mutex to the NIF layer, which prevents a use-after-free race between interrupt() and close(). The interrupt_mutex ensures interrupt reads the db pointer atomically even if close() is freeing it concurrently. Test results before this change: 149 tests, 20 pass, 2 fail (pool recovery test deadlocks at 30s timeout) Test results after this change: 160 tests, 2 failures - 21 of 22 cancellation tests now pass (busy handler test still fails as expected) - The integration test "exceeding timeout" fails with a pattern match error (expects {:ok, _, _} but gets {:error, "interrupted"} — will be fixed later) The passing cancellation tests demonstrate that interrupt successfully unblocks queries stuck in VDBE execution, fixing the primary deadlock issue. Known limitation: does not fix the case where a query is stuck in SQLite's busy handler sleep loop (waiting for a lock held by another connection). That requires a custom busy handler (separate change). Fixes the primary deadlock described in #192.
1 parent 508419e commit 1b24a10

1 file changed

Lines changed: 5 additions & 0 deletions

File tree

lib/exqlite/connection.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ defmodule Exqlite.Connection do
212212
apply(state.before_disconnect, [err, state])
213213
end
214214

215+
# Interrupt any in-flight query so close() doesn't block on conn->mutex.
216+
# Without this, a long-running dirty NIF holds the mutex and close() deadlocks.
217+
# See: https://github.com/elixir-sqlite/exqlite/issues/192
218+
Sqlite3.interrupt(db)
219+
215220
case Sqlite3.close(db) do
216221
:ok -> :ok
217222
{:error, reason} -> {:error, %Error{message: to_string(reason)}}

0 commit comments

Comments
 (0)