Skip to content

Commit 8c600d0

Browse files
committed
Merge 'tokio-1.47.5' into 'tokio-1.51.x' (#8123)
2 parents 64834ec + 11bfc13 commit 8c600d0

10 files changed

Lines changed: 248 additions & 39 deletions

File tree

tokio/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,20 @@ The MSRV is increased to 1.71.
330330
[#7672]: https://github.com/tokio-rs/tokio/pull/7672
331331
[#7675]: https://github.com/tokio-rs/tokio/pull/7675
332332

333+
# 1.47.5 (May 7th, 2026)
334+
335+
### Fixed
336+
337+
* sync: fix underflow in mpsc channel `len()` ([#8062])
338+
* sync: notify receivers in mpsc `OwnedPermit::release()` method ([#8075])
339+
* sync: require that an `RwLock` has `max_readers != 0` ([#8076])
340+
* sync: return `Empty` from `try_recv()` when mpsc is closed with outstanding permits ([#8074])
341+
342+
[#8062]: https://github.com/tokio-rs/tokio/pull/8062
343+
[#8074]: https://github.com/tokio-rs/tokio/pull/8074
344+
[#8075]: https://github.com/tokio-rs/tokio/pull/8075
345+
[#8076]: https://github.com/tokio-rs/tokio/pull/8076
346+
333347
# 1.47.4 (April 2nd, 2026)
334348

335349
### Fixed

tokio/src/sync/mpsc/block.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,6 @@ impl<T> Block<T> {
220220
self.header.ready_slots.fetch_or(TX_CLOSED, Release);
221221
}
222222

223-
pub(crate) unsafe fn is_closed(&self) -> bool {
224-
let ready_bits = self.header.ready_slots.load(Acquire);
225-
is_tx_closed(ready_bits)
226-
}
227-
228223
/// Resets the block to a blank state. This enables reusing blocks in the
229224
/// channel.
230225
///

tokio/src/sync/mpsc/bounded.rs

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1853,14 +1853,12 @@ impl<T> OwnedPermit<T> {
18531853
///
18541854
/// [`Sender`]: Sender
18551855
pub fn release(mut self) -> Sender<T> {
1856-
use chan::Semaphore;
1857-
18581856
let chan = self.chan.take().unwrap_or_else(|| {
18591857
unreachable!("OwnedPermit channel is only taken when the permit is moved")
18601858
});
18611859

18621860
// Add the permit back to the semaphore
1863-
chan.semaphore().add_permit();
1861+
drop(Permit { chan: &chan });
18641862
Sender { chan }
18651863
}
18661864

@@ -1919,21 +1917,10 @@ impl<T> OwnedPermit<T> {
19191917

19201918
impl<T> Drop for OwnedPermit<T> {
19211919
fn drop(&mut self) {
1922-
use chan::Semaphore;
1923-
19241920
// Are we still holding onto the sender?
19251921
if let Some(chan) = self.chan.take() {
1926-
let semaphore = chan.semaphore();
1927-
1928-
// Add the permit back to the semaphore
1929-
semaphore.add_permit();
1930-
1931-
// If this `OwnedPermit` is holding the last sender for this
1932-
// channel, wake the receiver so that it can be notified that the
1933-
// channel is closed.
1934-
if semaphore.is_closed() && semaphore.is_idle() {
1935-
chan.wake_rx();
1936-
}
1922+
// Reuse Drop impl of non-owned Permit.
1923+
drop(Permit { chan: &chan });
19371924
}
19381925

19391926
// Otherwise, do nothing.

tokio/src/sync/mpsc/chan.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,9 @@ impl<T, S: Semaphore> Rx<T, S> {
436436
}
437437
TryPopResult::Closed => return Err(TryRecvError::Disconnected),
438438
// If close() was called, an empty queue should report Disconnected.
439-
TryPopResult::Empty if rx_fields.rx_closed => {
439+
TryPopResult::Empty
440+
if rx_fields.rx_closed && self.inner.semaphore.is_idle() =>
441+
{
440442
return Err(TryRecvError::Disconnected)
441443
}
442444
TryPopResult::Empty => return Err(TryRecvError::Empty),

tokio/src/sync/mpsc/list.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,6 @@ impl<T> Tx<T> {
239239
let _ = unsafe { Box::from_raw(block.as_ptr()) };
240240
}
241241
}
242-
243-
pub(crate) fn is_closed(&self) -> bool {
244-
let tail = self.block_tail.load(Acquire);
245-
246-
unsafe {
247-
let tail_block = &*tail;
248-
tail_block.is_closed()
249-
}
250-
}
251242
}
252243

253244
impl<T> fmt::Debug for Tx<T> {
@@ -271,11 +262,58 @@ impl<T> Rx<T> {
271262
self.len(tx) == 0
272263
}
273264

265+
// Guaranteed to return true if `slot_index` is the fake message sent on channel close.
266+
// Guaranteed to return false if `slot_index` is a fully sent message.
267+
//
268+
// For messages that are partially sent, may return either true or false.
269+
fn is_maybe_closed(&self, tx: &Tx<T>, slot_index: usize) -> bool {
270+
let start_index = block::start_index(slot_index);
271+
272+
let tail = tx.block_tail.load(Acquire);
273+
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not be
274+
// freed right now.
275+
let tail_ref = unsafe { &*tail };
276+
if tail_ref.is_at_index(start_index) {
277+
return !tail_ref.has_value(slot_index);
278+
}
279+
280+
// This method is optimized for checking whether the last value is present, so most of the
281+
// time it is in `block_tail`. However, this isn't always the case since it's possible
282+
// that the list was grown with an empty block, in which case `block_tail` points one block
283+
// too far. To handle this case, we walk the list from the head.
284+
let mut block_ptr = Some(self.head);
285+
286+
while let Some(block) = block_ptr {
287+
// SAFETY: Only the receiver frees blocks, so since we are the receiver, this will not
288+
// be freed right now.
289+
let block_ref = unsafe { block.as_ref() };
290+
if block_ref.is_at_index(start_index) {
291+
return !block_ref.has_value(slot_index);
292+
}
293+
block_ptr = block_ref.load_next(Acquire);
294+
}
295+
true
296+
}
297+
274298
pub(crate) fn len(&self, tx: &Tx<T>) -> usize {
275-
// When all the senders are dropped, there will be a last block in the tail position,
276-
// but it will be closed
277299
let tail_position = tx.tail_position.load(Acquire);
278-
tail_position - self.index - (tx.is_closed() as usize)
300+
let mut len = tail_position.wrapping_sub(self.index);
301+
debug_assert!(0 <= len as isize);
302+
if len == 0 {
303+
return 0;
304+
}
305+
// There are messages present in the queue. However, it's possible that the last message is
306+
// a fake "closed" message that we do not wish to count. To avoid counting it, we do not
307+
// count the last message if the ready bit is unset.
308+
//
309+
// Note that it is also possible for the ready bit to be unset on a normal message, but
310+
// this happens only if that message is currently being sent *right now* in parallel on
311+
// another thread. That is okay because it is optional to count messages that are currently
312+
// being sent.
313+
if self.is_maybe_closed(tx, tail_position.wrapping_sub(1)) {
314+
len -= 1;
315+
}
316+
len
279317
}
280318

281319
/// Pops the next value off the queue.

tokio/src/sync/mpsc/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,10 @@ pub mod error;
137137
/// This value must be a power of 2. It also must be smaller than the number of
138138
/// bits in `usize`.
139139
#[cfg(all(target_pointer_width = "64", not(loom)))]
140-
const BLOCK_CAP: usize = 32;
140+
pub(crate) const BLOCK_CAP: usize = 32;
141141

142142
#[cfg(all(not(target_pointer_width = "64"), not(loom)))]
143-
const BLOCK_CAP: usize = 16;
143+
pub(crate) const BLOCK_CAP: usize = 16;
144144

145145
#[cfg(loom)]
146-
const BLOCK_CAP: usize = 2;
146+
pub(crate) const BLOCK_CAP: usize = 2;

tokio/src/sync/rwlock.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,13 @@ impl<T: ?Sized> RwLock<T> {
265265
///
266266
/// # Panics
267267
///
268-
/// Panics if `max_reads` is more than `u32::MAX >> 3`.
268+
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
269269
#[track_caller]
270270
pub fn with_max_readers(value: T, max_reads: u32) -> RwLock<T>
271271
where
272272
T: Sized,
273273
{
274+
assert_ne!(max_reads, 0, "a RwLock may not be created with 0 readers");
274275
assert!(
275276
max_reads <= MAX_READS,
276277
"a RwLock may not be created with more than {MAX_READS} readers"
@@ -366,11 +367,16 @@ impl<T: ?Sized> RwLock<T> {
366367
///
367368
/// static LOCK: RwLock<i32> = RwLock::const_with_max_readers(5, 1024);
368369
/// ```
370+
///
371+
/// # Panics
372+
///
373+
/// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`.
369374
#[cfg(not(all(loom, test)))]
370375
pub const fn const_with_max_readers(value: T, max_reads: u32) -> RwLock<T>
371376
where
372377
T: Sized,
373378
{
379+
assert!(max_reads != 0, "a RwLock may not be created with 0 readers");
374380
assert!(max_reads <= MAX_READS);
375381

376382
RwLock {
@@ -773,6 +779,7 @@ impl<T: ?Sized> RwLock<T> {
773779
/// ```
774780
pub async fn write(&self) -> RwLockWriteGuard<'_, T> {
775781
let acquire_fut = async {
782+
debug_assert_ne!(self.mr, 0);
776783
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
777784
// The semaphore was closed. but, we never explicitly close it, and we have a
778785
// handle to it through the Arc, which means that this can never happen.
@@ -911,6 +918,7 @@ impl<T: ?Sized> RwLock<T> {
911918
let resource_span = self.resource_span.clone();
912919

913920
let acquire_fut = async {
921+
debug_assert_ne!(self.mr, 0);
914922
self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| {
915923
// The semaphore was closed. but, we never explicitly close it, and we have a
916924
// handle to it through the Arc, which means that this can never happen.
@@ -975,6 +983,7 @@ impl<T: ?Sized> RwLock<T> {
975983
/// # }
976984
/// ```
977985
pub fn try_write(&self) -> Result<RwLockWriteGuard<'_, T>, TryLockError> {
986+
debug_assert_ne!(self.mr, 0);
978987
match self.s.try_acquire(self.mr as usize) {
979988
Ok(permit) => permit,
980989
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),
@@ -1033,6 +1042,7 @@ impl<T: ?Sized> RwLock<T> {
10331042
/// # }
10341043
/// ```
10351044
pub fn try_write_owned(self: Arc<Self>) -> Result<OwnedRwLockWriteGuard<T>, TryLockError> {
1045+
debug_assert_ne!(self.mr, 0);
10361046
match self.s.try_acquire(self.mr as usize) {
10371047
Ok(permit) => permit,
10381048
Err(TryAcquireError::NoPermits) => return Err(TryLockError(())),

tokio/src/sync/tests/loom_mpsc.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::sync::mpsc;
1+
use crate::sync::mpsc::{self, BLOCK_CAP};
22

33
use loom::future::block_on;
44
use loom::sync::Arc;
@@ -222,3 +222,56 @@ fn nonempty_after_send() {
222222
join.join().unwrap();
223223
});
224224
}
225+
226+
#[test]
227+
fn is_empty_during_close() {
228+
loom::model(|| {
229+
let (tx, rx) = mpsc::channel::<()>(1);
230+
231+
let th1 = thread::spawn(move || {
232+
assert!(rx.is_empty());
233+
});
234+
235+
drop(tx);
236+
237+
th1.join().unwrap();
238+
});
239+
}
240+
241+
fn len_during_close_helper(n: usize) {
242+
loom::model(move || {
243+
let (tx, rx) = mpsc::channel::<()>(n + 1);
244+
245+
for _ in 0..n {
246+
tx.try_send(()).unwrap();
247+
}
248+
249+
let th1 = thread::spawn(move || {
250+
assert_eq!(rx.len(), n);
251+
});
252+
253+
drop(tx);
254+
255+
th1.join().unwrap();
256+
});
257+
}
258+
259+
#[test]
260+
fn len_during_close_0() {
261+
len_during_close_helper(0);
262+
}
263+
264+
#[test]
265+
fn len_during_close_1() {
266+
len_during_close_helper(1);
267+
}
268+
269+
#[test]
270+
fn len_during_close_block_cap() {
271+
len_during_close_helper(BLOCK_CAP);
272+
}
273+
274+
#[test]
275+
fn len_during_close_block_cap_plus_1() {
276+
len_during_close_helper(BLOCK_CAP + 1);
277+
}

0 commit comments

Comments
 (0)