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}