17
17
//! That API is provided by [`Python::allow_threads`] and enforced via the [`Send`] bound on the
18
18
//! closure and the return type.
19
19
//!
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.
21
23
//!
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.
27
27
//!
28
28
//! ```rust, compile_fail
29
29
//! use pyo3::prelude::*;
33
33
//! let rc = Rc::new(5);
34
34
//!
35
35
//! py.allow_threads(|| {
36
- //! // This would actually be fine...
36
+ //! // This could be fine...
37
37
//! println!("{:?}", *rc);
38
38
//! });
39
39
//! });
40
40
//! ```
41
41
//!
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
+ //!
42
92
//! [`Rc`]: std::rc::Rc
43
93
//! [`Py`]: crate::Py
44
94
use crate :: err:: { self , PyDowncastError , PyErr , PyResult } ;
@@ -232,17 +282,19 @@ impl<'py> Python<'py> {
232
282
/// Temporarily releases the GIL, thus allowing other Python threads to run. The GIL will be
233
283
/// reacquired when `F`'s scope ends.
234
284
///
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.
238
287
///
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.
241
291
///
242
292
/// If you need to pass Python objects into the closure you can use [`Py`]`<T>`to create a
243
293
/// reference independent of the GIL lifetime. However, you cannot do much with those without a
244
294
/// [`Python`] token, for which you'd need to reacquire the GIL.
245
295
///
296
+ /// See the [module level documentation](self) for more information.
297
+ ///
246
298
/// # Example: Releasing the GIL while running a computation in Rust-only code
247
299
///
248
300
/// ```
@@ -409,7 +461,7 @@ impl<'py> Python<'py> {
409
461
410
462
/// An unsafe version of [`allow_threads`][Self::allow_threads]
411
463
///
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,
413
465
/// therefore it is more efficient and has access to thread-local storage
414
466
/// established at the call site.
415
467
///
@@ -436,6 +488,8 @@ impl<'py> Python<'py> {
436
488
/// });
437
489
/// ```
438
490
///
491
+ /// See the [module level documentation](self) for more information.
492
+ ///
439
493
/// # Safety
440
494
///
441
495
/// The caller must ensure that no code within the closure accesses GIL-protected data
0 commit comments