From 8f048efd695fcef58d0685a308ba0f5d4c860c73 Mon Sep 17 00:00:00 2001 From: funkpower Date: Mon, 21 Sep 2026 10:57:38 -0400 Subject: [PATCH] bindings: let go of the interpreter while reading a book read_colophon is several milliseconds of computation over a few hundred kilobytes, and it held the GIL for all of them. A caller reading a library through asyncio.to_thread therefore got no overlap whatsoever: six books in flight, one book's worth of work at a time. Measured on this library, that was most of why a concurrent pass still paced at four and a half hours. --- crates/bindings/src/python.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/bindings/src/python.rs b/crates/bindings/src/python.rs index 7cc6ecf..7a1cce9 100644 --- a/crates/bindings/src/python.rs +++ b/crates/bindings/src/python.rs @@ -451,15 +451,22 @@ fn _catena(module: &Bound<'_, PyModule>) -> PyResult<()> { #[pyo3(signature = (text, pages = colophon::PAGES, shelf = None))] fn read_colophon<'py>( py: Python<'py>, - text: &str, + text: String, pages: usize, - shelf: Option<&str>, + shelf: Option, ) -> PyResult> { if pages == 0 { return Err(PyValueError::new_err("pages = 0 would read nothing")); } - let shelf = shelf.map(colophon::shelf::Shelf::parse); - to_py(py, &colophon::read_onto(text, pages, shelf.as_ref())) + // Reading a book's front matter is several milliseconds of pure + // computation on a few hundred kilobytes, and holding the interpreter for + // it makes `asyncio.to_thread` a lie: a caller reading a library several + // books at a time got no overlap at all until this let go. + let printed = py.detach(move || { + let shelf = shelf.as_deref().map(colophon::shelf::Shelf::parse); + colophon::read_onto(&text, pages, shelf.as_ref()) + }); + to_py(py, &printed) } /// File subject headings onto a shelf without reading a book.