Skip to content

Race condition when working with a disconnected channel #838

@alygin

Description

@alygin

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:

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions