From 3ea11e2a5fb4139ca21b441044d98994a2b126c5 Mon Sep 17 00:00:00 2001 From: dentiny Date: Wed, 8 Apr 2026 00:59:54 -0700 Subject: [PATCH] sync: fix semaphore reopens after forget (#8021) --- tokio/src/sync/batch_semaphore.rs | 2 +- tokio/tests/sync_semaphore.rs | 54 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tokio/src/sync/batch_semaphore.rs b/tokio/src/sync/batch_semaphore.rs index 8662af3bb..6447cefb4 100644 --- a/tokio/src/sync/batch_semaphore.rs +++ b/tokio/src/sync/batch_semaphore.rs @@ -384,7 +384,7 @@ impl Semaphore { let new = curr.saturating_sub(n); match self.permits.compare_exchange_weak( curr_bits, - new << Self::PERMIT_SHIFT, + (new << Self::PERMIT_SHIFT) | (curr_bits & Self::CLOSED), AcqRel, Acquire, ) { diff --git a/tokio/tests/sync_semaphore.rs b/tokio/tests/sync_semaphore.rs index ab4b316ce..d976f1e68 100644 --- a/tokio/tests/sync_semaphore.rs +++ b/tokio/tests/sync_semaphore.rs @@ -50,6 +50,31 @@ async fn add_permits() { j.await.unwrap(); } +#[test] +fn add_permits_open() { + for size in 0..4 { + for add in 0..4 { + let sem = Arc::new(Semaphore::new(size)); + sem.add_permits(add); + assert_eq!(sem.available_permits(), size + add); + assert!(!sem.is_closed()); + } + } +} + +#[test] +fn add_permits_closed() { + for size in 0..4 { + for add in 0..4 { + let sem = Arc::new(Semaphore::new(size)); + sem.close(); + sem.add_permits(add); + assert_eq!(sem.available_permits(), size + add); + assert!(sem.is_closed()); + } + } +} + #[test] fn forget() { let sem = Arc::new(Semaphore::new(1)); @@ -63,6 +88,35 @@ fn forget() { assert!(sem.try_acquire().is_err()); } +#[test] +fn forget_open() { + for size in 0..4 { + for sub in 0..4 { + let sem = Arc::new(Semaphore::new(size)); + let actual_sub = sem.forget_permits(sub); + let expected = size.saturating_sub(sub); + assert_eq!(sem.available_permits(), expected, "case: {size}-{sub}"); + assert_eq!(actual_sub, size - expected); + assert!(!sem.is_closed()); + } + } +} + +#[test] +fn forget_closed() { + for size in 0..4 { + for sub in 0..4 { + let sem = Arc::new(Semaphore::new(size)); + sem.close(); + let actual_sub = sem.forget_permits(sub); + let expected = size.saturating_sub(sub); + assert_eq!(sem.available_permits(), expected, "case: {size}-{sub}"); + assert_eq!(actual_sub, size - expected); + assert!(sem.is_closed()); + } + } +} + #[test] fn merge() { let sem = Arc::new(Semaphore::new(3));