Skip to main content

osal_rs/async_primitives/
waker_slot.rs

1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Single-slot, atomic waker storage.
22//!
23//! [`WakerSlot`] stores at most one [`Waker`] at a time using an
24//! `AtomicPtr<Waker>`.  It is `Send + Sync` and does not require an OS mutex.
25//!
26//! # Why not `os::Mutex<Option<Waker>>`?
27//!
28//! Using an OS mutex to protect the waker inside an async primitive would
29//! create a circular dependency: the async mutex *itself* would need a waker
30//! to notify waiters, but storing that waker would require the mutex.
31//! `AtomicPtr` avoids this entirely.
32
33use core::sync::atomic::{AtomicPtr, Ordering};
34use core::task::Waker;
35
36use alloc::boxed::Box;
37
38/// A single-slot, thread-safe storage for a [`Waker`].
39///
40/// At most one waker is stored at any given time.  A new call to [`store`]
41/// replaces the previous waker (the old one is dropped).
42///
43/// [`store`]: WakerSlot::store
44pub struct WakerSlot(AtomicPtr<Waker>);
45
46impl WakerSlot {
47    /// Creates an empty slot.
48    #[inline]
49    pub const fn new() -> Self {
50        Self(AtomicPtr::new(core::ptr::null_mut()))
51    }
52
53    /// Stores `waker`, replacing and dropping any previously stored waker.
54    ///
55    /// This is called by async futures from their `poll` implementation.
56    pub fn store(&self, waker: &Waker) {
57        let new_ptr = Box::into_raw(Box::new(waker.clone()));
58        let old_ptr = self.0.swap(new_ptr, Ordering::AcqRel);
59        if !old_ptr.is_null() {
60            // SAFETY: old_ptr was created by Box::into_raw in a previous store.
61            unsafe { drop(Box::from_raw(old_ptr)) };
62        }
63    }
64
65    /// Wakes and removes the stored waker, if any.
66    ///
67    /// This is called by signalling paths (e.g. after posting to a queue)
68    /// to notify the task waiting on the corresponding future.
69    pub fn wake(&self) {
70        let ptr = self.0.swap(core::ptr::null_mut(), Ordering::AcqRel);
71        if !ptr.is_null() {
72            // SAFETY: ptr was created by Box::into_raw in store().
73            let waker = unsafe { Box::from_raw(ptr) };
74            waker.wake();
75        }
76    }
77}
78
79impl Default for WakerSlot {
80    
81    #[inline]
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87// SAFETY: WakerSlot only holds a Box<Waker> behind an AtomicPtr; all
88// mutations go through swap which provides the necessary memory ordering.
89unsafe impl Send for WakerSlot {}
90unsafe impl Sync for WakerSlot {}
91
92impl Drop for WakerSlot {
93    fn drop(&mut self) {
94        // Clean up any waker that was never consumed.
95        let ptr = *self.0.get_mut();
96        if !ptr.is_null() {
97            unsafe { drop(Box::from_raw(ptr)) };
98        }
99    }
100}