subetha_cxc/
blocking_semaphore.rs1use std::path::{Path, PathBuf};
24use std::sync::Arc;
25use std::time::{Duration, Instant};
26
27use crate::cross_process_waker::{
28 CrossProcessWaker, MAX_WAITERS_DEFAULT, WakerError,
29};
30use crate::shared_semaphore::{SemaphoreError, SharedSemaphore};
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum BlockingSemaphoreError {
35 Semaphore(SemaphoreError),
36 Waker(WakerError),
37 Timeout,
38}
39
40impl From<SemaphoreError> for BlockingSemaphoreError {
41 fn from(e: SemaphoreError) -> Self { Self::Semaphore(e) }
42}
43impl From<WakerError> for BlockingSemaphoreError {
44 fn from(e: WakerError) -> Self {
45 match e {
46 WakerError::Timeout => Self::Timeout,
47 other => Self::Waker(other),
48 }
49 }
50}
51
52pub struct BlockingSemaphore {
54 inner: Arc<SharedSemaphore>,
55 waker: Arc<CrossProcessWaker>,
56}
57
58const PRE_PARK_SPIN: u32 = 32;
59
60impl BlockingSemaphore {
61 pub fn create(
67 base_path: impl AsRef<Path>,
68 max_permits: u32,
69 init_permits: u32,
70 ) -> Result<Self, BlockingSemaphoreError> {
71 let base = base_path.as_ref();
72 let inner = SharedSemaphore::create(base, init_permits, max_permits)?;
73 let waker = CrossProcessWaker::create(waker_path(base), MAX_WAITERS_DEFAULT)?;
74 Ok(Self {
75 inner: Arc::new(inner),
76 waker: Arc::new(waker),
77 })
78 }
79
80 pub fn open(
82 base_path: impl AsRef<Path>,
83 expected_max_permits: u32,
84 ) -> Result<Self, BlockingSemaphoreError> {
85 let base = base_path.as_ref();
86 let inner = SharedSemaphore::open(base, expected_max_permits)?;
87 let waker = CrossProcessWaker::open(waker_path(base), MAX_WAITERS_DEFAULT)?;
88 Ok(Self {
89 inner: Arc::new(inner),
90 waker: Arc::new(waker),
91 })
92 }
93
94 pub fn try_acquire(&self) -> Result<BlockingPermit<'_>, BlockingSemaphoreError> {
96 match self.inner.try_acquire() {
97 Ok(p) => {
98 std::mem::forget(p);
103 Ok(BlockingPermit { sem: self })
104 }
105 Err(SemaphoreError::WouldBlock) => Err(BlockingSemaphoreError::Semaphore(SemaphoreError::WouldBlock)),
106 Err(e) => Err(BlockingSemaphoreError::Semaphore(e)),
107 }
108 }
109
110 pub fn acquire_park(&self) -> Result<BlockingPermit<'_>, BlockingSemaphoreError> {
114 loop {
115 if let Ok(p) = self.inner.try_acquire() {
116 std::mem::forget(p);
117 return Ok(BlockingPermit { sem: self });
118 }
119 for _ in 0..PRE_PARK_SPIN {
120 if let Ok(p) = self.inner.try_acquire() {
121 std::mem::forget(p);
122 return Ok(BlockingPermit { sem: self });
123 }
124 std::hint::spin_loop();
125 }
126 self.inner.mark_waiter_entered();
130 let snapshot = self.inner.wakeup_generation();
131 let token = match self.waker.try_park(snapshot + 1) {
132 Ok(t) => t,
133 Err(e) => {
134 self.inner.mark_waiter_left();
135 return Err(BlockingSemaphoreError::from(e));
136 }
137 };
138 if let Ok(p) = self.inner.try_acquire() {
139 self.waker.release(token);
140 self.inner.mark_waiter_left();
141 std::mem::forget(p);
142 return Ok(BlockingPermit { sem: self });
143 }
144 let wait_res = self.waker.wait(token, None);
145 self.inner.mark_waiter_left();
146 wait_res?;
147 }
148 }
149
150 pub fn acquire_park_timeout(
153 &self,
154 timeout: Duration,
155 ) -> Result<BlockingPermit<'_>, BlockingSemaphoreError> {
156 let deadline = Instant::now() + timeout;
157 loop {
158 if let Ok(p) = self.inner.try_acquire() {
159 std::mem::forget(p);
160 return Ok(BlockingPermit { sem: self });
161 }
162 for _ in 0..PRE_PARK_SPIN {
163 if let Ok(p) = self.inner.try_acquire() {
164 std::mem::forget(p);
165 return Ok(BlockingPermit { sem: self });
166 }
167 std::hint::spin_loop();
168 }
169 self.inner.mark_waiter_entered();
170 let snapshot = self.inner.wakeup_generation();
171 let token = match self.waker.try_park(snapshot + 1) {
172 Ok(t) => t,
173 Err(e) => {
174 self.inner.mark_waiter_left();
175 return Err(BlockingSemaphoreError::from(e));
176 }
177 };
178 if let Ok(p) = self.inner.try_acquire() {
179 self.waker.release(token);
180 self.inner.mark_waiter_left();
181 std::mem::forget(p);
182 return Ok(BlockingPermit { sem: self });
183 }
184 let now = Instant::now();
185 if now >= deadline {
186 self.waker.release(token);
187 self.inner.mark_waiter_left();
188 return Err(BlockingSemaphoreError::Timeout);
189 }
190 let remaining = deadline - now;
191 let wait_res = self.waker.wait(token, Some(remaining));
192 self.inner.mark_waiter_left();
193 match wait_res {
194 Ok(()) => continue,
195 Err(WakerError::Timeout) => return Err(BlockingSemaphoreError::Timeout),
196 Err(e) => return Err(BlockingSemaphoreError::Waker(e)),
197 }
198 }
199 }
200
201 pub fn release(&self) -> Result<(), BlockingSemaphoreError> {
204 self.inner.release()?;
205 let new_gen = self.inner.wakeup_generation();
209 self.waker.wake_up_to(new_gen);
210 Ok(())
211 }
212
213 pub fn available(&self) -> u32 { self.inner.available() }
215
216 pub fn max_permits(&self) -> u32 { self.inner.max_permits() }
218
219 pub fn inner(&self) -> &Arc<SharedSemaphore> { &self.inner }
221}
222
223pub struct BlockingPermit<'a> {
225 sem: &'a BlockingSemaphore,
226}
227
228impl Drop for BlockingPermit<'_> {
229 fn drop(&mut self) {
230 if let Err(e) = self.sem.release() {
234 eprintln!("BlockingSemaphore: release failed in Drop: {e:?}");
235 }
236 }
237}
238
239fn waker_path(base: &Path) -> PathBuf {
240 let mut p = base.as_os_str().to_owned();
241 p.push(".waker.bin");
242 PathBuf::from(p)
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248 use std::sync::atomic::{AtomicU64, Ordering};
249 use std::thread;
250
251 fn fresh_base() -> PathBuf {
252 let dir = std::env::temp_dir();
253 static N: AtomicU64 = AtomicU64::new(0);
256 let n = N.fetch_add(1, Ordering::Relaxed);
257 dir.join(format!("subetha_bsem_test_{}_{}", std::process::id(), n))
258 }
259
260 fn cleanup(base: &Path) {
261 for suffix in [
262 ".count.bin", ".wakeup.bin", ".waiters.bin",
263 ".count.bin.hh.bin", ".count.bin.ring.bin",
264 ".wakeup.bin.hh.bin", ".wakeup.bin.ring.bin",
265 ".waiters.bin.hh.bin", ".waiters.bin.ring.bin",
266 ".hh.bin", ".ring.bin",
267 ".waker.bin",
268 ] {
269 let mut p = base.as_os_str().to_owned();
270 p.push(suffix);
271 drop(std::fs::remove_file(PathBuf::from(p)));
272 }
273 }
274
275 #[test]
276 fn try_acquire_succeeds_when_permits_available() {
277 let base = fresh_base();
278 cleanup(&base);
279 let sem = BlockingSemaphore::create(&base, 4, 4).expect("create");
280 let p = sem.try_acquire().expect("permit");
281 drop(p);
282 cleanup(&base);
283 }
284
285 #[test]
286 fn acquire_park_blocks_then_completes_on_release() {
287 let base = fresh_base();
288 cleanup(&base);
289 let sem = Arc::new(BlockingSemaphore::create(&base, 1, 1).expect("create"));
290 let p0 = sem.try_acquire().expect("permit-0");
291
292 let s2 = Arc::clone(&sem);
299 let t = thread::spawn(move || {
300 let _g = s2.acquire_park().expect("park-acquire");
301 Instant::now()
302 });
303 thread::sleep(Duration::from_millis(40));
304 let released_at = Instant::now();
305 drop(p0); let completed_at = t.join().unwrap();
307 assert!(completed_at >= released_at,
308 "acquire_park must not complete before the permit released");
309
310 cleanup(&base);
311 }
312
313 #[test]
314 fn acquire_park_timeout_returns_timeout() {
315 let base = fresh_base();
316 cleanup(&base);
317 let sem = BlockingSemaphore::create(&base, 1, 1).expect("create");
318 let _hold = sem.try_acquire().expect("hold");
319 let t0 = Instant::now();
320 let err = sem.acquire_park_timeout(Duration::from_millis(60));
321 assert!(matches!(err, Err(BlockingSemaphoreError::Timeout)));
322 assert!(t0.elapsed() >= Duration::from_millis(50));
323 cleanup(&base);
324 }
325}