Skip to content

Commit f4d591e

Browse files
committed
WIP: AtomicPy
1 parent 39bf3b8 commit f4d591e

File tree

2 files changed

+199
-0
lines changed

2 files changed

+199
-0
lines changed

src/atomic.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
use std::{
2+
marker::PhantomData,
3+
sync::atomic::{AtomicPtr, Ordering},
4+
};
5+
6+
use crate::{ffi, Borrowed, Bound, Py, Python};
7+
8+
/// Variant of [`Py<T>`] that can be atomically swapped
9+
#[repr(transparent)]
10+
pub struct AtomicPy<T> {
11+
inner: AtomicPtr<ffi::PyObject>,
12+
_marker: PhantomData<T>,
13+
}
14+
15+
// SAFETY: same as `Py<T>`
16+
unsafe impl<T> Send for AtomicPy<T> {}
17+
unsafe impl<T> Sync for AtomicPy<T> {}
18+
19+
impl<T> AtomicPy<T> {
20+
/// Create an [`AtomicPy<T>`] holding the [`Py<T>`]
21+
#[inline]
22+
pub fn from_py(obj: Py<T>) -> Self {
23+
Self {
24+
inner: AtomicPtr::new(obj.into_ptr()),
25+
_marker: PhantomData,
26+
}
27+
}
28+
29+
/// Create an [`AtomicPy<T>`] holding the [`Bound<T>`]
30+
#[inline]
31+
pub fn from_bound(obj: Bound<'_, T>) -> Self {
32+
Self::from_py(obj.unbind())
33+
}
34+
35+
/// Consumes the [`AtomicPy<T>`] and turns it back into a [`Py<T>`]
36+
#[inline]
37+
pub fn into_inner(self) -> Py<T> {
38+
let ptr = *std::mem::ManuallyDrop::new(self).inner.get_mut();
39+
unsafe { Py::from_owned_ptr_unchecked(ptr) }
40+
}
41+
42+
/// Loads the object stored in the [`AtomicPy`]
43+
#[inline]
44+
pub fn load<'py>(&self, py: Python<'py>, order: Ordering) -> Bound<'py, T> {
45+
let ptr = self.inner.load(order);
46+
unsafe {
47+
Borrowed::from_ptr_unchecked(py, ptr)
48+
.to_owned()
49+
.cast_into_unchecked()
50+
}
51+
}
52+
53+
/// Stores `obj` in the [`AtomicPy`], dropping its current value
54+
///
55+
/// Note: This uses [`swap`](Self::swap) under the hood.
56+
#[inline]
57+
pub fn store<'py>(&self, obj: Bound<'py, T>, order: Ordering) {
58+
let _ = self.swap(obj, order);
59+
}
60+
61+
/// Stores `obj` in the [`AtomicPy`], returning the previous value.
62+
///
63+
/// See [`swap`](AtomicPy::swap)
64+
#[inline]
65+
pub fn swap_unbound(&self, obj: Py<T>, order: Ordering) -> Py<T> {
66+
let ptr = self.inner.swap(obj.into_ptr(), order);
67+
// SAFETY: `ptr` is a non-null, owned pointer to `ffi::PyObject` of type `T`
68+
unsafe { Py::from_owned_ptr_unchecked(ptr) }
69+
}
70+
71+
/// Stores `obj` in the [`AtomicPy`], returning the previous value.
72+
///
73+
/// `swap` takes an [Ordering] argument which describes the memory ordering of this operation.
74+
/// All ordering modes are possible. Note that using [Acquire](Ordering::Acquire) makes the
75+
/// store part of this operation [Relaxed](Ordering::Relaxed), and using
76+
/// [Release](Ordering::Release) makes the load part [Relaxed](Ordering::Relaxed).
77+
#[inline]
78+
pub fn swap<'py>(&self, obj: Bound<'py, T>, order: Ordering) -> Bound<'py, T> {
79+
let py = obj.py();
80+
self.swap_unbound(obj.unbind(), order).into_bound(py)
81+
}
82+
83+
/// Stores `new` into the [`AtomicPy`] if its current value is `current` (Python `current is
84+
/// self`)
85+
///
86+
/// The return value is a [`Result`] indicating whether `new` was written and containing the
87+
/// previous value.
88+
///
89+
/// `compare_exchange` takes two [Ordering] arguments to describe the memory ordering of this
90+
/// operation. `success` describes the required ordering for the read-modify-write operation
91+
/// that takes place if the comparison with `current` succeeds. `failure` describes the required
92+
/// ordering for the load operation that takes place when the comparison fails. Using
93+
/// [Acquire](Ordering::Acquire) as success ordering makes the store part of this operation
94+
/// [Relaxed](Ordering::Relaxed), and using [Release](Ordering::Release) makes the successful
95+
/// load [Relaxed](Ordering::Relaxed). The failure ordering can only be
96+
/// [SeqCst](Ordering::SeqCst), [Acquire](Ordering::Acquire) or [Relaxed](Ordering::Relaxed).
97+
#[inline]
98+
pub fn compare_exchange<'py>(
99+
&self,
100+
current: &Bound<'py, T>,
101+
new: &Bound<'py, T>,
102+
success: Ordering,
103+
failure: Ordering,
104+
) -> Result<Bound<'py, T>, Bound<'py, T>> {
105+
let py = current.py();
106+
match self
107+
.inner
108+
.compare_exchange(current.as_ptr(), new.as_ptr(), success, failure)
109+
{
110+
// stored `new`, `ptr` is owned
111+
Ok(ptr) => unsafe {
112+
let _ = std::mem::ManuallyDrop::new(new.clone()); // only incref if `new` was successfully stored
113+
114+
// SAFETY: `ptr` is a non-null, owned pointer to `ffi::PyObject` of type `T`
115+
Ok(Bound::from_owned_ptr_unchecked(py, ptr).cast_into_unchecked())
116+
},
117+
// did not store `new`, `ptr` is borrowed
118+
Err(ptr) => unsafe {
119+
// SAFETY: `ptr` is a non-null, borrowed pointer to `ffi::PyObject` of type `T`
120+
Err(Borrowed::from_ptr_unchecked(py, ptr)
121+
.to_owned()
122+
.cast_into_unchecked())
123+
},
124+
}
125+
}
126+
127+
/// Stores `new` into the [`AtomicPy`] if its current value is `current` (Python `current is
128+
/// self`)
129+
///
130+
/// Unlike [AtomicPy::compare_exchange], this function is allowed to spuriously fail even when
131+
/// the comparison succeeds, which can result in more efficient code on some platforms. The
132+
/// return value is a [`Result`] indicating whether `new` was written and containing the
133+
/// previous value.
134+
///
135+
/// `compare_exchange_weak` takes two [Ordering] arguments to describe the memory ordering of
136+
/// this operation. `success` describes the required ordering for the read-modify-write
137+
/// operation that takes place if the comparison with `current` succeeds. `failure` describes
138+
/// the required ordering for the load operation that takes place when the comparison fails.
139+
/// Using [Acquire](Ordering::Acquire) as success ordering makes the store part of this
140+
/// operation [Relaxed](Ordering::Relaxed), and using [Release](Ordering::Release) makes the
141+
/// successful load [Relaxed](Ordering::Relaxed). The failure ordering can only be
142+
/// [SeqCst](Ordering::SeqCst), [Acquire](Ordering::Acquire) or [Relaxed](Ordering::Relaxed).
143+
#[inline]
144+
pub fn compare_exchange_weak<'py>(
145+
&self,
146+
current: &Bound<'py, T>,
147+
new: &Bound<'py, T>,
148+
success: Ordering,
149+
failure: Ordering,
150+
) -> Result<Bound<'py, T>, Bound<'py, T>> {
151+
let py = current.py();
152+
match self
153+
.inner
154+
.compare_exchange_weak(current.as_ptr(), new.as_ptr(), success, failure)
155+
{
156+
// stored `new`, `ptr` is owned `current`
157+
Ok(ptr) => unsafe {
158+
let _ = std::mem::ManuallyDrop::new(new.clone()); // only incref if `new` was successfully stored
159+
160+
// SAFETY: `ptr` is a non-null, owned pointer to `ffi::PyObject` of type `T`
161+
Ok(Bound::from_owned_ptr_unchecked(py, ptr).cast_into_unchecked())
162+
},
163+
// did not store `new`, `ptr` is borrowed
164+
Err(ptr) => unsafe {
165+
// SAFETY: `ptr` is a non-null, borrowed pointer to `ffi::PyObject` of type `T`
166+
Err(Borrowed::from_ptr_unchecked(py, ptr)
167+
.to_owned()
168+
.cast_into_unchecked())
169+
},
170+
}
171+
}
172+
}
173+
174+
impl<T> From<Py<T>> for AtomicPy<T> {
175+
/// Create an [`AtomicPy<T>`] holding the [`Py<T>`]
176+
fn from(obj: Py<T>) -> Self {
177+
Self::from_py(obj)
178+
}
179+
}
180+
181+
impl<T> From<Bound<'_, T>> for AtomicPy<T> {
182+
/// Create an [`AtomicPy<T>`] holding the [`Bound<T>`]
183+
fn from(obj: Bound<'_, T>) -> Self {
184+
Self::from_bound(obj)
185+
}
186+
}
187+
188+
impl<T> Drop for AtomicPy<T> {
189+
/// Drop the inner [`Py<T>`]
190+
fn drop(&mut self) {
191+
// SAFETY: `inner` is a non-null, owned pointer to `ffi::PyObject` of type `T`
192+
unsafe { Py::<T>::from_owned_ptr_unchecked(*self.inner.get_mut()) };
193+
}
194+
}
195+
196+
#[cfg(test)]
197+
mod tests {}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ pub mod impl_;
422422
mod internal_tricks;
423423
mod internal;
424424

425+
mod atomic;
425426
pub mod buffer;
426427
pub mod call;
427428
pub mod conversion;
@@ -449,6 +450,7 @@ mod version;
449450

450451
#[allow(unused_imports)] // with no features enabled this module has no public exports
451452
pub use crate::conversions::*;
453+
pub use atomic::AtomicPy;
452454

453455
#[cfg(feature = "macros")]
454456
pub use pyo3_macros::{

0 commit comments

Comments
 (0)