platform/
semaphore.rs

1// SPDX-FileCopyrightText: 2025 Jens Pitkänen <jens.pitkanen@helsinki.fi>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5/// A trait to implement a semaphore object.
6pub trait SemaphoreImpl: Sync {
7    /// Increments the semaphore.
8    fn increment(&self);
9    /// Decrements the semaphore, possible waiting for an increment if the
10    /// internal counter is already at zero.
11    ///
12    /// Single-threaded platforms can implement this as a no-op, so this might
13    /// never wait. Any functionality making use of this function should make
14    /// sure to check that any resource being synchronized with this semaphore
15    /// is actually in the expected state, and panic or otherwise error out if
16    /// it's not.
17    fn decrement(&self);
18}
19
20struct SingleThreadedSemaphore;
21impl SemaphoreImpl for SingleThreadedSemaphore {
22    fn increment(&self) {}
23    fn decrement(&self) {}
24}
25
26/// Atomically counting semaphore for efficiently waiting on other threads,
27/// intended to be created by the platform implementation, making use of OS
28/// synchronization primitives.
29///
30/// On a single-threaded platform, these operations can be no-ops, because a
31/// decrement without a preceding increment would be a deadlock anyway. Users of
32/// the semaphore should expect this and probably panic if this happens.
33#[derive(Clone)]
34pub struct Semaphore {
35    semaphore_ptr: *const (dyn SemaphoreImpl + Sync),
36    drop_fn: Option<fn(*const (dyn SemaphoreImpl + Sync))>,
37}
38
39// Safety: Semaphore::single_threaded makes sure this struct is inert,
40// Semaphore::new has safety requirements to make sure this isn't an issue.
41unsafe impl Sync for Semaphore {}
42
43impl Semaphore {
44    /// Creates a semaphore from very raw parts. Intended to be used in a
45    /// platform implementation.
46    ///
47    /// `drop_fn` is called in Semaphore's drop implementation and
48    /// `semaphore_ptr` is passed in. The `semaphore_ptr` isn't used after that.
49    ///
50    /// ### Safety
51    ///
52    /// `semaphore_ptr` should be valid for the whole lifetime of the semaphore
53    /// (until drop).
54    pub unsafe fn new(
55        semaphore_ptr: *const (dyn SemaphoreImpl + Sync),
56        drop_fn: Option<fn(*const (dyn SemaphoreImpl + Sync))>,
57    ) -> Semaphore {
58        Semaphore {
59            semaphore_ptr,
60            drop_fn,
61        }
62    }
63
64    /// Creates a no-op semaphore. Fits single-threaded platforms — will cause
65    /// panics if used in multi-threaded ones.
66    pub fn single_threaded() -> Semaphore {
67        Semaphore {
68            semaphore_ptr: &SingleThreadedSemaphore,
69            drop_fn: None,
70        }
71    }
72
73    /// Increments the semaphore's count.
74    pub fn increment(&self) {
75        // Safety: the constructor requires the pointer to be valid to use for
76        // the whole lifetime of the semaphore.
77        unsafe { &(*self.semaphore_ptr) }.increment();
78    }
79
80    /// Waits until the count is positive, and then decrements the semaphore's
81    /// count.
82    ///
83    /// Allowed to wake up without a matching increment if the alternative is
84    /// deadlocking. So this being matched by an increment can't be depended on
85    /// for unsafe operations. However, it's fine to panic in such a case,
86    /// because it's a clear bug.
87    pub fn decrement(&self) {
88        // Safety: the constructor requires the pointer to be valid to use for
89        // the whole lifetime of the semaphore.
90        unsafe { &(*self.semaphore_ptr) }.decrement();
91    }
92}
93
94impl Drop for Semaphore {
95    fn drop(&mut self) {
96        if let Some(drop_fn) = self.drop_fn {
97            drop_fn(self.semaphore_ptr);
98        }
99    }
100}