Skip to main content

ax_task/sync/
semaphore.rs

1//! FIFO counting semaphores with raw IRQ-safe metadata and ordinary task waits.
2
3use alloc::{collections::VecDeque, sync::Arc};
4use core::sync::atomic::{AtomicBool, Ordering};
5
6use super::RawSpinLock;
7use crate::{
8    runtime::task_runtime,
9    thread::{
10        TaskError, ThreadWakeHandle,
11        current::{self, CurrentParkStart},
12    },
13    time::MonotonicDeadline,
14};
15
16/// A counting semaphore without priority inheritance or task ownership.
17///
18/// `up` and `try_down` are IRQ-safe. Blocking acquisition is task-only and
19/// grants queued waiters in FIFO order without allowing a new caller to steal
20/// a published grant.
21pub struct Semaphore {
22    state: RawSpinLock<SemaphoreState>,
23}
24
25struct SemaphoreState {
26    permits: usize,
27    waiters: VecDeque<Arc<SemaphoreWaiter>>,
28}
29struct SemaphoreWaiter {
30    wake: ThreadWakeHandle,
31    granted: AtomicBool,
32    handoff_complete: AtomicBool,
33}
34
35/// Failure to acquire or release a counting semaphore.
36#[derive(Debug, thiserror::Error)]
37pub enum SemaphoreError {
38    /// The task's interruption predicate became true before a grant.
39    #[error("semaphore wait interrupted")]
40    Interrupted,
41    /// The absolute deadline expired before a grant.
42    #[error("semaphore wait timed out")]
43    TimedOut,
44    /// The available permit count cannot be incremented.
45    #[error("semaphore permit count overflow")]
46    CountOverflow,
47    /// The scheduler rejected the caller's context or park transaction.
48    #[error(transparent)]
49    Task(#[from] TaskError),
50}
51
52struct SemaphoreRegistration<'a> {
53    semaphore: &'a Semaphore,
54    waiter: Arc<SemaphoreWaiter>,
55    consumed: bool,
56}
57
58impl Semaphore {
59    /// Creates a semaphore with `permits` initially available grants.
60    pub const fn new(permits: usize) -> Self {
61        Self {
62            state: RawSpinLock::new(SemaphoreState {
63                permits,
64                waiters: VecDeque::new(),
65            }),
66        }
67    }
68
69    /// Consumes one available permit without blocking, including in IRQ context.
70    pub fn try_down(&self) -> bool {
71        let mut state = self.state.lock_irqsave();
72        if state.permits == 0 {
73            return false;
74        }
75        state.permits -= 1;
76        true
77    }
78
79    /// Releases a permit or directly grants the oldest queued waiter.
80    pub fn up(&self) -> Result<(), SemaphoreError> {
81        // Like Linux wake_q flushing, keep the producer executing until the
82        // post-lock wake and queue-reference release are both complete.
83        let _preempt = crate::runtime::lock::PreemptScope::enter();
84        let waiter = {
85            let mut state = self.state.lock_irqsave();
86            if let Some(waiter) = state.waiters.pop_front() {
87                waiter.granted.store(true, Ordering::Release);
88                Some(waiter)
89            } else {
90                state.permits = state
91                    .permits
92                    .checked_add(1)
93                    .ok_or(SemaphoreError::CountOverflow)?;
94                None
95            }
96        };
97        if let Some(waiter) = waiter {
98            // Borrow the pre-existing task-context wake reference. IRQ release
99            // neither clones nor destroys a ThreadWakeHandle.
100            waiter.wake.wake();
101            let _state = self.state.lock_irqsave();
102            waiter.handoff_complete.store(true, Ordering::Release);
103            // The receiver must observe completion under this same lock before
104            // dropping its registration, so this cannot be the final Arc.
105            drop(waiter);
106        }
107        Ok(())
108    }
109
110    /// Waits without interruption until one permit is granted.
111    pub fn down(&self) -> Result<(), SemaphoreError> {
112        self.acquire(None, || false)
113    }
114
115    /// Waits until a permit is granted or an ordinary wake observes interruption.
116    /// The interruption publisher must also wake this task.
117    pub fn down_interruptible(
118        &self,
119        interrupted: impl FnMut() -> bool,
120    ) -> Result<(), SemaphoreError> {
121        self.acquire(None, interrupted)
122    }
123
124    /// Waits for a permit until an absolute monotonic deadline.
125    pub fn down_until(&self, deadline: MonotonicDeadline) -> Result<(), SemaphoreError> {
126        self.acquire(Some(deadline), || false)
127    }
128
129    fn acquire(
130        &self,
131        deadline: Option<MonotonicDeadline>,
132        mut interrupted: impl FnMut() -> bool,
133    ) -> Result<(), SemaphoreError> {
134        current::validate_blocking_context()?;
135        if self.try_down() {
136            return Ok(());
137        }
138        let mut registration = SemaphoreRegistration {
139            semaphore: self,
140            consumed: false,
141            waiter: Arc::new(SemaphoreWaiter {
142                wake: current::current_thread_handle()?.wake_handle(),
143                granted: AtomicBool::new(false),
144                handoff_complete: AtomicBool::new(false),
145            }),
146        };
147        if self.enqueue(&registration.waiter) {
148            return Ok(());
149        }
150        loop {
151            let interruption = interrupted();
152            let expired =
153                deadline.is_some_and(|deadline| task_runtime::monotonic_now().reached(deadline));
154            {
155                let mut state = self.state.lock_irqsave();
156                if registration.waiter.granted.load(Ordering::Acquire) {
157                    if registration.waiter.handoff_complete.load(Ordering::Acquire) {
158                        registration.consumed = true;
159                        return Ok(());
160                    }
161                    drop(state);
162                    core::hint::spin_loop();
163                    continue;
164                }
165                if interruption || expired {
166                    // Grant versus cancellation is decided by the same raw
167                    // lock; Drop must not race a late grant after this choice.
168                    registration.remove_locked(&mut state);
169                    return Err(if interruption {
170                        SemaphoreError::Interrupted
171                    } else {
172                        SemaphoreError::TimedOut
173                    });
174                }
175            }
176            let CurrentParkStart::Prepared(mut park) = current::begin_current_park()? else {
177                continue;
178            };
179            let granted = {
180                let _state = self.state.lock_irqsave();
181                registration.waiter.granted.load(Ordering::Acquire)
182            };
183            if granted {
184                park.cancel()?;
185                continue;
186            }
187            if let Some(deadline) = deadline
188                && let Err(error) = arm_wait_deadline(&mut park, deadline)
189            {
190                park.cancel()?;
191                return Err(error.into());
192            }
193            park.commit()?;
194        }
195    }
196
197    /// Returns true if a permit became available before registration.
198    fn enqueue(&self, waiter: &Arc<SemaphoreWaiter>) -> bool {
199        let mut replacement = VecDeque::new();
200        loop {
201            let mut state = self.state.lock_irqsave();
202            if state.permits != 0 {
203                state.permits -= 1;
204                return true;
205            }
206            if state.waiters.len() < state.waiters.capacity() {
207                state.waiters.push_back(Arc::clone(waiter));
208                return false;
209            }
210            if replacement.capacity() > state.waiters.len() {
211                replacement.extend(state.waiters.drain(..));
212                core::mem::swap(&mut state.waiters, &mut replacement);
213                state.waiters.push_back(Arc::clone(waiter));
214                return false;
215            }
216            let capacity = state
217                .waiters
218                .len()
219                .checked_add(1)
220                .expect("waiter count exhausted");
221            drop(state);
222            // Allocation and destruction of replacement storage are task-only.
223            replacement = VecDeque::with_capacity(capacity);
224        }
225    }
226}
227
228#[cfg(feature = "fault-injection")]
229static FAIL_NEXT_TIMER: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
230
231#[cfg(feature = "fault-injection")]
232/// Injects the next semaphore timer-registration failure for a real-runtime test.
233pub fn fail_next_semaphore_timer_registration() {
234    FAIL_NEXT_TIMER.store(
235        current::current_thread_id()
236            .expect("timer fault probe requires a task")
237            .as_u64(),
238        Ordering::Release,
239    );
240}
241
242fn arm_wait_deadline(
243    park: &mut current::PreparedCurrentPark,
244    deadline: MonotonicDeadline,
245) -> Result<(), TaskError> {
246    #[cfg(feature = "fault-injection")]
247    if FAIL_NEXT_TIMER
248        .compare_exchange(
249            current::current_thread_id()?.as_u64(),
250            0,
251            Ordering::AcqRel,
252            Ordering::Acquire,
253        )
254        .is_ok()
255    {
256        return Err(TaskError::TimerCapacity);
257    }
258    park.arm_deadline(deadline)
259}
260
261impl SemaphoreRegistration<'_> {
262    fn remove_locked(&self, state: &mut SemaphoreState) {
263        if let Some(index) = state
264            .waiters
265            .iter()
266            .position(|waiter| Arc::ptr_eq(waiter, &self.waiter))
267        {
268            state.waiters.remove(index);
269        }
270    }
271}
272
273impl Drop for SemaphoreRegistration<'_> {
274    fn drop(&mut self) {
275        let restore = loop {
276            let mut state = self.semaphore.state.lock_irqsave();
277            self.remove_locked(&mut state);
278            let granted = self.waiter.granted.load(Ordering::Acquire);
279            if granted && !self.waiter.handoff_complete.load(Ordering::Acquire) {
280                drop(state);
281                core::hint::spin_loop();
282                continue;
283            }
284            break !self.consumed && granted;
285        };
286        if restore {
287            self.semaphore
288                .up()
289                .expect("a cancelled acquisition must return its reserved permit");
290        }
291    }
292}