use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};
pub struct PyMutex<T: ?Sized> {
pub(crate) mutex: UnsafeCell<crate::ffi::PyMutex>,
pub(crate) data: UnsafeCell<T>,
}
pub struct PyMutexGuard<'a, T: ?Sized> {
inner: &'a PyMutex<T>,
_phantom: PhantomData<*const ()>,
}
unsafe impl<T: ?Sized + Sync> Sync for PyMutexGuard<'_, T> {}
unsafe impl<T: ?Sized + Send> Send for PyMutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for PyMutex<T> {}
impl<T> PyMutex<T> {
pub fn lock(&self) -> PyMutexGuard<'_, T> {
unsafe { crate::ffi::PyMutex_Lock(self.mutex.get()) };
PyMutexGuard::new(self)
}
pub const fn new(value: T) -> Self {
Self {
mutex: UnsafeCell::new(crate::ffi::PyMutex::new()),
data: UnsafeCell::new(value),
}
}
#[cfg(Py_3_14)]
pub fn is_locked(&self) -> bool {
let ret = unsafe { crate::ffi::PyMutex_IsLocked(self.mutex.get()) };
ret != 0
}
pub fn into_inner(self) -> T
where
T: Sized,
{
self.data.into_inner()
}
}
impl<'mutex, T: ?Sized> PyMutexGuard<'mutex, T> {
fn new(lock: &'mutex PyMutex<T>) -> PyMutexGuard<'mutex, T> {
PyMutexGuard {
inner: lock,
_phantom: PhantomData,
}
}
}
impl<'a, T: ?Sized> Drop for PyMutexGuard<'a, T> {
fn drop(&mut self) {
unsafe { crate::ffi::PyMutex_Unlock(self.inner.mutex.get()) };
}
}
impl<'a, T> Deref for PyMutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.inner.data.get() }
}
}
impl<'a, T> DerefMut for PyMutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.data.get() }
}
}
#[cfg(test)]
mod tests {
extern crate std;
#[cfg(not(target_arch = "wasm32"))]
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Barrier;
use super::*;
#[cfg(not(target_arch = "wasm32"))]
use crate::Py;
#[cfg(not(target_arch = "wasm32"))]
use crate::Python;
#[cfg(not(target_arch = "wasm32"))]
use crate::types::{PyAnyMethods, PyDict, PyDictMethods, PyNone};
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_pymutex() {
let mutex = Python::attach(|py| -> PyMutex<Py<PyDict>> {
let d = PyDict::new(py);
PyMutex::new(d.unbind())
});
#[cfg_attr(not(Py_3_14), allow(unused_variables))]
let mutex = Python::attach(|py| {
let mutex = py.detach(|| -> PyMutex<Py<PyDict>> {
std::thread::spawn(|| {
let dict_guard = mutex.lock();
Python::attach(|py| {
let dict = dict_guard.bind(py);
dict.set_item(PyNone::get(py), PyNone::get(py)).unwrap();
});
#[cfg(Py_3_14)]
assert!(mutex.is_locked());
drop(dict_guard);
#[cfg(Py_3_14)]
assert!(!mutex.is_locked());
mutex
})
.join()
.unwrap()
});
let dict_guard = mutex.lock();
#[cfg(Py_3_14)]
assert!(mutex.is_locked());
let d = dict_guard.bind(py);
assert!(
d.get_item(PyNone::get(py))
.unwrap()
.unwrap()
.eq(PyNone::get(py))
.unwrap()
);
#[cfg(Py_3_14)]
assert!(mutex.is_locked());
drop(dict_guard);
#[cfg(Py_3_14)]
assert!(!mutex.is_locked());
mutex
});
#[cfg(Py_3_14)]
assert!(!mutex.is_locked());
}
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_pymutex_blocks() {
let mutex = PyMutex::new(());
let first_thread_locked_once = AtomicBool::new(false);
let second_thread_locked_once = AtomicBool::new(false);
let finished = AtomicBool::new(false);
let barrier = Barrier::new(2);
std::thread::scope(|s| {
s.spawn(|| {
let guard = mutex.lock();
first_thread_locked_once.store(true, Ordering::SeqCst);
while !finished.load(Ordering::SeqCst) {
if second_thread_locked_once.load(Ordering::SeqCst) {
std::thread::sleep(core::time::Duration::from_millis(10));
barrier.wait();
finished.store(true, Ordering::SeqCst);
}
}
drop(guard);
});
s.spawn(|| {
while !first_thread_locked_once.load(Ordering::SeqCst) {
core::hint::spin_loop();
}
second_thread_locked_once.store(true, Ordering::SeqCst);
let guard = mutex.lock();
assert!(finished.load(Ordering::SeqCst));
drop(guard);
});
barrier.wait();
});
}
#[test]
fn test_send_not_send() {
use crate::impl_::pyclass::{IsSend, IsSync, value_of};
assert!(!value_of!(IsSend, PyMutexGuard<'_, i32>));
assert!(value_of!(IsSync, PyMutexGuard<'_, i32>));
assert!(value_of!(IsSend, PyMutex<i32>));
assert!(value_of!(IsSync, PyMutex<i32>));
}
}