From bf18ed452d6aae438e84ae008a01a74776abdc19 Mon Sep 17 00:00:00 2001 From: LeoniePhiline <22329650+LeoniePhiline@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:13:26 +0100 Subject: [PATCH 1/2] 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. --- tokio/src/sync/mpsc/chan.rs | 14 +++++--------- tokio/tests/sync_mpsc.rs | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/tokio/src/sync/mpsc/chan.rs b/tokio/src/sync/mpsc/chan.rs index 20e2c90d4..7f7f8b16d 100644 --- a/tokio/src/sync/mpsc/chan.rs +++ b/tokio/src/sync/mpsc/chan.rs @@ -306,13 +306,11 @@ impl Rx { 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 Rx { 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 Rx { 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 { diff --git a/tokio/tests/sync_mpsc.rs b/tokio/tests/sync_mpsc.rs index 3ebac7390..048b94eb5 100644 --- a/tokio/tests/sync_mpsc.rs +++ b/tokio/tests/sync_mpsc.rs @@ -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::(1); + + let mut buffer: Vec = 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::(); + + let mut buffer: Vec = 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() { From aa65d0d0b8ea6eec80985b9d231390f137493071 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 2 Apr 2026 14:12:06 +0200 Subject: [PATCH 2/2] chore: prepare Tokio v1.47.4 (#8002) --- Cargo.lock | 6 +++--- README.md | 2 +- tokio/CHANGELOG.md | 10 +++++++++- tokio/Cargo.toml | 2 +- tokio/README.md | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0634fc3b..d1a338d43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cast" @@ -1405,7 +1405,7 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.2" +version = "1.47.4" dependencies = [ "async-stream", "backtrace", diff --git a/README.md b/README.md index 14e812bc4..a0e2e47ae 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.47.3", features = ["full"] } +tokio = { version = "1.47.4", features = ["full"] } ``` Then, on your main.rs: diff --git a/tokio/CHANGELOG.md b/tokio/CHANGELOG.md index 197c2224f..128fbd0d4 100644 --- a/tokio/CHANGELOG.md +++ b/tokio/CHANGELOG.md @@ -1,4 +1,12 @@ -# 1.47.3 (Januar 3rd, 2026) +# 1.47.4 (April 2nd, 2026) + +### Fixed + +* sync: fix panic in `Chan::recv_many` when called with non-empty vector on closed channel ([#7991]) + +[#7991]: https://github.com/tokio-rs/tokio/pull/7991 + +# 1.47.3 (January 3rd, 2026) ### Fixed diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index d87ce00b3..e0e83a19d 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -6,7 +6,7 @@ name = "tokio" # - README.md # - Update CHANGELOG.md. # - Create "v1.x.y" git tag. -version = "1.47.3" +version = "1.47.4" edition = "2021" rust-version = "1.70" authors = ["Tokio Contributors "] diff --git a/tokio/README.md b/tokio/README.md index 14e812bc4..a0e2e47ae 100644 --- a/tokio/README.md +++ b/tokio/README.md @@ -56,7 +56,7 @@ Make sure you activated the full features of the tokio crate on Cargo.toml: ```toml [dependencies] -tokio = { version = "1.47.3", features = ["full"] } +tokio = { version = "1.47.4", features = ["full"] } ``` Then, on your main.rs: