Repository navigation
Introduce PyAtomicRef and impl for PyObject.typ - #3828
Conversation
|
Feel free to ignore those Clippy failures. @youknowone likely missed them in #3817. |
| type> | ||
|
|
||
| fn deref(&self) -> &Self::Target { | ||
| unsafe { self.ptr.load().as_ref() } |
There was a problem hiding this comment.
This is looking unsafe.
While thread 1 called deref() to obtain &Py<T>, inner can be swapped out in thread 2.
When the swapped out object is dropped, thread 1 will have a dangling pointer.
|
Is this approach safe as we manaully require the reference to the pytype cannot across between the frames. Most of the case we use the pytype is either using pytyperef or using the reference immediately. |
|
Oh, I missed your last push. I will check the changes soon. |
| } | ||
|
|
||
| impl PyTypeRef { | ||
| impl Py<PyType> { |
youknowone
left a comment
There was a problem hiding this comment.
Logically looking good to me.
| /// tracer function for this frame (usually is None) | ||
| pub trace: PyMutex<PyObjectRef>, | ||
| state: PyMutex<FrameState>, | ||
| pub temperary_refs: PyMutex<Vec<PyObjectRef>>, |
There was a problem hiding this comment.
Maybe we can reuse this for del.
I didn't know many fields in Frame are mutex. I thought they are single-thread accessible.
typo
| pub temperary_refs: PyMutex<Vec<PyObjectRef>>, | |
| pub temporary_refs: PyMutex<Vec<PyObjectRef>>, |
There was a problem hiding this comment.
If frame itself is executed single-threaded, we could replace mutex with an atomic increasing vec, do we have any crates doing that?
There was a problem hiding this comment.
I don't think we have those crate. When we create safety by human, UnsafeCell is the common tool
| #[must_use] | ||
| pub unsafe fn swap(&self, pyref: PyRef<T>) -> PyRef<T> { | ||
| let py = PyRef::leak(pyref); | ||
| let old = self.0.swap(NonNull::from(py)); |
There was a problem hiding this comment.
| let old = self.0.swap(NonNull::from(py)); | |
| let old = self.0.swap(NonNull::new_unchecked(py)); |
we already know py is not a null.
I am not sure possible but retaining &'static mut T will be good when available.
There was a problem hiding this comment.
these two are equal. no check will be made as NonNull::from only accept valid value and it is calling new_unchecked inside the function
There was a problem hiding this comment.
what do you mean about &'static mut T
There was a problem hiding this comment.
oh, got it. py is a reference, not a pointer.
I was thinking if this is possible.
pub struct PyAtomicRef<T: PyObjectPayload>(AtomicCell<&'static mut Py<T>>);There was a problem hiding this comment.
I think I've found over the past while, mainly from looking at crossbeam's issue list, that atomics aren't actually able to be hidden away enough such that AtomicCell would be a good abstraction. I'd honestly prefer a PyAtomic<*mut Py<T>>, which is a type alias that resolves to std::sync::atomic::AtomicPtr when threading is enabled, and Cell<*mut _> when it's not. I meant to go and replace all the AtomicCell uses with PyAtomic after I added PyAtomic, but I never got around to it.
| } | ||
| } | ||
|
|
||
| pub struct PyAtomicRef<T: PyObjectPayload>(AtomicCell<NonNull<Py<T>>>); |
There was a problem hiding this comment.
Will using AtomicCell<NonNull> spend same cost as AtomicPtr?
There was a problem hiding this comment.
AtomicCell::::is_lock_free() should be true for the platforms that support AtomicPtr. But I am not sure about any overhead that AtomicCell does.
| pub fn swap_to_temporary_refs(&self, pyref: PyRef<T>, vm: &VirtualMachine) { | ||
| let old = unsafe { self.swap(pyref) }; | ||
| if let Some(frame) = vm.current_frame() { | ||
| frame.temporary_refs.lock().push(old.into()); |
There was a problem hiding this comment.
Does temporary_refs ever get cleared? Like I guess it doesn't need to be, necessarily, but I don't like the idea of
class A: pass
class B: pass
B().__class__ = Ameaning that A will never ever ever be deallocated.
What if instead class() returned a guard.... no that wouldn't work. I'll try and think of something by the morning, maybe.
There was a problem hiding this comment.
I guess when the frame is popped
There was a problem hiding this comment.
Ohhh, I missed that its per-frame, yeah I guess that does make sense. Nevermind!
There was a problem hiding this comment.
I guess when the frame is popped
Yes, we can clear the refs for each instruction, but I don't think it is worth.
|
(using PyAtomic instead of AtomicCell would fix the provenance errors in miri) |
let me try |
|
Also, |
Don't know why but seem like failing the compile when threading is off, any mistake about how to use PyAtomic? |
nevermind, it just the name conflict |
|
@coolreader18 @youknowone review? |
youknowone
left a comment
There was a problem hiding this comment.
@qingshi163 looks good to me.
If @coolreader18 is too busy to review this PR, please feel free to merge it yourself.
Thank you for the awesome work!
* Remove unused PyLease PyLease wrapped a read guard over the object's type when `typ` was a `PyRwLock<PyTypeRef>`. Its last users, `class()` and `lease_class()`, went away when `typ` became a `PyAtomicRef<PyType>` (#3828). Its field is private and it has no constructor, so it cannot be built anywhere. Assisted-by: Claude Code:claude-opus-5-5 * Skip test_large_content_length_truncated on macOS The test sets the CGI handler timeout to 1ms. That timeout also bounds reading the request line, so when the client has not sent the request within 1ms the server logs "Request timed out" and closes the connection. The client then gets ConnectionResetError or BrokenPipeError. This reproduces on macOS with both RustPython and CPython 3.14.6. The skip was removed in #8759 after Linux-only repeats. Assisted-by: Claude Code:claude-opus-5-5
@youknowone correctness and performance check?