Skip to main content

agentos_kernel/
poll.rs

1use crate::socket_table::SocketId;
2use event_listener::Event;
3use std::ops::{BitOr, BitOrAssign};
4use std::sync::{Arc, Condvar, Mutex, MutexGuard};
5use std::time::Duration;
6use web_time::Instant;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub struct PollEvents(u16);
10
11impl PollEvents {
12    pub const fn empty() -> Self {
13        Self(0)
14    }
15
16    pub const fn from_bits(bits: u16) -> Self {
17        Self(bits)
18    }
19
20    pub const fn bits(self) -> u16 {
21        self.0
22    }
23
24    pub const fn is_empty(self) -> bool {
25        self.0 == 0
26    }
27
28    pub const fn contains(self, other: Self) -> bool {
29        self.0 & other.0 == other.0
30    }
31
32    pub const fn intersects(self, other: Self) -> bool {
33        self.0 & other.0 != 0
34    }
35}
36
37impl BitOr for PollEvents {
38    type Output = Self;
39
40    fn bitor(self, rhs: Self) -> Self::Output {
41        Self(self.0 | rhs.0)
42    }
43}
44
45impl BitOrAssign for PollEvents {
46    fn bitor_assign(&mut self, rhs: Self) {
47        self.0 |= rhs.0;
48    }
49}
50
51pub const POLLIN: PollEvents = PollEvents(0x0001);
52pub const POLLOUT: PollEvents = PollEvents(0x0004);
53pub const POLLERR: PollEvents = PollEvents(0x0008);
54pub const POLLHUP: PollEvents = PollEvents(0x0010);
55pub const POLLNVAL: PollEvents = PollEvents(0x0020);
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct PollFd {
59    pub fd: u32,
60    pub events: PollEvents,
61    pub revents: PollEvents,
62}
63
64impl PollFd {
65    pub const fn new(fd: u32, events: PollEvents) -> Self {
66        Self {
67            fd,
68            events,
69            revents: PollEvents::empty(),
70        }
71    }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct PollResult {
76    pub ready_count: usize,
77    pub fds: Vec<PollFd>,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum PollTarget {
82    Fd(u32),
83    Socket(SocketId),
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub struct PollTargetEntry {
88    pub target: PollTarget,
89    pub events: PollEvents,
90    pub revents: PollEvents,
91}
92
93impl PollTargetEntry {
94    pub const fn new(target: PollTarget, events: PollEvents) -> Self {
95        Self {
96            target,
97            events,
98            revents: PollEvents::empty(),
99        }
100    }
101
102    pub const fn fd(fd: u32, events: PollEvents) -> Self {
103        Self::new(PollTarget::Fd(fd), events)
104    }
105
106    pub const fn socket(socket_id: SocketId, events: PollEvents) -> Self {
107        Self::new(PollTarget::Socket(socket_id), events)
108    }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct PollTargetResult {
113    pub ready_count: usize,
114    pub targets: Vec<PollTargetEntry>,
115}
116
117/// Cloneable, Send handle onto the kernel's poll notifier so a caller can wait
118/// for "some poll-visible state changed" OFF the thread that owns the kernel
119/// (deferred readiness servicing), then re-check readiness with a zero-timeout
120/// poll on the owning thread. Take `snapshot()` BEFORE the readiness check it
121/// guards so a change landing between the check and the wait wakes immediately
122/// instead of being lost.
123#[derive(Debug, Clone)]
124pub struct PollWaitHandle {
125    notifier: PollNotifier,
126}
127
128impl PollWaitHandle {
129    pub(crate) fn new(notifier: PollNotifier) -> Self {
130        Self { notifier }
131    }
132
133    /// Snapshot the current change generation.
134    pub fn snapshot(&self) -> u64 {
135        self.notifier.snapshot()
136    }
137
138    /// Block until the generation moves past `observed` or `timeout` elapses
139    /// (`None` = wait forever). Returns true when a change was observed.
140    pub fn wait_for_change(&self, observed: u64, timeout: Option<Duration>) -> bool {
141        self.notifier.wait_for_change(observed, timeout)
142    }
143
144    /// Yield until the generation moves past `observed` without occupying an
145    /// OS thread. The returned future is runtime-neutral; the sidecar may await
146    /// it on its one process-owned executor while browser-free kernel users may
147    /// use any compatible executor.
148    pub async fn wait_for_change_async(&self, observed: u64) -> bool {
149        self.notifier.wait_for_change_async(observed).await
150    }
151}
152
153#[derive(Debug, Clone, Default)]
154pub(crate) struct PollNotifier {
155    inner: Arc<PollNotifierInner>,
156}
157
158#[derive(Debug, Default)]
159struct PollNotifierInner {
160    generation: Mutex<u64>,
161    waiters: Condvar,
162    async_waiters: Event,
163}
164
165impl PollNotifier {
166    pub(crate) fn notify(&self) {
167        {
168            let mut generation = lock_or_recover(&self.inner.generation);
169            *generation = generation.wrapping_add(1);
170        }
171        self.inner.waiters.notify_all();
172        self.inner.async_waiters.notify(usize::MAX);
173    }
174
175    pub(crate) fn snapshot(&self) -> u64 {
176        *lock_or_recover(&self.inner.generation)
177    }
178
179    pub(crate) fn wait_for_change(&self, observed: u64, timeout: Option<Duration>) -> bool {
180        let mut generation = lock_or_recover(&self.inner.generation);
181        if *generation != observed {
182            return true;
183        }
184
185        let Some(timeout) = timeout else {
186            while *generation == observed {
187                generation = wait_or_recover(&self.inner.waiters, generation);
188            }
189            return true;
190        };
191
192        if timeout.is_zero() {
193            return *generation != observed;
194        }
195
196        let deadline = Instant::now() + timeout;
197        loop {
198            let now = Instant::now();
199            if now >= deadline {
200                return *generation != observed;
201            }
202
203            let remaining = deadline.saturating_duration_since(now);
204            let (next_generation, wait_result) =
205                wait_timeout_or_recover(&self.inner.waiters, generation, remaining);
206            generation = next_generation;
207            if *generation != observed {
208                return true;
209            }
210            if wait_result.timed_out() {
211                return false;
212            }
213        }
214    }
215
216    pub(crate) async fn wait_for_change_async(&self, observed: u64) -> bool {
217        loop {
218            // Register before checking the generation so a notification that
219            // races this check is retained by the listener rather than lost.
220            let listener = self.inner.async_waiters.listen();
221            if self.snapshot() != observed {
222                return true;
223            }
224            listener.await;
225            if self.snapshot() != observed {
226                return true;
227            }
228        }
229    }
230}
231
232fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
233    match mutex.lock() {
234        Ok(guard) => guard,
235        Err(poisoned) => poisoned.into_inner(),
236    }
237}
238
239fn wait_or_recover<'a, T>(condvar: &Condvar, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
240    match condvar.wait(guard) {
241        Ok(guard) => guard,
242        Err(poisoned) => poisoned.into_inner(),
243    }
244}
245
246fn wait_timeout_or_recover<'a, T>(
247    condvar: &Condvar,
248    guard: MutexGuard<'a, T>,
249    timeout: Duration,
250) -> (MutexGuard<'a, T>, std::sync::WaitTimeoutResult) {
251    match condvar.wait_timeout(guard, timeout) {
252        Ok(result) => result,
253        Err(poisoned) => poisoned.into_inner(),
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::PollNotifier;
260    use std::sync::mpsc;
261    use std::thread;
262    use std::time::Duration;
263
264    #[test]
265    fn infinite_wait_returns_after_notification_without_waiter_storage() {
266        let notifier = PollNotifier::default();
267        let observed = notifier.snapshot();
268        let waiter = notifier.clone();
269        let (started_tx, started_rx) = mpsc::channel();
270        let (done_tx, done_rx) = mpsc::channel();
271
272        let handle = thread::spawn(move || {
273            started_tx.send(()).expect("signal waiter start");
274            let changed = waiter.wait_for_change(observed, None);
275            done_tx.send(changed).expect("signal waiter result");
276        });
277
278        started_rx.recv().expect("waiter should start");
279        assert!(
280            done_rx.recv_timeout(Duration::from_millis(25)).is_err(),
281            "waiter should stay blocked before notification"
282        );
283
284        notifier.notify();
285        assert!(done_rx
286            .recv_timeout(Duration::from_secs(1))
287            .expect("waiter should wake after notification"));
288        handle.join().expect("waiter thread should finish");
289    }
290
291    #[test]
292    fn saturated_generation_still_notifies_waiters() {
293        let notifier = PollNotifier::default();
294        {
295            let mut generation = super::lock_or_recover(&notifier.inner.generation);
296            *generation = u64::MAX;
297        }
298
299        let observed = notifier.snapshot();
300        let waiter = notifier.clone();
301        let (started_tx, started_rx) = mpsc::channel();
302        let (done_tx, done_rx) = mpsc::channel();
303
304        let handle = thread::spawn(move || {
305            started_tx.send(()).expect("signal waiter start");
306            let changed = waiter.wait_for_change(observed, Some(Duration::from_secs(1)));
307            done_tx.send(changed).expect("signal waiter result");
308        });
309
310        started_rx.recv().expect("waiter should start");
311        notifier.notify();
312
313        assert!(
314            done_rx
315                .recv_timeout(Duration::from_secs(2))
316                .expect("waiter should return after saturated notify"),
317            "saturated notify should still wake the waiter"
318        );
319        handle.join().expect("waiter thread should finish");
320    }
321}