Code change:
The unbounded channel implementation from can segfault due to what looks like a race condition when dropping channel senders and receivers. You can find a repro at Unfortunately it is not minimal at all, since it depends on the timely-dataflow crate, which is the actual channel user. I hope it is still self-contained enough that people familiar with the channel implementation can run it and use it as a starting point for debugging. Run the example like this: On some machines it will segfault after some time, e.g.: The segfault happens inside , as visible in the gdb backtrace below. The same segfault also happens with . You can reproduce this by reverting the second-to-last commit on the repro branch. I understand that is a port of , so that's not surprising. : I can reproduce the segfault on nightly as well. The segfault is not reproducible on all machines! We were able to reproduce it on: a c5.2xlarge EC2 instance running Ubuntu an AMD Threadripper Thinkpad running Debian an Intel i5-8250U Dell XPS 13 running Arch Linux It doesn't seem to reproduce on: an M1 MacBook Pro Also note that the repro is using more and more memory over time. That is because the different worker threads create and drop dataflows at different speeds and timely-dataflow keeps per-dataflow communication state (including channels) around until the last worker has dropped the dataflow. It is possible to avoid that by adding a that lets late workers catch up, but in my experience that also stops the segfault from happening. <details<summaryBacktrace</summary<p</p</details
Here are the results of some initial investigation I have been doing. I used , but I assume the results will apply to the std port as well: The segfault happens in . In the linked line, is a null pointer, and so is . It seems like there is an inconsistency between the channel's and . is pointing to nothing while points to a block. I observed the following variable assignments at the time of the segfault: If I change all the atomic orderings in to , the segfault still occurs. This suggests that the issue is not caused by the usage of too-weak orderings. If I add an after , it is triggered under the repro. This is where the sender is advancing and it seems plausible that this is the place where the inconsistency between and described above is introduced. I couldn't figure out why becomes a null pointer. The same method makes sure that the head is allocated previously, so some other thread must set it to null after the check.
cc
This suggests that the issue is not caused by the usage of too-weak orderings. I wouldn't be so quick to write off an ordering issue, the race condition could require stronger synchronization to fix (e.g. fences).
The segfault did occur (after about 5 minutes of runtime) on my Macbook Pro with an Intel Core i7 processor running macOS 13.3 and rustc 1.68.2. So whatever is causing this, it's not exclusive to Linux.
I have a feeling the issue is to do with a CAS failure when updating the tail not synchronizing with the update to head. If the thread that actually makes the update is preempted, the second thread might continue with the assumption that head has been updated, which might matter if it quickly writes to the entire block and the channel is disconnected before the head is ever touched. I haven't verified if this is possible, but it would be extremely rare if it is, which explains why this issue hasn't been hit before.

Related discussion:
text: let mut head = self.head.index.load(Ordering::Acquire); let mut block = self.head.block.load(Ordering::Acquire); <ins> // If we're going to be dropping messages we need to synchronize with initialization if head >> SHIFT != tail >> SHIFT { // The block can be null here only if a sender is in the process of initializing the // channel while another sender managed to send a message by inserting it into the // semi-initialized channel and advanced the tail. // In that case, just wait until it gets initialized. while block.is_null() { backoff.spin_heavy(); block = self.head.block.load(Ordering::Acquire); } } </ins> unsafe { // Drop all messages between head and tail and deallocate the heap-allocated blocks. while head >> SHIFT != tail >> SHIFT {