1#![allow(
2 clippy::not_unsafe_ptr_arg_deref,
3 reason = "Semaphore helpers intentionally mirror OS handle and pointer APIs."
4)]
5#![allow(
6 clippy::result_unit_err,
7 reason = "These helpers preserve the existing host-facing error surface."
8)]
9
10#[cfg(unix)]
11use alloc::ffi::CString;
12#[cfg(windows)]
13use std::io;
14
15#[cfg(unix)]
16use libc::sem_t;
17#[cfg(unix)]
18use nix::errno::Errno;
19
20#[cfg(unix)]
21#[repr(transparent)]
22#[derive(Debug)]
23pub struct SemHandle {
24 raw: *mut sem_t,
25}
26
27#[cfg(unix)]
30unsafe impl Send for SemHandle {}
31#[cfg(unix)]
32unsafe impl Sync for SemHandle {}
33
34#[cfg(unix)]
35#[derive(Copy, Clone, Debug, Eq, PartialEq)]
36pub enum SemError {
37 WouldBlock,
38 TimedOut,
39 Interrupted,
40 AlreadyExists,
41 NotFound,
42 InvalidInput,
43 InteriorNul,
44 Other(i32),
45}
46
47#[cfg(unix)]
48impl SemError {
49 const fn from_errno(err: Errno) -> Self {
50 match err {
51 Errno::EAGAIN => Self::WouldBlock,
52 Errno::ETIMEDOUT => Self::TimedOut,
53 Errno::EINTR => Self::Interrupted,
54 Errno::EEXIST => Self::AlreadyExists,
55 Errno::ENOENT => Self::NotFound,
56 Errno::EINVAL => Self::InvalidInput,
57 other => Self::Other(other as i32),
58 }
59 }
60
61 pub const fn raw_os_error(self) -> i32 {
62 match self {
63 Self::WouldBlock => Errno::EAGAIN as i32,
64 Self::TimedOut => Errno::ETIMEDOUT as i32,
65 Self::Interrupted => Errno::EINTR as i32,
66 Self::AlreadyExists => Errno::EEXIST as i32,
67 Self::NotFound => Errno::ENOENT as i32,
68 Self::InvalidInput | Self::InteriorNul => Errno::EINVAL as i32,
69 Self::Other(code) => code,
70 }
71 }
72
73 pub fn description(self) -> String {
74 Errno::from_raw(self.raw_os_error()).desc().to_owned()
75 }
76}
77
78#[cfg(unix)]
79#[derive(Copy, Clone, Debug, Eq, PartialEq)]
80pub enum TryAcquireStatus {
81 Acquired,
82 WouldBlock,
83 Interrupted,
84 Error(SemError),
85}
86
87#[cfg(unix)]
88#[derive(Copy, Clone, Debug, Eq, PartialEq)]
89pub enum WaitStatus {
90 Acquired,
91 TimedOut,
92 Interrupted,
93 Error(SemError),
94}
95
96#[cfg(unix)]
97#[derive(Copy, Clone, Debug)]
98pub struct Deadline {
99 spec: libc::timespec,
100}
101
102#[cfg(windows)]
103use windows_sys::Win32::{
104 Foundation::{
105 CloseHandle, ERROR_TOO_MANY_POSTS, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WAIT_FAILED,
106 WAIT_OBJECT_0, WAIT_TIMEOUT,
107 },
108 Networking::WinSock::{SOCKET, WSAGetLastError, closesocket, recv, send},
109 System::Threading::{
110 CreateSemaphoreW, GetCurrentThreadId, INFINITE, ReleaseSemaphore, WaitForSingleObjectEx,
111 },
112};
113
114#[cfg(windows)]
115pub type RawHandle = HANDLE;
116#[cfg(windows)]
117pub type RawSocket = SOCKET;
118#[cfg(windows)]
119pub const INFINITE_TIMEOUT: u32 = INFINITE;
120
121#[cfg(windows)]
122#[repr(transparent)]
123#[derive(Debug)]
124pub struct SemHandle {
125 raw: HANDLE,
126}
127
128#[cfg(windows)]
129unsafe impl Send for SemHandle {}
130#[cfg(windows)]
131unsafe impl Sync for SemHandle {}
132
133#[cfg(unix)]
134impl SemHandle {
135 pub fn create(
136 name: &str,
137 value: u32,
138 unlink: bool,
139 ) -> Result<(Self, Option<String>), SemError> {
140 let cname = semaphore_name(name)?;
141 let raw = unsafe {
142 libc::sem_open(
143 cname.as_ptr(),
144 crate::os::O_CREAT | crate::os::O_EXCL,
145 0o600,
146 value,
147 )
148 };
149 if raw == libc::SEM_FAILED {
150 return Err(SemError::from_errno(Errno::last()));
151 }
152 if unlink {
153 if unsafe { libc::sem_unlink(cname.as_ptr()) } != 0 {
154 let err = SemError::from_errno(Errno::last());
155 unsafe {
156 libc::sem_close(raw);
157 }
158 Err(err)
159 } else {
160 Ok((Self { raw }, None))
161 }
162 } else {
163 Ok((Self { raw }, Some(name.to_owned())))
164 }
165 }
166
167 pub fn open_existing(name: &str) -> Result<Self, SemError> {
168 let cname = semaphore_name(name)?;
169 let raw = unsafe { libc::sem_open(cname.as_ptr(), 0) };
170 if raw == libc::SEM_FAILED {
171 Err(SemError::from_errno(Errno::last()))
172 } else {
173 Ok(Self { raw })
174 }
175 }
176
177 #[inline]
178 pub fn as_handle_int(&self) -> isize {
179 self.raw as isize
180 }
181
182 #[inline]
189 pub const unsafe fn from_raw(raw: *mut sem_t) -> Self {
190 Self { raw }
191 }
192
193 #[inline]
194 pub fn trywait(&self) -> TryAcquireStatus {
195 sem_trywait_status(self.raw)
196 }
197
198 #[inline]
199 pub fn post(&self) -> Result<(), SemError> {
200 sem_post(self.raw)
201 }
202
203 #[inline]
204 pub fn wait(&self, deadline: Option<&Deadline>) -> WaitStatus {
205 sem_wait_status(self.raw, deadline.map(|deadline| &deadline.spec))
206 }
207
208 #[cfg(not(target_vendor = "apple"))]
209 #[inline]
210 pub fn value(&self) -> Result<i32, SemError> {
211 unsafe { get_semaphore_value(self.raw) }
213 }
214
215 #[cfg(target_vendor = "apple")]
216 #[inline]
217 pub fn poll_wait_step(
218 &self,
219 deadline: &Deadline,
220 delay: u64,
221 ) -> Result<PollWaitStep, SemError> {
222 sem_timedwait_poll_step(self.raw, &deadline.spec, delay)
223 }
224}
225
226#[cfg(windows)]
227impl SemHandle {
228 pub fn create(value: i32, maxvalue: i32) -> io::Result<Self> {
229 use crate::windows::CheckWin32Handle;
230 let handle =
231 unsafe { CreateSemaphoreW(core::ptr::null(), value, maxvalue, core::ptr::null()) }
232 .check_nonnull()?;
233 Ok(Self { raw: handle })
234 }
235
236 #[inline]
237 pub fn from_raw(raw: HANDLE) -> Self {
238 Self { raw }
239 }
240
241 #[inline]
242 pub fn as_handle_int(&self) -> isize {
243 self.raw as isize
244 }
245
246 #[inline]
247 pub fn as_raw(&self) -> HANDLE {
248 self.raw
249 }
250
251 #[inline]
252 pub fn wait(&self, timeout_ms: u32) -> u32 {
253 wait_for_single_object(self.raw, timeout_ms)
254 }
255
256 #[inline]
257 pub fn release(&self) -> Result<(), u32> {
258 release_semaphore(self.raw)
259 }
260
261 #[inline]
262 pub fn value(&self) -> Result<i32, ()> {
263 get_semaphore_value(self.raw)
264 }
265}
266
267#[cfg(unix)]
268impl Drop for SemHandle {
269 fn drop(&mut self) {
270 if !self.raw.is_null() {
271 unsafe {
272 libc::sem_close(self.raw);
273 }
274 }
275 }
276}
277
278#[cfg(windows)]
279impl Drop for SemHandle {
280 fn drop(&mut self) {
281 if self.raw != 0 as HANDLE && self.raw != INVALID_HANDLE_VALUE {
282 unsafe {
283 CloseHandle(self.raw);
284 }
285 }
286 }
287}
288
289#[cfg(unix)]
290#[inline]
291pub fn current_thread_id() -> u64 {
292 unsafe { libc::pthread_self() as u64 }
293}
294
295#[cfg(windows)]
296#[inline]
297pub fn current_thread_id() -> u32 {
298 unsafe { GetCurrentThreadId() }
299}
300
301#[cfg(windows)]
302#[inline]
303pub fn wait_for_single_object(handle: HANDLE, timeout_ms: u32) -> u32 {
304 unsafe { WaitForSingleObjectEx(handle, timeout_ms, 0) }
305}
306
307#[cfg(windows)]
308#[inline]
309pub fn wait_object_0() -> u32 {
310 WAIT_OBJECT_0
311}
312
313#[cfg(windows)]
314#[inline]
315pub fn wait_timeout() -> u32 {
316 WAIT_TIMEOUT
317}
318
319#[cfg(windows)]
320#[inline]
321pub fn close_socket(socket: SOCKET) -> io::Result<()> {
322 let res = unsafe { closesocket(socket) };
323 if res != 0 {
324 Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() }))
325 } else {
326 Ok(())
327 }
328}
329
330#[cfg(windows)]
331pub fn recv_socket(socket: SOCKET, size: usize) -> io::Result<Vec<u8>> {
332 let len = i32::try_from(size).map_err(|_| {
333 io::Error::new(io::ErrorKind::InvalidInput, "socket receive size too large")
334 })?;
335 let mut buf = vec![0u8; size];
336 let n_read = unsafe { recv(socket, buf.as_mut_ptr() as *mut _, len, 0) };
337 if n_read < 0 {
338 Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() }))
339 } else {
340 buf.truncate(n_read as usize);
341 Ok(buf)
342 }
343}
344
345#[cfg(windows)]
346pub fn send_socket(socket: SOCKET, buf: &[u8]) -> io::Result<i32> {
347 let len = i32::try_from(buf.len())
348 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "socket send buffer too large"))?;
349 let ret = unsafe { send(socket, buf.as_ptr() as *const _, len, 0) };
350 if ret < 0 {
351 Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() }))
352 } else {
353 Ok(ret)
354 }
355}
356
357#[cfg(windows)]
358#[inline]
359pub fn wait_failed() -> u32 {
360 WAIT_FAILED
361}
362
363#[cfg(windows)]
364pub fn release_semaphore(handle: HANDLE) -> Result<(), u32> {
365 if unsafe { ReleaseSemaphore(handle, 1, core::ptr::null_mut()) } == 0 {
366 Err(unsafe { GetLastError() })
367 } else {
368 Ok(())
369 }
370}
371
372#[cfg(windows)]
373pub fn get_semaphore_value(handle: HANDLE) -> Result<i32, ()> {
374 match wait_for_single_object(handle, 0) {
375 WAIT_OBJECT_0 => {
376 let mut previous: i32 = 0;
377 if unsafe { ReleaseSemaphore(handle, 1, &mut previous) } == 0 {
378 Err(())
379 } else {
380 Ok(previous + 1)
381 }
382 }
383 WAIT_TIMEOUT => Ok(0),
384 _ => Err(()),
385 }
386}
387
388#[cfg(windows)]
389#[inline]
390pub fn is_too_many_posts(err: u32) -> bool {
391 err == ERROR_TOO_MANY_POSTS
392}
393
394#[cfg(unix)]
395pub fn semaphore_name(name: &str) -> Result<CString, SemError> {
396 let mut full = String::with_capacity(name.len() + 2);
397 if !name.starts_with('/') {
398 full.push('/');
399 }
400 full.push_str(name);
401 CString::new(full).map_err(|_| SemError::InteriorNul)
402}
403
404#[cfg(unix)]
405pub fn sem_unlink(name: &str) -> Result<(), SemError> {
406 let cname = semaphore_name(name)?;
407 let res = unsafe { libc::sem_unlink(cname.as_ptr()) };
408 if res < 0 {
409 Err(SemError::from_errno(Errno::last()))
410 } else {
411 Ok(())
412 }
413}
414
415#[cfg(all(unix, not(target_vendor = "apple")))]
416unsafe fn get_semaphore_value(handle: *mut sem_t) -> Result<i32, SemError> {
421 let mut sval: libc::c_int = 0;
422 let res = unsafe { libc::sem_getvalue(handle, &mut sval) };
423 if res < 0 {
424 Err(SemError::from_errno(Errno::last()))
425 } else {
426 Ok(if sval < 0 { 0 } else { sval })
427 }
428}
429
430#[cfg(unix)]
431#[allow(clippy::not_unsafe_ptr_arg_deref)]
432fn sem_trywait_status(handle: *mut sem_t) -> TryAcquireStatus {
433 if unsafe { libc::sem_trywait(handle) } == 0 {
434 TryAcquireStatus::Acquired
435 } else {
436 match Errno::last() {
437 Errno::EAGAIN => TryAcquireStatus::WouldBlock,
438 Errno::EINTR => TryAcquireStatus::Interrupted,
439 err => TryAcquireStatus::Error(SemError::from_errno(err)),
440 }
441 }
442}
443
444#[cfg(unix)]
445#[allow(clippy::not_unsafe_ptr_arg_deref)]
446fn sem_post(handle: *mut sem_t) -> Result<(), SemError> {
447 if unsafe { libc::sem_post(handle) } < 0 {
448 Err(SemError::from_errno(Errno::last()))
449 } else {
450 Ok(())
451 }
452}
453
454#[cfg(unix)]
455pub fn sem_value_max() -> i32 {
456 let val = unsafe { libc::sysconf(libc::_SC_SEM_VALUE_MAX) };
457 if val < 0 || val > i32::MAX as libc::c_long {
458 i32::MAX
459 } else {
460 val as i32
461 }
462}
463
464#[cfg(unix)]
465fn gettimeofday() -> Result<libc::timeval, SemError> {
466 let mut tv = libc::timeval {
467 tv_sec: 0,
468 tv_usec: 0,
469 };
470 if unsafe { libc::gettimeofday(&mut tv, core::ptr::null_mut()) } < 0 {
471 Err(SemError::from_errno(Errno::last()))
472 } else {
473 Ok(tv)
474 }
475}
476
477#[cfg(unix)]
478pub fn deadline_from_timeout(timeout: f64) -> Result<Deadline, SemError> {
479 let timeout = if timeout < 0.0 { 0.0 } else { timeout };
480 if !timeout.is_finite() {
481 return Err(SemError::InvalidInput);
482 }
483 let tv = gettimeofday()?;
484 let sec_f64 = timeout.floor();
485 if sec_f64 > libc::time_t::MAX as f64 {
486 return Err(SemError::InvalidInput);
487 }
488 let sec = sec_f64 as libc::time_t;
489 let nsec = (1e9 * (timeout - sec as f64) + 0.5) as libc::c_long;
490 let tv_nsec = (tv.tv_usec as libc::c_long)
491 .checked_mul(1000)
492 .and_then(|base| base.checked_add(nsec))
493 .ok_or(SemError::InvalidInput)?;
494 let mut deadline = libc::timespec {
495 tv_sec: tv.tv_sec.checked_add(sec).ok_or(SemError::InvalidInput)?,
496 tv_nsec: tv_nsec as _,
497 };
498 deadline.tv_sec = deadline
499 .tv_sec
500 .checked_add((deadline.tv_nsec / 1_000_000_000) as libc::time_t)
501 .ok_or(SemError::InvalidInput)?;
502 deadline.tv_nsec %= 1_000_000_000;
503 Ok(Deadline { spec: deadline })
504}
505
506#[cfg(unix)]
507#[allow(clippy::not_unsafe_ptr_arg_deref)]
508fn sem_wait_status(handle: *mut sem_t, deadline: Option<&libc::timespec>) -> WaitStatus {
509 #[cfg(not(target_vendor = "apple"))]
510 if let Some(deadline) = deadline {
511 if unsafe { libc::sem_timedwait(handle, deadline) } == 0 {
512 WaitStatus::Acquired
513 } else {
514 match Errno::last() {
515 Errno::ETIMEDOUT => WaitStatus::TimedOut,
516 Errno::EINTR => WaitStatus::Interrupted,
517 err => WaitStatus::Error(SemError::from_errno(err)),
518 }
519 }
520 } else {
521 if unsafe { libc::sem_wait(handle) } == 0 {
522 WaitStatus::Acquired
523 } else {
524 match Errno::last() {
525 Errno::EINTR => WaitStatus::Interrupted,
526 err => WaitStatus::Error(SemError::from_errno(err)),
527 }
528 }
529 }
530
531 #[cfg(target_vendor = "apple")]
532 {
533 debug_assert!(deadline.is_none());
534 if unsafe { libc::sem_wait(handle) } == 0 {
535 WaitStatus::Acquired
536 } else {
537 match Errno::last() {
538 Errno::EINTR => WaitStatus::Interrupted,
539 err => WaitStatus::Error(SemError::from_errno(err)),
540 }
541 }
542 }
543}
544
545#[cfg(target_vendor = "apple")]
546pub enum PollWaitStep {
547 Acquired,
548 Timeout,
549 Continue(u64),
550}
551
552#[cfg(target_vendor = "apple")]
553#[allow(clippy::not_unsafe_ptr_arg_deref)]
554fn sem_timedwait_poll_step(
555 handle: *mut sem_t,
556 deadline: &libc::timespec,
557 delay: u64,
558) -> Result<PollWaitStep, SemError> {
559 if unsafe { libc::sem_trywait(handle) } == 0 {
560 return Ok(PollWaitStep::Acquired);
561 }
562 let err = Errno::last();
563 if err != Errno::EAGAIN {
564 return Err(SemError::from_errno(err));
565 }
566
567 let now = gettimeofday()?;
568 let deadline_usec = deadline.tv_sec * 1_000_000 + deadline.tv_nsec / 1000;
569 #[allow(clippy::unnecessary_cast)]
570 let now_usec = now.tv_sec as i64 * 1_000_000 + now.tv_usec as i64;
571 if now_usec >= deadline_usec {
572 return Ok(PollWaitStep::Timeout);
573 }
574
575 let difference = (deadline_usec - now_usec) as u64;
576 let mut delay = delay + 1000;
577 if delay > 20000 {
578 delay = 20000;
579 }
580 if delay > difference {
581 delay = difference;
582 }
583
584 let mut tv_delay = libc::timeval {
585 tv_sec: (delay / 1_000_000) as _,
586 tv_usec: (delay % 1_000_000) as _,
587 };
588 unsafe {
589 libc::select(
590 0,
591 core::ptr::null_mut(),
592 core::ptr::null_mut(),
593 core::ptr::null_mut(),
594 &mut tv_delay,
595 );
596 }
597 Ok(PollWaitStep::Continue(delay))
598}