sync: fix panic in Chan::recv_many when called with non-empty vector on closed channel (#7991)

`Chan::recv_many` intends to assert that no slots
have been consumed when exiting with `Ready` via
the `rx_closed` code path.

Instead of asserting no items were added to the
buffer, it asserted buffer emptiness, incorrectly
making assumptions about the provided buffer.

When `recv_many` was called on an empty channel
with idle semaphore after the receiver was closed,
the method would panic.

The branch coverage had been previously missing.

This changeset corrects the assertion
and adds tests covering the code path.

Fixes #7990.
This commit is contained in:
LeoniePhiline
2026-04-02 13:16:03 +02:00
committed by Alice Ryhl
parent f320197693
commit bf18ed452d
2 changed files with 29 additions and 9 deletions
+5 -9
View File
@@ -306,13 +306,11 @@ impl<T, S: Semaphore> Rx<T, S> {
return Ready(Some(value));
}
Some(Read::Closed) => {
// TODO: This check may not be required as it most
// likely can only return `true` at this point. A
// channel is closed when all tx handles are
// A channel is closed when all tx handles are
// dropped. Dropping a tx handle releases memory,
// which ensures that if dropping the tx handle is
// visible, then all messages sent are also visible.
assert!(self.inner.semaphore.is_idle());
debug_assert!(self.inner.semaphore.is_idle());
coop.made_progress();
return Ready(None);
}
@@ -380,13 +378,11 @@ impl<T, S: Semaphore> Rx<T, S> {
if number_added > 0 {
self.inner.semaphore.add_permits(number_added);
}
// TODO: This check may not be required as it most
// likely can only return `true` at this point. A
// channel is closed when all tx handles are
// A channel is closed when all tx handles are
// dropped. Dropping a tx handle releases memory,
// which ensures that if dropping the tx handle is
// visible, then all messages sent are also visible.
assert!(self.inner.semaphore.is_idle());
debug_assert!(self.inner.semaphore.is_idle());
coop.made_progress();
return Ready(number_added);
}
@@ -415,7 +411,7 @@ impl<T, S: Semaphore> Rx<T, S> {
try_recv!();
if rx_fields.rx_closed && self.inner.semaphore.is_idle() {
assert!(buffer.is_empty());
debug_assert_eq!(buffer.len(), initial_length);
coop.made_progress();
Ready(0usize)
} else {
+24
View File
@@ -358,6 +358,30 @@ async fn send_recv_many_unbounded_capacity() {
assert_eq!(expected, buffer);
}
#[maybe_tokio_test]
async fn recv_many_with_non_empty_buffer_bounded_rx_closed_and_idle() {
let (_tx, mut rx) = mpsc::channel::<i32>(1);
let mut buffer: Vec<i32> = vec![1];
rx.close();
assert_eq!(0, rx.recv_many(&mut buffer, 1).await);
assert_eq!(vec![1], buffer);
}
#[maybe_tokio_test]
async fn recv_many_with_non_empty_buffer_unbounded_rx_closed_and_idle() {
let (_tx, mut rx) = mpsc::unbounded_channel::<i32>();
let mut buffer: Vec<i32> = vec![1];
rx.close();
assert_eq!(0, rx.recv_many(&mut buffer, 1).await);
assert_eq!(vec![1], buffer);
}
#[tokio::test]
#[cfg(feature = "full")]
async fn async_send_recv_unbounded() {