-
Notifications
You must be signed in to change notification settings - Fork 501
Description
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):
fn race() {
static mut V: u32 = 0;
let (s, r) = bounded::<i32>(10);
let t = thread::spawn(move || {
unsafe { V = 1 }; // (A)
drop(s);
});
let _ = r.recv().unwrap_err();
unsafe { V = 2 } // (B)
t.join().unwrap();
}
The race is only present when using the Array
or List
flavor, while Zero
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
atomic::fence(Ordering::SeqCst); | |
let tail = self.tail.load(Ordering::Relaxed); |
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?