|
5 | 5 | //!
|
6 | 6 | //! [PEP 703]: https://peps.python.org/pep-703/
|
7 | 7 | use crate::{
|
| 8 | + ffi, |
| 9 | + sealed::Sealed, |
8 | 10 | types::{any::PyAnyMethods, PyAny, PyString},
|
9 | 11 | Bound, Py, PyResult, PyTypeCheck, Python,
|
10 | 12 | };
|
11 |
| -use std::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit, sync::Once}; |
| 13 | +use std::{ |
| 14 | + cell::UnsafeCell, |
| 15 | + marker::PhantomData, |
| 16 | + mem::MaybeUninit, |
| 17 | + sync::{Once, OnceState}, |
| 18 | +}; |
12 | 19 |
|
13 | 20 | #[cfg(not(Py_GIL_DISABLED))]
|
14 | 21 | use crate::PyVisit;
|
@@ -473,6 +480,58 @@ where
|
473 | 480 | }
|
474 | 481 | }
|
475 | 482 |
|
| 483 | +/// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a |
| 484 | +/// Python thread. |
| 485 | +pub trait OnceExt: Sealed { |
| 486 | + /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily |
| 487 | + /// if blocking on another thread currently calling this `Once`. |
| 488 | + fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()); |
| 489 | + |
| 490 | + /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL |
| 491 | + /// temporarily if blocking on another thread currently calling this `Once`. |
| 492 | + fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)); |
| 493 | +} |
| 494 | + |
| 495 | +impl OnceExt for Once { |
| 496 | + fn call_once_py_attached(&self, _py: Python<'_>, f: impl FnOnce()) { |
| 497 | + if self.is_completed() { |
| 498 | + return; |
| 499 | + } |
| 500 | + |
| 501 | + // Safety: we are currently attached to the GIL, and we expect to block. We will save |
| 502 | + // the current thread state and restore it as soon as we are done blocking. |
| 503 | + let mut ts = Some(unsafe { ffi::PyEval_SaveThread() }); |
| 504 | + |
| 505 | + self.call_once(|| { |
| 506 | + unsafe { ffi::PyEval_RestoreThread(ts.take().unwrap()) }; |
| 507 | + f(); |
| 508 | + }); |
| 509 | + if let Some(ts) = ts { |
| 510 | + // Some other thread filled this Once, so we need to restore the GIL state. |
| 511 | + unsafe { ffi::PyEval_RestoreThread(ts) }; |
| 512 | + } |
| 513 | + } |
| 514 | + |
| 515 | + fn call_once_force_py_attached(&self, _py: Python<'_>, f: impl FnOnce(&OnceState)) { |
| 516 | + if self.is_completed() { |
| 517 | + return; |
| 518 | + } |
| 519 | + |
| 520 | + // Safety: we are currently attached to the GIL, and we expect to block. We will save |
| 521 | + // the current thread state and restore it as soon as we are done blocking. |
| 522 | + let mut ts = Some(unsafe { ffi::PyEval_SaveThread() }); |
| 523 | + |
| 524 | + self.call_once_force(|state| { |
| 525 | + unsafe { ffi::PyEval_RestoreThread(ts.take().unwrap()) }; |
| 526 | + f(state); |
| 527 | + }); |
| 528 | + if let Some(ts) = ts { |
| 529 | + // Some other thread filled this Once, so we need to restore the GIL state. |
| 530 | + unsafe { ffi::PyEval_RestoreThread(ts) }; |
| 531 | + } |
| 532 | + } |
| 533 | +} |
| 534 | + |
476 | 535 | #[cfg(test)]
|
477 | 536 | mod tests {
|
478 | 537 | use super::*;
|
|
0 commit comments