Skip to main content

axpoll_set/
lib.rs

1//! Shared and exclusive readiness queue implementation.
2
3#![no_std]
4#![deny(missing_docs)]
5
6extern crate alloc;
7
8use alloc::{boxed::Box, sync::Arc, vec::Vec};
9use core::{
10    sync::atomic::{AtomicBool, Ordering},
11    task::Waker,
12};
13
14use ax_lazyinit::OnceLock;
15use ax_sync::SpinLock;
16use axpoll::{IoEvents, PollRegistration, PollSource, RegistrationMode};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19struct RegistrationId(u64);
20
21struct Entry {
22    id: RegistrationId,
23    waker: Waker,
24    notified: Arc<AtomicBool>,
25    interests: IoEvents,
26    mode: RegistrationMode,
27}
28
29struct Inner {
30    entries: Vec<Entry>,
31    next_id: u64,
32    closed: bool,
33}
34
35impl Inner {
36    const fn new() -> Self {
37        Self {
38            entries: Vec::new(),
39            next_id: 0,
40            closed: false,
41        }
42    }
43
44    fn register(
45        &mut self,
46        waker: &Waker,
47        interests: IoEvents,
48        mode: RegistrationMode,
49    ) -> Option<(RegistrationId, Arc<AtomicBool>)> {
50        if self.closed || interests.is_empty() {
51            return None;
52        }
53        let id = RegistrationId(self.next_id);
54        let notified = Arc::new(AtomicBool::new(false));
55        self.next_id = self
56            .next_id
57            .checked_add(1)
58            .expect("poll registration ID space exhausted");
59        self.entries.push(Entry {
60            id,
61            waker: waker.clone(),
62            notified: notified.clone(),
63            interests,
64            mode,
65        });
66        Some((id, notified))
67    }
68
69    fn unregister(&mut self, id: RegistrationId) {
70        if let Some(index) = self.entries.iter().position(|entry| entry.id == id) {
71            self.entries.remove(index);
72        }
73    }
74
75    fn wake_boundary(&self) -> u64 {
76        self.next_id
77    }
78
79    fn take_next_matching(
80        &mut self,
81        ready: IoEvents,
82        boundary: u64,
83        exclusive_available: bool,
84    ) -> Option<Entry> {
85        let index = self.entries.iter().position(|entry| {
86            entry.id.0 < boundary
87                && entry.interests.intersects(ready)
88                && (entry.mode == RegistrationMode::Shared || exclusive_available)
89        })?;
90        self.entries[index].notified.store(true, Ordering::Release);
91        Some(self.entries.remove(index))
92    }
93
94    fn take_next_before(&mut self, boundary: u64) -> Option<Entry> {
95        let index = self
96            .entries
97            .iter()
98            .position(|entry| entry.id.0 < boundary)?;
99        self.entries[index].notified.store(true, Ordering::Release);
100        Some(self.entries.remove(index))
101    }
102}
103
104struct PollState(SpinLock<Inner>);
105
106impl PollState {
107    const fn new() -> Self {
108        Self(SpinLock::new(Inner::new()))
109    }
110
111    fn register(
112        &self,
113        waker: &Waker,
114        interests: IoEvents,
115        mode: RegistrationMode,
116    ) -> Option<(RegistrationId, Arc<AtomicBool>)> {
117        self.0.lock().register(waker, interests, mode)
118    }
119
120    fn unregister(&self, id: RegistrationId) {
121        self.0.lock().unregister(id);
122    }
123
124    fn wake_with(
125        &self,
126        ready: IoEvents,
127        mut exclusive_budget: usize,
128        wake: &mut impl FnMut(Waker),
129    ) -> usize {
130        let boundary = self.0.lock().wake_boundary();
131        let mut woke = 0;
132        loop {
133            let entry = self
134                .0
135                .lock()
136                .take_next_matching(ready, boundary, exclusive_budget != 0);
137            let Some(entry) = entry else {
138                return woke;
139            };
140            if entry.mode == RegistrationMode::Exclusive {
141                exclusive_budget -= 1;
142            }
143            wake(entry.waker);
144            woke += 1;
145        }
146    }
147
148    fn close(&self) {
149        let boundary = {
150            let mut inner = self.0.lock();
151            inner.closed = true;
152            inner.wake_boundary()
153        };
154        loop {
155            let entry = self.0.lock().take_next_before(boundary);
156            let Some(entry) = entry else {
157                return;
158            };
159            entry.waker.wake();
160        }
161    }
162}
163
164struct Registration {
165    state: Arc<PollState>,
166    id: RegistrationId,
167    notified: Arc<AtomicBool>,
168}
169
170impl PollRegistration for Registration {
171    fn was_notified(&self) -> bool {
172        self.notified.load(Ordering::Acquire)
173    }
174}
175
176impl Drop for Registration {
177    fn drop(&mut self) {
178        self.state.unregister(self.id);
179    }
180}
181
182/// A readiness source containing shared observers and exclusive consumers.
183pub struct PollSet(OnceLock<Arc<PollState>>);
184
185impl Default for PollSet {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191impl PollSet {
192    /// Creates an empty readiness source.
193    pub const fn new() -> Self {
194        Self(OnceLock::new())
195    }
196
197    fn state(&self) -> Arc<PollState> {
198        Arc::clone(self.0.call_once(|| Arc::new(PollState::new())))
199    }
200
201    /// Wakes every matching shared observer and one matching exclusive consumer.
202    ///
203    /// # Safety
204    ///
205    /// This method is task/deferred-context only. Readiness must be published
206    /// first through synchronization observed by the consumer's readiness
207    /// check, and the caller must not hold a lock that a waker may re-enter.
208    pub unsafe fn wake(&self, ready: IoEvents) -> usize {
209        let Some(state) = self.0.get() else {
210            return 0;
211        };
212        state.wake_with(ready, 1, &mut Waker::wake)
213    }
214
215    /// Wakes matching registrations through a caller-selected Waker policy.
216    ///
217    /// Every matching shared observer and one matching exclusive consumer is
218    /// removed and passed by value to `wake`. The callback runs after the
219    /// readiness-queue lock is released. This permits an OS adapter to attach
220    /// scheduler intent such as Linux `WF_SYNC` without coupling this generic
221    /// readiness crate to one task implementation.
222    ///
223    /// # Safety
224    ///
225    /// This method is task/deferred-context only. Readiness must be published
226    /// first through synchronization observed by the consumer's readiness
227    /// check, and the caller must not hold a lock that the callback may
228    /// re-enter.
229    pub unsafe fn wake_with(&self, ready: IoEvents, mut wake: impl FnMut(Waker)) -> usize {
230        let Some(state) = self.0.get() else {
231            return 0;
232        };
233        state.wake_with(ready, 1, &mut wake)
234    }
235
236    /// Wakes every matching shared observer and exclusive consumer.
237    ///
238    /// Use this only after publishing a permanent terminal transition.
239    ///
240    /// # Safety
241    ///
242    /// This method is task/deferred-context only. The caller must not hold a
243    /// lock that a waker may re-enter. The terminal state must be published
244    /// through synchronization observed by the consumer's readiness check.
245    pub unsafe fn wake_all(&self, ready: IoEvents) -> usize {
246        let Some(state) = self.0.get() else {
247            return 0;
248        };
249        state.wake_with(ready, usize::MAX, &mut Waker::wake)
250    }
251}
252
253impl PollSource for PollSet {
254    unsafe fn register(
255        &self,
256        waker: &Waker,
257        interests: IoEvents,
258        mode: RegistrationMode,
259    ) -> Option<Box<dyn PollRegistration>> {
260        let state = self.state();
261        state
262            .register(waker, interests, mode)
263            .map(|(id, notified)| {
264                let registration: Box<dyn PollRegistration> = Box::new(Registration {
265                    state,
266                    id,
267                    notified,
268                });
269                registration
270            })
271    }
272}
273
274impl Drop for PollSet {
275    fn drop(&mut self) {
276        if let Some(state) = self.0.get() {
277            state.close();
278        }
279    }
280}