Skip to main content

osiris/sync/
waiter.rs

1//! Single-consumer park/wake primitive: at most one thread parks per
2//! [`ParkedWaiter`] at a time. Producers call [`wake`] from IRQ context
3//! (lock-free); consumers call [`park`] / [`park_current`], or drive
4//! arming and parking themselves with [`arm`] / [`disarm`]. A second
5//! concurrent consumer is rejected with `EBUSY`.
6//!
7//! [`arm`]: ParkedWaiter::arm
8//! [`disarm`]: ParkedWaiter::disarm
9//! [`park`]: ParkedWaiter::park
10//! [`park_current`]: ParkedWaiter::park_current
11//! [`wake`]: ParkedWaiter::wake
12
13use crate::error::Result;
14use crate::sched::thread::{self, Id};
15use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
16
17// 0 is the idle thread's uid, which is never allowed to park.
18const UNARMED: usize = 0;
19
20pub struct ParkedWaiter {
21    uid: AtomicUsize,
22}
23
24impl ParkedWaiter {
25    pub const fn new() -> Self {
26        Self {
27            uid: AtomicUsize::new(UNARMED),
28        }
29    }
30
31    pub fn arm(&self, uid: usize) -> Result<()> {
32        if uid == UNARMED {
33            return Err(kerr!(EINVAL, "ParkedWaiter::arm requires non-zero uid"));
34        }
35        self.uid
36            .compare_exchange(UNARMED, uid, Ordering::Release, Ordering::Relaxed)
37            .map(|_| ())
38            .map_err(|_| kerr!(EBUSY, "ParkedWaiter already armed"))
39    }
40
41    pub fn disarm(&self) {
42        self.uid.store(UNARMED, Ordering::Release);
43    }
44
45    /// Park the calling thread; returns `EBUSY` if another consumer is
46    /// already parked here.
47    pub fn park_current(&self) -> Result<()> {
48        let uid = crate::sched::with(|s| s.current_uid())
49            .ok_or_else(|| kerr!(EINVAL, "park_current with no current thread"))?;
50        if uid == UNARMED {
51            return Err(kerr!(EINVAL, "idle thread cannot park"));
52        }
53        self.park(uid)
54    }
55
56    /// Park `uid`. Prefer [`park_current`](Self::park_current) unless
57    /// you already have the uid in hand.
58    pub fn park(&self, uid: usize) -> Result<()> {
59        // IRQs masked across arm + scheduler park so a wake firing
60        // in between can't kick a uid the scheduler hasn't yet
61        // recorded as sleeping.
62        crate::sync::atomic::irq_free(|| -> Result<()> {
63            self.arm(uid)?;
64            let tid = thread::UId::new(uid, thread::Id::new(0, crate::sched::task::UId::new(0)));
65            crate::sched::with(|s| {
66                if s.sleep_until(Some(tid), u64::MAX, crate::time::tick())
67                    .is_err()
68                {
69                    bug!("park with no current thread despite armed uid");
70                }
71            });
72            Ok(())
73        })?;
74        self.disarm();
75        Ok(())
76    }
77
78    /// Wake the parked thread, if any. IRQ-safe and lock-free.
79    pub fn wake(&self) {
80        let uid = self.uid.load(Ordering::Acquire);
81        if uid != UNARMED {
82            crate::sched::kick_thread(uid as u32);
83        }
84    }
85}