Add GIL-releasing block_on helpers on the shared tokio runtime

Add the `block_on`/`block_on_result`/`block_on_next` helpers the Postgres
backend uses to drive its async `tokio-postgres` futures to completion from
sync, GIL-holding Python methods, releasing the GIL for the wait. They take
a `tokio::runtime::Handle` and block on it from the calling (Python) thread.

Rather than give the DB backend a runtime of its own, they use the
extension's existing shared runtime (`tokio_runtime::PyTokioRuntime`, stored
on the reactor). `start` is made idempotent and a `runtime_handle` accessor
starts it on demand, so a caller that needs a connection before the reactor
is running still gets a handle; once the reactor runs, its
`callWhenRunning(start)` hook is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPEeXx2fAG67o6u4CnmC8W
This commit is contained in:
Erik Johnston
2026-07-23 14:35:04 +00:00
co-authored by Claude Opus 4.8
parent abe8cb1ff2
commit f7e12aef2d
3 changed files with 304 additions and 20 deletions
+10 -3
View File
@@ -7,14 +7,16 @@
//! The driver itself is async; the eventual `Connection` / `Cursor` types will
//! drive it from sync Python methods via a shared multi-thread tokio runtime.
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use pyo3::types::PyModule;
// `pub` (rather than private) so the value-mapping types are reachable from the
// crate root as public API while nothing inside the crate consumes them yet.
// This is what stops clippy's `dead_code` lint from firing on them before the
// `pub` (rather than private) so the not-yet-consumed public items in these
// submodules are reachable from the crate root as public API. This is what
// stops clippy's `dead_code` lint from firing on them before the
// cursor/connection code (added in later changes) wires them up; the visibility
// is tightened back to private once that happens.
pub mod helpers;
pub mod value;
/// Register the `postgres` submodule under the parent `database` module.
@@ -35,3 +37,8 @@ pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()>
Ok(())
}
/// Map a [`tokio_postgres`] error into a Python `RuntimeError`.
fn pg_err_to_py(e: tokio_postgres::Error) -> PyErr {
PyRuntimeError::new_err(format!("postgres error: {e}"))
}