-
Notifications
You must be signed in to change notification settings - Fork 876
AtomicPy
experiment
#5356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
AtomicPy
experiment
#5356
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sketching this out! I had a first read, mostly looks like how I expect. I think I spotted an edge case which makes me wonder if this is going to end up a bit hamstrung, need to think about that 🤔
The interaction between AtomicPy
/ AtomicOptionPy
is also a touch unfortunate, but I guess Option<AtomicPy<T>>
is also unhelpful for obvious reasons. I guess AtomicPtr
is technically nullable, should we just lean into that and only have the AtomicOptionPy
form?
src/atomic.rs
Outdated
// SAFETY: `ptr` is a non-null, borrowed pointer to `ffi::PyObject` of type `T` | ||
Err(Borrowed::from_ptr_unchecked(py, ptr) | ||
.to_owned() | ||
.cast_into_unchecked()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, is there a potential race here if another thread writes avalue to the AtomicPy
dropping this ptr
before the .to_owned
call?
... is this potentially indicative of a more general problem with AtomicPy
, e.g. does load
have the same issue? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uff, I think you're right here. I guess this means we can only really provide the swap
operation (and probably take
for the option variant), because that does not touch the ref-count (at least if we want to keep this a thin wrapper).
I could image providing load
and swap
if we take more control.
load
would need to swap with nullptr, increment the ref-count, store back originalload
andswap
spin briefly if we're in the middle of aload
- we can't expose an
Ordering
anymore
Not sure if that would have significant benefits left over a Mutex<Py>
.
compare_exchange
looks pretty hopeless, I don't see a way to expose that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's possible to expose this in general, you end up needing something like crossbeam-epoch or arc-swap.
/// store part of this operation [Relaxed](Ordering::Relaxed), and using | ||
/// [Release](Ordering::Release) makes the load part [Relaxed](Ordering::Relaxed). | ||
#[inline] | ||
pub fn swap<'py>(&self, obj: Bound<'py, T>, order: Ordering) -> Bound<'py, T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It confused me a bit confusing to have from_bound
and from_py
on one side and swap_unbound
and swap
on the other side. What about something like from_bound
/from_unbound
and swap_bound
/swap_unbound
?
This is an experiment playing with the idea (#5349 (comment)) of an
AtomicPy
that can be swapped locked-free using anAtomicPtr
internally. This is basically a really thin layer on top ofAtomicPtr
, just to enough to handle the reference counting, so it's still fairly low level. There could also be a companionAtomicOptionPy
that makes use of the null-pointer.The primary use case I could imagine for this are
frozen
pyclasses (in combination with the free-threaded build). There may be others as well. I would be curious what others think about this.