Skip to content

fix: acquire GIL before AppState write lock to prevent deadlock - #227

Merged
ZhuchkaTriplesix merged 1 commit into
devfrom
issue-205-fix-gil-lock-deadlock
Sep 29, 2026
Merged

ZhuchkaTriplesix merged 1 commit into
devfrom
issue-205-fix-gil-lock-deadlock

Conversation

@ZhuchkaTriplesix

Copy link
Copy Markdown
Member

Problem

ensure_compiled_snapshot (dispatch.rs), setup_database, and close_database (lib.rs) took the AppState write lock and then called rebuild_snapshot(), which acquires the GIL internally to clone Py<PyAny> handles. These run on tokio worker threads without the GIL already held (async run_rsgi path, or a spawned future), so the lock order there was Rust lock → GIL.

Elsewhere (e.g. handle_rsgi), the order is GIL → Rust lock (GIL held for the whole pymethod call, then state.read()/state.write()).

Two different lock orders on the same pair of locks is a textbook AB-BA deadlock: one thread can hold the Rust write lock while blocked acquiring the GIL, while another thread holds the GIL and is blocked acquiring the Rust lock. Under real concurrency (e.g. no explicit freeze() + any middleware, which skips the sync short-circuit) this hangs the worker.

Fix

Acquire the GIL first in all three call sites, then take the write lock inside that scope — matching the GIL-then-lock order used everywhere else in the hot path. Python::with_gil is reentrant, so this is a no-op cost when GIL is already held (e.g. pymethods).

Testing

cargo build --lib passes. No behavior change to the lock-free read path.

Closes #205

…lock

ensure_compiled_snapshot, setup_database, and close_database took the
AppState write lock and then called rebuild_snapshot(), which acquires
the GIL to clone Py<PyAny> handles. When those ran on a tokio worker
thread without the GIL already held, this created an AB-BA lock-order
inversion against handle_rsgi (GIL held, then state.read()/write()):
one thread could hold the Rust lock waiting for the GIL while another
held the GIL waiting for the Rust lock, deadlocking the worker.

Acquire the GIL first in all three call sites, then take the write
lock inside that scope, matching the GIL-then-lock order used
everywhere else in the hot path.

Closes #205
@ZhuchkaTriplesix
ZhuchkaTriplesix merged commit 5d483e9 into dev Sep 29, 2026
17 checks passed

@ZhuchkaTriplesix ZhuchkaTriplesix left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

123

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant