use crate::{
internal::state::SuspendAttach,
sealed::Sealed,
types::{PyAny, PyString},
Bound, Py, Python,
};
use std::{
cell::UnsafeCell,
marker::PhantomData,
mem::MaybeUninit,
sync::{Once, OnceState},
};
pub mod critical_section;
pub(crate) mod once_lock;
#[deprecated(
since = "0.28.0",
note = "use pyo3::sync::critical_section::with_critical_section instead"
)]
pub fn with_critical_section<F, R>(object: &Bound<'_, PyAny>, f: F) -> R
where
F: FnOnce() -> R,
{
crate::sync::critical_section::with_critical_section(object, f)
}
#[deprecated(
since = "0.28.0",
note = "use pyo3::sync::critical_section::with_critical_section2 instead"
)]
pub fn with_critical_section2<F, R>(a: &Bound<'_, PyAny>, b: &Bound<'_, PyAny>, f: F) -> R
where
F: FnOnce() -> R,
{
crate::sync::critical_section::with_critical_section2(a, b, f)
}
pub use self::once_lock::PyOnceLock;
#[deprecated(
since = "0.26.0",
note = "Now internal only, to be removed after https://github.com/PyO3/pyo3/pull/5341"
)]
pub(crate) struct GILOnceCell<T> {
once: Once,
data: UnsafeCell<MaybeUninit<T>>,
_marker: PhantomData<T>,
}
#[allow(deprecated)]
impl<T> Default for GILOnceCell<T> {
fn default() -> Self {
Self::new()
}
}
#[allow(deprecated)]
unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
#[allow(deprecated)]
unsafe impl<T: Send> Send for GILOnceCell<T> {}
#[allow(deprecated)]
impl<T> GILOnceCell<T> {
pub const fn new() -> Self {
Self {
once: Once::new(),
data: UnsafeCell::new(MaybeUninit::uninit()),
_marker: PhantomData,
}
}
#[inline]
pub fn get(&self, _py: Python<'_>) -> Option<&T> {
if self.once.is_completed() {
Some(unsafe { (*self.data.get()).assume_init_ref() })
} else {
None
}
}
#[inline]
pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
if let Some(value) = self.get(py) {
return Ok(value);
}
self.init(py, f)
}
#[cold]
fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
where
F: FnOnce() -> Result<T, E>,
{
let value = f()?;
let _ = self.set(py, value);
Ok(self.get(py).unwrap())
}
pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
let mut value = Some(value);
self.once.call_once_force(|_| {
unsafe {
(*self.data.get()).write(value.take().unwrap());
}
});
match value {
Some(value) => Err(value),
None => Ok(()),
}
}
}
#[allow(deprecated)]
impl<T> Drop for GILOnceCell<T> {
fn drop(&mut self) {
if self.once.is_completed() {
unsafe { MaybeUninit::assume_init_drop(self.data.get_mut()) }
}
}
}
#[macro_export]
macro_rules! intern {
($py: expr, $text: expr) => {{
static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text);
INTERNED.get($py)
}};
}
#[doc(hidden)]
pub struct Interned(&'static str, PyOnceLock<Py<PyString>>);
impl Interned {
pub const fn new(value: &'static str) -> Self {
Interned(value, PyOnceLock::new())
}
#[inline]
pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> {
self.1
.get_or_init(py, || PyString::intern(py, self.0).into())
.bind(py)
}
}
pub trait OnceExt: Sealed {
type OnceState;
fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce());
fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&Self::OnceState));
}
pub trait OnceLockExt<T>: once_lock_ext_sealed::Sealed {
fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
where
F: FnOnce() -> T;
}
pub trait MutexExt<T>: Sealed {
type LockResult<'a>
where
Self: 'a;
fn lock_py_attached(&self, py: Python<'_>) -> Self::LockResult<'_>;
}
pub trait RwLockExt<T>: rwlock_ext_sealed::Sealed {
type ReadLockResult<'a>
where
Self: 'a;
type WriteLockResult<'a>
where
Self: 'a;
fn read_py_attached(&self, py: Python<'_>) -> Self::ReadLockResult<'_>;
fn write_py_attached(&self, py: Python<'_>) -> Self::WriteLockResult<'_>;
}
impl OnceExt for Once {
type OnceState = OnceState;
fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) {
if self.is_completed() {
return;
}
init_once_py_attached(self, py, f)
}
fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) {
if self.is_completed() {
return;
}
init_once_force_py_attached(self, py, f);
}
}
#[cfg(feature = "parking_lot")]
impl OnceExt for parking_lot::Once {
type OnceState = parking_lot::OnceState;
fn call_once_py_attached(&self, _py: Python<'_>, f: impl FnOnce()) {
if self.state().done() {
return;
}
let ts_guard = unsafe { SuspendAttach::new() };
self.call_once(move || {
drop(ts_guard);
f();
});
}
fn call_once_force_py_attached(
&self,
_py: Python<'_>,
f: impl FnOnce(&parking_lot::OnceState),
) {
if self.state().done() {
return;
}
let ts_guard = unsafe { SuspendAttach::new() };
self.call_once_force(move |state| {
drop(ts_guard);
f(&state);
});
}
}
impl<T> OnceLockExt<T> for std::sync::OnceLock<T> {
fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
where
F: FnOnce() -> T,
{
self.get()
.unwrap_or_else(|| init_once_lock_py_attached(self, py, f))
}
}
impl<T> MutexExt<T> for std::sync::Mutex<T> {
type LockResult<'a>
= std::sync::LockResult<std::sync::MutexGuard<'a, T>>
where
Self: 'a;
fn lock_py_attached(
&self,
_py: Python<'_>,
) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>> {
match self.try_lock() {
Ok(inner) => return Ok(inner),
Err(std::sync::TryLockError::Poisoned(inner)) => {
return std::sync::LockResult::Err(inner)
}
Err(std::sync::TryLockError::WouldBlock) => {}
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.lock();
drop(ts_guard);
res
}
}
#[cfg(feature = "lock_api")]
impl<R: lock_api::RawMutex, T> MutexExt<T> for lock_api::Mutex<R, T> {
type LockResult<'a>
= lock_api::MutexGuard<'a, R, T>
where
Self: 'a;
fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::MutexGuard<'_, R, T> {
if let Some(guard) = self.try_lock() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.lock();
drop(ts_guard);
res
}
}
#[cfg(feature = "arc_lock")]
impl<R, T> MutexExt<T> for std::sync::Arc<lock_api::Mutex<R, T>>
where
R: lock_api::RawMutex,
{
type LockResult<'a>
= lock_api::ArcMutexGuard<R, T>
where
Self: 'a;
fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::ArcMutexGuard<R, T> {
if let Some(guard) = self.try_lock_arc() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.lock_arc();
drop(ts_guard);
res
}
}
#[cfg(feature = "lock_api")]
impl<R, G, T> MutexExt<T> for lock_api::ReentrantMutex<R, G, T>
where
R: lock_api::RawMutex,
G: lock_api::GetThreadId,
{
type LockResult<'a>
= lock_api::ReentrantMutexGuard<'a, R, G, T>
where
Self: 'a;
fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::ReentrantMutexGuard<'_, R, G, T> {
if let Some(guard) = self.try_lock() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.lock();
drop(ts_guard);
res
}
}
#[cfg(feature = "arc_lock")]
impl<R, G, T> MutexExt<T> for std::sync::Arc<lock_api::ReentrantMutex<R, G, T>>
where
R: lock_api::RawMutex,
G: lock_api::GetThreadId,
{
type LockResult<'a>
= lock_api::ArcReentrantMutexGuard<R, G, T>
where
Self: 'a;
fn lock_py_attached(&self, _py: Python<'_>) -> lock_api::ArcReentrantMutexGuard<R, G, T> {
if let Some(guard) = self.try_lock_arc() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.lock_arc();
drop(ts_guard);
res
}
}
impl<T> RwLockExt<T> for std::sync::RwLock<T> {
type ReadLockResult<'a>
= std::sync::LockResult<std::sync::RwLockReadGuard<'a, T>>
where
Self: 'a;
type WriteLockResult<'a>
= std::sync::LockResult<std::sync::RwLockWriteGuard<'a, T>>
where
Self: 'a;
fn read_py_attached(&self, _py: Python<'_>) -> Self::ReadLockResult<'_> {
match self.try_read() {
Ok(inner) => return Ok(inner),
Err(std::sync::TryLockError::Poisoned(inner)) => {
return std::sync::LockResult::Err(inner)
}
Err(std::sync::TryLockError::WouldBlock) => {}
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.read();
drop(ts_guard);
res
}
fn write_py_attached(&self, _py: Python<'_>) -> Self::WriteLockResult<'_> {
match self.try_write() {
Ok(inner) => return Ok(inner),
Err(std::sync::TryLockError::Poisoned(inner)) => {
return std::sync::LockResult::Err(inner)
}
Err(std::sync::TryLockError::WouldBlock) => {}
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.write();
drop(ts_guard);
res
}
}
#[cfg(feature = "lock_api")]
impl<R: lock_api::RawRwLock, T> RwLockExt<T> for lock_api::RwLock<R, T> {
type ReadLockResult<'a>
= lock_api::RwLockReadGuard<'a, R, T>
where
Self: 'a;
type WriteLockResult<'a>
= lock_api::RwLockWriteGuard<'a, R, T>
where
Self: 'a;
fn read_py_attached(&self, _py: Python<'_>) -> Self::ReadLockResult<'_> {
if let Some(guard) = self.try_read() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.read();
drop(ts_guard);
res
}
fn write_py_attached(&self, _py: Python<'_>) -> Self::WriteLockResult<'_> {
if let Some(guard) = self.try_write() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.write();
drop(ts_guard);
res
}
}
#[cfg(feature = "arc_lock")]
impl<R, T> RwLockExt<T> for std::sync::Arc<lock_api::RwLock<R, T>>
where
R: lock_api::RawRwLock,
{
type ReadLockResult<'a>
= lock_api::ArcRwLockReadGuard<R, T>
where
Self: 'a;
type WriteLockResult<'a>
= lock_api::ArcRwLockWriteGuard<R, T>
where
Self: 'a;
fn read_py_attached(&self, _py: Python<'_>) -> Self::ReadLockResult<'_> {
if let Some(guard) = self.try_read_arc() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.read_arc();
drop(ts_guard);
res
}
fn write_py_attached(&self, _py: Python<'_>) -> Self::WriteLockResult<'_> {
if let Some(guard) = self.try_write_arc() {
return guard;
}
let ts_guard = unsafe { SuspendAttach::new() };
let res = self.write_arc();
drop(ts_guard);
res
}
}
#[cold]
fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
where
F: FnOnce() -> T,
{
let ts_guard = unsafe { SuspendAttach::new() };
once.call_once(move || {
drop(ts_guard);
f();
});
}
#[cold]
fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
where
F: FnOnce(&OnceState) -> T,
{
let ts_guard = unsafe { SuspendAttach::new() };
once.call_once_force(move |state| {
drop(ts_guard);
f(state);
});
}
#[cold]
fn init_once_lock_py_attached<'a, F, T>(
lock: &'a std::sync::OnceLock<T>,
_py: Python<'_>,
f: F,
) -> &'a T
where
F: FnOnce() -> T,
{
let ts_guard = unsafe { SuspendAttach::new() };
let value = lock.get_or_init(move || {
drop(ts_guard);
f()
});
value
}
mod once_lock_ext_sealed {
pub trait Sealed {}
impl<T> Sealed for std::sync::OnceLock<T> {}
}
mod rwlock_ext_sealed {
pub trait Sealed {}
impl<T> Sealed for std::sync::RwLock<T> {}
#[cfg(feature = "lock_api")]
impl<R, T> Sealed for lock_api::RwLock<R, T> {}
#[cfg(feature = "arc_lock")]
impl<R, T> Sealed for std::sync::Arc<lock_api::RwLock<R, T>> {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{PyAnyMethods, PyDict, PyDictMethods};
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "macros")]
use std::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "macros")]
use std::sync::Barrier;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(feature = "macros")]
#[crate::pyclass(crate = "crate")]
struct BoolWrapper(AtomicBool);
#[test]
fn test_intern() {
Python::attach(|py| {
let foo1 = "foo";
let foo2 = intern!(py, "foo");
let foo3 = intern!(py, stringify!(foo));
let dict = PyDict::new(py);
dict.set_item(foo1, 42_usize).unwrap();
assert!(dict.contains(foo2).unwrap());
assert_eq!(
dict.get_item(foo3)
.unwrap()
.unwrap()
.extract::<usize>()
.unwrap(),
42
);
});
}
#[test]
#[allow(deprecated)]
fn test_once_cell() {
Python::attach(|py| {
let cell = GILOnceCell::new();
assert!(cell.get(py).is_none());
assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
assert!(cell.get(py).is_none());
assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
assert_eq!(cell.get(py), Some(&2));
assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
})
}
#[test]
#[allow(deprecated)]
fn test_once_cell_drop() {
#[derive(Debug)]
struct RecordDrop<'a>(&'a mut bool);
impl Drop for RecordDrop<'_> {
fn drop(&mut self) {
*self.0 = true;
}
}
Python::attach(|py| {
let mut dropped = false;
let cell = GILOnceCell::new();
cell.set(py, RecordDrop(&mut dropped)).unwrap();
let drop_container = cell.get(py).unwrap();
assert!(!*drop_container.0);
drop(cell);
assert!(dropped);
});
}
#[test]
#[cfg(not(target_arch = "wasm32"))] fn test_once_ext() {
macro_rules! test_once {
($once:expr, $is_poisoned:expr) => {{
let init = $once;
std::thread::scope(|s| {
let handle = s.spawn(|| {
Python::attach(|py| {
init.call_once_py_attached(py, || panic!());
})
});
assert!(handle.join().is_err());
let handle = s.spawn(|| {
Python::attach(|py| {
init.call_once_py_attached(py, || {});
});
});
assert!(handle.join().is_err());
Python::attach(|py| {
init.call_once_force_py_attached(py, |state| {
assert!($is_poisoned(state.clone()));
});
init.call_once_py_attached(py, || {});
});
Python::attach(|py| init.call_once_force_py_attached(py, |_| panic!()));
});
}};
}
test_once!(Once::new(), OnceState::is_poisoned);
#[cfg(feature = "parking_lot")]
test_once!(parking_lot::Once::new(), parking_lot::OnceState::poisoned);
}
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_once_lock_ext() {
let cell = std::sync::OnceLock::new();
std::thread::scope(|s| {
assert!(cell.get().is_none());
s.spawn(|| {
Python::attach(|py| {
assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345);
});
});
});
assert_eq!(cell.get(), Some(&12345));
}
#[cfg(feature = "macros")]
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_mutex_ext() {
let barrier = Barrier::new(2);
let mutex = Python::attach(|py| -> Mutex<Py<BoolWrapper>> {
Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
});
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b = mutex.lock_py_attached(py).unwrap();
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
(*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b = mutex.lock_py_attached(py).unwrap();
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
});
});
});
}
#[cfg(feature = "macros")]
#[cfg(all(
any(feature = "parking_lot", feature = "lock_api"),
not(target_arch = "wasm32") // We are building wasm Python with pthreads disabled
))]
#[test]
fn test_parking_lot_mutex_ext() {
macro_rules! test_mutex {
($guard:ty ,$mutex:stmt) => {{
let barrier = Barrier::new(2);
let mutex = Python::attach({ $mutex });
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b: $guard = mutex.lock_py_attached(py);
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
(*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b: $guard = mutex.lock_py_attached(py);
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
});
});
});
}};
}
test_mutex!(parking_lot::MutexGuard<'_, _>, |py| {
parking_lot::Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
});
test_mutex!(parking_lot::ReentrantMutexGuard<'_, _>, |py| {
parking_lot::ReentrantMutex::new(
Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(),
)
});
#[cfg(feature = "arc_lock")]
test_mutex!(parking_lot::ArcMutexGuard<_, _>, |py| {
let mutex =
parking_lot::Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap());
std::sync::Arc::new(mutex)
});
#[cfg(feature = "arc_lock")]
test_mutex!(parking_lot::ArcReentrantMutexGuard<_, _, _>, |py| {
let mutex =
parking_lot::ReentrantMutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap());
std::sync::Arc::new(mutex)
});
}
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_mutex_ext_poison() {
let mutex = Mutex::new(42);
std::thread::scope(|s| {
let lock_result = s.spawn(|| {
Python::attach(|py| {
let _unused = mutex.lock_py_attached(py);
panic!();
});
});
assert!(lock_result.join().is_err());
assert!(mutex.is_poisoned());
});
let guard = Python::attach(|py| {
match mutex.lock_py_attached(py) {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
});
assert_eq!(*guard, 42);
}
#[cfg(feature = "macros")]
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_rwlock_ext_writer_blocks_reader() {
use std::sync::RwLock;
let barrier = Barrier::new(2);
let rwlock = Python::attach(|py| -> RwLock<Py<BoolWrapper>> {
RwLock::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
});
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b = rwlock.write_py_attached(py).unwrap();
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
(*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b = rwlock.read_py_attached(py).unwrap();
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
});
});
});
}
#[cfg(feature = "macros")]
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_rwlock_ext_reader_blocks_writer() {
use std::sync::RwLock;
let barrier = Barrier::new(2);
let rwlock = Python::attach(|py| -> RwLock<Py<BoolWrapper>> {
RwLock::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
});
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b = rwlock.read_py_attached(py).unwrap();
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(!(*b).bind(py).borrow().0.load(Ordering::Acquire));
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b = rwlock.write_py_attached(py).unwrap();
(*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
});
Python::attach(|py| {
let b = rwlock.read_py_attached(py).unwrap();
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
drop(b);
});
}
#[cfg(feature = "macros")]
#[cfg(all(
any(feature = "parking_lot", feature = "lock_api"),
not(target_arch = "wasm32") // We are building wasm Python with pthreads disabled
))]
#[test]
fn test_parking_lot_rwlock_ext_writer_blocks_reader() {
macro_rules! test_rwlock {
($write_guard:ty, $read_guard:ty, $rwlock:stmt) => {{
let barrier = Barrier::new(2);
let rwlock = Python::attach({ $rwlock });
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b: $write_guard = rwlock.write_py_attached(py);
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
(*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b: $read_guard = rwlock.read_py_attached(py);
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
});
});
});
}};
}
test_rwlock!(
parking_lot::RwLockWriteGuard<'_, _>,
parking_lot::RwLockReadGuard<'_, _>,
|py| {
parking_lot::RwLock::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
}
);
#[cfg(feature = "arc_lock")]
test_rwlock!(
parking_lot::ArcRwLockWriteGuard<_, _>,
parking_lot::ArcRwLockReadGuard<_, _>,
|py| {
let rwlock = parking_lot::RwLock::new(
Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(),
);
std::sync::Arc::new(rwlock)
}
);
}
#[cfg(feature = "macros")]
#[cfg(all(
any(feature = "parking_lot", feature = "lock_api"),
not(target_arch = "wasm32") // We are building wasm Python with pthreads disabled
))]
#[test]
fn test_parking_lot_rwlock_ext_reader_blocks_writer() {
macro_rules! test_rwlock {
($write_guard:ty, $read_guard:ty, $rwlock:stmt) => {{
let barrier = Barrier::new(2);
let rwlock = Python::attach({ $rwlock });
std::thread::scope(|s| {
s.spawn(|| {
Python::attach(|py| {
let b: $read_guard = rwlock.read_py_attached(py);
barrier.wait();
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(!(*b).bind(py).borrow().0.load(Ordering::Acquire)); (*b).bind(py).borrow().0.store(true, Ordering::Release);
drop(b);
});
});
s.spawn(|| {
barrier.wait();
Python::attach(|py| {
let b: $write_guard = rwlock.write_py_attached(py);
(*b).bind(py).borrow().0.store(true, Ordering::Release);
});
});
});
Python::attach(|py| {
let b: $read_guard = rwlock.read_py_attached(py);
assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
drop(b);
});
}};
}
test_rwlock!(
parking_lot::RwLockWriteGuard<'_, _>,
parking_lot::RwLockReadGuard<'_, _>,
|py| {
parking_lot::RwLock::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
}
);
#[cfg(feature = "arc_lock")]
test_rwlock!(
parking_lot::ArcRwLockWriteGuard<_, _>,
parking_lot::ArcRwLockReadGuard<_, _>,
|py| {
let rwlock = parking_lot::RwLock::new(
Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(),
);
std::sync::Arc::new(rwlock)
}
);
}
#[cfg(not(target_arch = "wasm32"))] #[test]
fn test_rwlock_ext_poison() {
use std::sync::RwLock;
let rwlock = RwLock::new(42);
std::thread::scope(|s| {
let lock_result = s.spawn(|| {
Python::attach(|py| {
let _unused = rwlock.write_py_attached(py);
panic!();
});
});
assert!(lock_result.join().is_err());
assert!(rwlock.is_poisoned());
Python::attach(|py| {
assert!(rwlock.read_py_attached(py).is_err());
assert!(rwlock.write_py_attached(py).is_err());
});
});
Python::attach(|py| {
let guard = rwlock.write_py_attached(py).unwrap_err().into_inner();
assert_eq!(*guard, 42);
});
}
}