-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Soundness of std::sync::Once #65796
Comments
Heh, I remember almost filing an issue about this as well, but I believe the code is correct. Specifically, if a thread is unparked before it is parked, it will not block. Docs
|
Ah, good! Ok, that crosses off one from the list, and the other is already taken care of in #65719. |
Thanks for the report here @pitdicker! I'll try to give my thoughts on the problems here
As previously mentioned I think this isn't a bug, it's just how park/unpark works.
This seems fine to me to tweak, I don't really know what the current state of the rules for mutable references and such are. I don't believe it's actually UB today but if we need to future-proof ourselves somehow seems reasonable!
I agree that this looks like a rough duplicate of #55005 if I understand that right. To make sure I understand the problem you're thinking about, the moment after we store Does that sounds like an accurate summary?
FWIW we've always designed park/unpark to literally use futexes eventually, we've just never had the need to actually change the implementation at this point.
I think the only really important thing with |
Please amend the issue text to mention that this was an incorrect observation -- I just lost a bunch of time re-discovering the correctness of this code before reading on here. ;) Also, we should likely add a clarifying comment in the code. Since you are refactoring that anyway, could you include such a comment in your PR?
That looks dangerous indeed! The question is, what is that Notice that we already have interior mutability, right?
Confirmed, this is the same problem as in
Yes. |
Apologies! I added a commit with such a comment.
It passes an |
Things should be fixed with #65719, but I thought to create this issue to get a little more attention because that one started out with the innocent "refactor" in the title. |
But only the other thread accesses the Basically, all accesses in the enqueuing thread should be changed to go through a raw pointer; the same raw pointer that is also sent to the other thread: let mut node = Waiter {
thread: Some(thread::current()),
signaled: AtomicBool::new(false),
next: ptr::null_mut(),
};
let node = &mut node as *mut Waiter; // henceforth only use this
Please don't; there is no need for interior mutability here and it also doesn't help -- the issue is about using |
Closing since #65719 was merged. |
While working on a refactor of
std::sync::Once
in #65719 I stumbled across multiple soundness issues. I now believe the current algorithm that queues waiting threads in a linked list with nodes an the stack of each waiting thread is not worth the complexity.Thread may be parked forever
Edit: this is not a valid concern, see #65796 (comment).
Code:
The thread managing the waiter queue (thread 1) sets
node.signaled
and unparkes a thread.The thread that wants to wait (thread 2) checks
node.signaled
before parking itself.But at HERE thread 1 may set
node.signaled
and unpark thread 2 (which is not parked yet). Afterwards thread 2 will park itself, and never receive an unpark again.This can be solved by using
park_timeout
. It does seems suboptimal to me though.Aliasing of a mutable reference
Code
This can be solved by using shared references and interior mutability.
Use of a potentially dangling shared reference (#55005)
Code
The waiting thread can free
queue
aftersignaled
is set after a spurious wakeup. At this pointstore
can in theory still hold a dangling reference.There is not much the implementation of
OnceCell
can do here, but I suppose if will be solved in the same way as #55005.This reason
std::sync::Once
does not go with the obvious implementation that uses a mutex, is becauseMutex::new
is not const. This is the explanation in the comments:The talk about atomics and lock-free make it sound like this implementation might be more optimal then using a mutex. But the park/unpark machinery currently relies on a mutex+condvar per thread. So instead of using one mutex, it is using as many as there are waiting threads.
Alternative implementation
While also a bit tricky, it is still possible to just use a mutex: encode a reference to an
Arc<Mutex>
inOnce.state
. The reference will be created when theOnce
starts running, and not be dropped until theOnce
is dropped.I'd like to include the fixes in #65719, and make unother PR with an alternative implementation using a mutex. Also a thing to remember is that if #65719 ever lands, the standard library can just use the
Once
implementation fromparking_lot
.The text was updated successfully, but these errors were encountered: