ITADN

fix: use slot instead of trigger for async recv

#172Pull Requestacking-you 创建于 2026-01-12
A
acking-youcommented
# Fix async rendezvous channel deadlock (Issue #140) ## Problem Async receivers use `Hook::trigger` which has no slot to store messages. When a sender finds a waiting async receiver on a rendezvous channel (bounded(0)), it: 1. Calls `fire_send()` which returns `(Some(msg), signal)` because trigger has no slot 2. Puts the message into `chan.queue` 3. Fires the signal to wake the receiver 4. Returns `Ok(())` immediately This violates rendezvous semantics where the sender should block until the receiver actually takes the message. The premature return causes race conditions and deadlocks in high-throughput scenarios. ## Root Cause In `src/async.rs`, `RecvFut::poll_inner` creates a trigger hook: ```rust || Hook::trigger(AsyncSignal::new(cx, stream)) ``` `Hook::trigger` has no slot (`self.0 = None`), so when `fire_send` is called: ```rust pub fn fire_send(&self, msg: T) -> (Option<T>, &S) { let ret = match self.lock() { Some(mut lock) => { *lock = Some(msg); None } None => Some(msg), // trigger returns Some(msg) }; (ret, self.signal()) } ``` The message is returned back and placed in the queue instead of being delivered directly to the receiver. ## Fix Change async receivers to use `Hook::slot` instead of `Hook::trigger`: ```rust // Before || Hook::trigger(AsyncSignal::new(cx, stream)) // After || Hook::slot(None, AsyncSignal::new(cx, stream)) ``` And check the slot first when polling: ```rust if let Some(hook) = self.hook.as_ref() { // Check if message was delivered directly to our slot if let Some(msg) = hook.try_take() { return Poll::Ready(Ok(msg)); } // ... rest of the logic } ``` ## Reproduction ```rust use flume::bounded; use std::time::{Duration, Instant}; const MESSAGES: usize = 1_000_000; const THREADS: usize = 4; #[tokio::main] async fn main() { // Run MPMC test repeatedly (simulating benchmark) loop { let (tx, rx) = bounded::<usize>(0); let mut handles = Vec::new(); for _ in 0..THREADS { let tx = tx.clone(); handles.push(tokio::spawn(async move { for i in 1..MESSAGES / THREADS + 1 { tx.send_async(i).await.unwrap(); } })); } for _ in 0..THREADS { let rx = rx.clone(); handles.push(tokio::spawn(async move { for _ in 0..MESSAGES / THREADS { rx.recv_async().await.unwrap(); } })); } for h in handles { h.await.unwrap(); } // Deadlocks after 1-2 iterations without the fix } } ``` ## Testing - All existing tests pass - The reproduction case completes successfully with the fix - Benchmark `flume-async` runs to completion ## Notes - Zero-sized types (ZST) may not trigger this bug due to compiler optimizations - The bug is more likely to manifest in repeated runs or high-throughput scenarios - This is related to Issue #140
合并状态:未合并 1 条评论