-
Notifications
You must be signed in to change notification settings - Fork 470
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
Race condition when working with a disconnected channel #838
Comments
I've ported some tests from Go, both miri and tsan detect races. |
As long as there's a Release operation on This seems more like an issue with TSAN not detecting fences properly; something which even the Rust stdlib specializes for. |
@kprotty, thanks! |
BTW, it's not only TSAN. Miri also detects data race here. |
@kprotty, following your comment in the related miri issue:
I agree that the My initial concern was that on disconnection, the receiving side awakes but isn't guaranteed to see effects of the memory changes made by the sending side before disconnecting. This may be an unexpected behavior. For instance, channels in Go provide such synchronization on purpose. The question is should crossbeam channels guarantee the synchronization on disconnection or not? For the Array flavor it can be easily achieved without performance degradation (just by swapping the fence and the load, or even by moving the fence deeper to the |
IMO, a channel should only ensure synchronization for the data sent and retrieved through the channel's API. Relying on the channel to create happens-before edges with data external to it (disconnect included, as it's a state observation not a data transfer) feels like unspecified behavior and is better guaranteed through explicit/separate synchronization. |
Intuitively I would expect that a disconnect is a signal I send through the channel, and everyone who receives the signal gets a happens-after from me sending the signal, as with usual message passing. There should be good perf reasons for not having this kind of message-passing semantics.
Miri supports fences just fine, so that's not it. |
@RalfJung, actually, channels work as you described. But there's a more subtle case: What if the receiver gets to the Should we guarantee that the main thread sees the effect of (A)? I think we should, because that makes reasoning about possible execution paths easier and removes data race. It can be implemented without performance penalty. At least for the |
It implicitly receives the message that the channel has been closed, doesn't it? It doesn't matter whether it actually had to wait or not, the
In my opinion, definitely yes. We can only get to B if A already happened, so the happens-before edge should be established. |
At the moment, the following piece of code contains a race condition if the spawned thread drops the sender before the receiver blocks on
recv()
(thread sanitizer catches it):The race is only present when using the
Array
orList
flavor, whileZero
works fine here. It looks like the reason is that such relaxed loads:crossbeam/crossbeam-channel/src/flavors/array.rs
Lines 276 to 277 in 80224bc
provide no acquire semantics, thus the main thread might not see effect of (A).
The same problem with sending to a disconnected channel.
In earlier versions this was a
SeqCst
load, but then it was optimized and lost its synchronization property, hence (A) is not guaranteed to happen-before (B).To me it looks like a bug because channels are usually treated as synchronization objects:
std::sync::mpsc
is presented this way in Rust Doc (though mpsc::channel has the same flaw), Go guarantees such synchronization (here's the same test for Go channels, it passes thread sanitizer checks).But maybe this was a conscious choice to sacrifice synchronization for performance in such cases?
The text was updated successfully, but these errors were encountered: