diff --git a/src/dispatch.rs b/src/dispatch.rs index f5915e3..9176787 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -314,12 +314,17 @@ fn ensure_compiled_snapshot(state: &Arc>) -> Arc` handles, and taking a Rust lock first would let another thread hold the + // GIL while blocked on this same lock — classic AB-BA deadlock (issue #205). + Python::with_gil(|_py| { + let mut st = state.write(); + if st.compiled.is_none() { + st.compiled = Some(Arc::new(st.snapshot_routers())); + st.rebuild_snapshot(); + } + Arc::clone(st.compiled.as_ref().expect("just populated")) + }) } /// Synchronous RSGI handling for **openapi**, **404**, **405**, and **trivial matched routes**: diff --git a/src/lib.rs b/src/lib.rs index e195c34..587532e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -748,9 +748,14 @@ impl App { .map_err(|e| { pyo3::exceptions::PyRuntimeError::new_err(format!("DB connect error: {}", e)) })?; - let mut st = state.write(); - st.db_pool = Some(pool); - st.rebuild_snapshot(); + // GIL before the write lock (see the comment on `ensure_compiled_snapshot` in + // dispatch.rs / issue #205): this future runs on a tokio worker thread without + // the GIL, and `rebuild_snapshot` needs it to clone `Py` handles. + Python::with_gil(|_py| { + let mut st = state.write(); + st.db_pool = Some(pool); + st.rebuild_snapshot(); + }); Ok(()) }) } @@ -759,12 +764,12 @@ impl App { fn close_database<'py>(&self, py: Python<'py>) -> PyResult> { let state = self.state.clone(); pyo3_async_runtimes::tokio::future_into_py(py, async move { - let pool = { + let pool = Python::with_gil(|_py| { let mut st = state.write(); let p = st.db_pool.take(); st.rebuild_snapshot(); p - }; + }); if let Some(p) = pool { p.close().await; }