1717//! That API is provided by [`Python::allow_threads`] and enforced via the [`Send`] bound on the
1818//! closure and the return type.
1919//!
20- //! In practice this API works quite well, but it comes with some drawbacks:
20+ //! In practice this API works quite well, but it comes with a big drawback:
21+ //! There is no instrinsic reason to prevent `!Send` types like [`Rc`] from crossing the closure.
22+ //! After all, we release the GIL to let other Python threads run, not necessarily to launch new threads.
2123//!
22- //! ## Drawbacks
23- //!
24- //! There is no reason to prevent `!Send` types like [`Rc`] from crossing the closure. After all,
25- //! [`Python::allow_threads`] just lets other Python threads run - it does not itself launch a new
26- //! thread.
24+ //! But to isolate the closure from references bound to the current thread holding the GIL
25+ //! and to close soundness holes implied by thread-local storage hiding such references,
26+ //! we do need to run the closure on a dedicated runtime thread.
2727//!
2828//! ```rust, compile_fail
2929//! use pyo3::prelude::*;
3333//! let rc = Rc::new(5);
3434//!
3535//! py.allow_threads(|| {
36- //! // This would actually be fine...
36+ //! // This could be fine...
3737//! println!("{:?}", *rc);
3838//! });
3939//! });
4040//! ```
4141//!
42+ //! However, running the closure on a distinct thread is required as otherwise
43+ //! thread-local storage could be used to "smuggle" GIL-bound data into it
44+ //! independently of any trait bounds (whether using `Send` or an auto trait
45+ //! dedicated to handling GIL-bound data):
46+ //!
47+ //! ```rust, no_run
48+ //! use pyo3::prelude::*;
49+ //! use pyo3::types::PyString;
50+ //! use scoped_tls::scoped_thread_local;
51+ //!
52+ //! scoped_thread_local!(static WRAPPED: PyString);
53+ //!
54+ //! fn callback() {
55+ //! WRAPPED.with(|smuggled: &PyString| {
56+ //! println!("{:?}", smuggled);
57+ //! });
58+ //! }
59+ //!
60+ //! Python::with_gil(|py| {
61+ //! let string = PyString::new(py, "foo");
62+ //!
63+ //! WRAPPED.set(string, || {
64+ //! py.allow_threads(callback);
65+ //! });
66+ //! });
67+ //! ```
68+ //!
69+ //! PyO3 tries to minimize the overhead of using dedicated threads by re-using them,
70+ //! i.e. after a thread is spawned to execute a closure with the GIL temporarily released,
71+ //! it is kept around for up to one minute to potentially service subsequent invocations of `allow_threads`.
72+ //!
73+ //! Note that PyO3 will however not wait to re-use an existing that is currently blocked by other work,
74+ //! i.e. to keep latency to a minimum a new thread will be started to immediately run the given closure.
75+ //!
76+ //! These long-lived background threads are named `pyo3 allow_threads runtime thread`
77+ //! to facilitate diagnosing any performance issues they might cause on the process level.
78+ //!
79+ //! One important consequence of this approach is that the state of thread-local storage (TLS)
80+ //! is essentially undefined: The thread might be newly spawn so that TLS needs to be newly initialized,
81+ //! but it might also be re-used so that TLS contains values created by previous calls to `allow_threads`.
82+ //!
83+ //! If the performance overhead of shunting the closure to another is too high
84+ //! or code requires access to thread-local storage established by the calling thread,
85+ //! there is the unsafe escape hatch [`Python::unsafe_allow_threads`]
86+ //! which executes the closure directly after suspending the GIL.
87+ //!
88+ //! However, note establishing the required invariants to soundly call this function
89+ //! requires highly non-local reasoning as thread-local storage allows "smuggling" GIL-bound references
90+ //! using what is essentially global state.
91+ //!
4292//! [`Rc`]: std::rc::Rc
4393//! [`Py`]: crate::Py
4494use crate :: err:: { self , PyDowncastError , PyErr , PyResult } ;
@@ -232,17 +282,19 @@ impl<'py> Python<'py> {
232282 /// Temporarily releases the GIL, thus allowing other Python threads to run. The GIL will be
233283 /// reacquired when `F`'s scope ends.
234284 ///
235- /// If you don't need to touch the Python
236- /// interpreter for some time and have other Python threads around, this will let you run
237- /// Rust-only code while letting those other Python threads make progress.
285+ /// If you don't need to touch the Python interpreter for some time and have other Python threads around,
286+ /// this will let you run Rust-only code while letting those other Python threads make progress.
238287 ///
239- /// Only types that implement [`Send`] can cross the closure. See the
240- /// [module level documentation](self) for more information.
288+ /// Only types that implement [`Send`] can cross the closure
289+ /// because *it is executed on a dedicated runtime thread*
290+ /// to prevent access to GIL-bound references based on thread identity.
241291 ///
242292 /// If you need to pass Python objects into the closure you can use [`Py`]`<T>`to create a
243293 /// reference independent of the GIL lifetime. However, you cannot do much with those without a
244294 /// [`Python`] token, for which you'd need to reacquire the GIL.
245295 ///
296+ /// See the [module level documentation](self) for more information.
297+ ///
246298 /// # Example: Releasing the GIL while running a computation in Rust-only code
247299 ///
248300 /// ```
@@ -409,7 +461,7 @@ impl<'py> Python<'py> {
409461
410462 /// An unsafe version of [`allow_threads`][Self::allow_threads]
411463 ///
412- /// This version does not run the given closure on a dedicated runtime thread,
464+ /// This version does _not_ run the given closure on a dedicated runtime thread,
413465 /// therefore it is more efficient and has access to thread-local storage
414466 /// established at the call site.
415467 ///
@@ -436,6 +488,8 @@ impl<'py> Python<'py> {
436488 /// });
437489 /// ```
438490 ///
491+ /// See the [module level documentation](self) for more information.
492+ ///
439493 /// # Safety
440494 ///
441495 /// The caller must ensure that no code within the closure accesses GIL-protected data
0 commit comments