Skip to main content

dope_core/driver/schedule/clock/timer/
mod.rs

1use std::{mem, task, time};
2
3use o3::mem::{Lease, Return};
4
5use crate::{
6    driver,
7    driver::schedule::{
8        clock,
9        clock::timer,
10        ready::{wake, wake::resident::slot},
11    },
12};
13
14pub mod heap;
15
16pub(in crate::driver) struct Tag;
17
18pub(in crate::driver) enum Expiry {
19    Fired,
20    Pending,
21}
22
23/// The driver's fixed timer capacity has no unleased resident slot.
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25pub enum Exhausted {
26    Slots,
27}
28
29/// Copyable access to the fixed timer store of one exact driver generation.
30#[derive(Clone, Copy)]
31#[repr(transparent)]
32pub struct Timer<'d> {
33    pub(in crate::driver) driver: driver::Reference<'d>,
34}
35
36impl<'d> Timer<'d> {
37    pub(in crate::driver) const fn new(driver: driver::Reference<'d>) -> Self {
38        Self { driver }
39    }
40
41    pub fn earliest(self) -> Option<clock::Deadline<'d>> {
42        self.access()
43            .earliest()
44            .map(|at| self.driver.clock().deadline_at(at))
45    }
46
47    pub fn try_alarm(self) -> Result<Alarm<'d>, Exhausted> {
48        let slot = self.access().lease().ok_or(Exhausted::Slots)?;
49        Ok(Alarm::new(self.access(), slot))
50    }
51
52    pub(in crate::driver) fn expire_one(self, now: clock::Moment<'d>) -> Expiry {
53        self.access().expire_one(now.into_raw())
54    }
55
56    fn access(self) -> Access<'d> {
57        Access::new(self.driver)
58    }
59}
60
61const _: () = {
62    assert!(mem::size_of::<Timer<'static>>() == mem::size_of::<usize>());
63    assert!(mem::align_of::<Timer<'static>>() == mem::align_of::<usize>());
64};
65
66#[derive(Clone, Copy)]
67#[repr(transparent)]
68pub(super) struct Access<'d> {
69    driver: driver::Reference<'d>,
70}
71
72/// Linear access to one timer store for the duration of one sealed command.
73struct Command<'turn, 'd> {
74    heap: heap::Min<'turn, 'd, time::Instant, slot::Slot, Tag>,
75    driver: driver::Reference<'d>,
76}
77
78/// A resident alarm tied to one exact driver generation.
79///
80pub struct Alarm<'d> {
81    lease: Lease<Access<'d>>,
82}
83
84/// Exclusive proof that one alarm remains armed or fired.
85#[must_use = "an armed alarm must be polled or cancelled"]
86#[repr(transparent)]
87pub struct Armed<'a, 'd> {
88    alarm: &'a mut Alarm<'d>,
89}
90
91/// State transition after polling an exclusively armed alarm.
92#[must_use]
93pub enum Poll<'a, 'd> {
94    Pending(Armed<'a, 'd>),
95    Ready,
96}
97
98impl<'d> Command<'_, 'd> {
99    fn set(&mut self, slot: &heap::Lease<'d, Tag>, wake: &wake::Waker<'d>) {
100        self.heap.resident(slot).set(*wake.key());
101    }
102
103    fn arm(&mut self, slot: &heap::Lease<'d, Tag>, due: time::Instant, wake: &wake::Waker<'d>) {
104        self.set(slot, wake);
105        self.heap.set(slot, due);
106    }
107
108    fn clear(&mut self, slot: &heap::Lease<'d, Tag>) {
109        self.heap.clear(slot);
110        self.heap.resident(slot).clear();
111    }
112}
113
114impl<'d> Access<'d> {
115    const fn new(driver: driver::Reference<'d>) -> Self {
116        Self { driver }
117    }
118
119    fn earliest(self) -> Option<time::Instant> {
120        self.command(|cmd| cmd.heap.peek().copied())
121    }
122
123    fn lease(self) -> Option<heap::Lease<'d, Tag>> {
124        self.command(|mut cmd| {
125            let vacancy = cmd.heap.vacancy()?;
126            Some(vacancy.lease(slot::Slot::default()))
127        })
128    }
129
130    fn has_alarm(self, slot: &heap::Lease<'d, Tag>) -> bool {
131        self.command(|cmd| cmd.heap.state(slot) != heap::State::Idle)
132    }
133
134    fn arm(self, slot: &heap::Lease<'d, Tag>, due: time::Instant, target: wake::Waker<'d>) {
135        self.command(|mut cmd| cmd.arm(slot, due, &target));
136    }
137
138    fn arm_and_poll(
139        self,
140        slot: &heap::Lease<'d, Tag>,
141        due: time::Instant,
142        now: time::Instant,
143        target: wake::Waker<'d>,
144    ) -> task::Poll<()> {
145        self.command(|mut cmd| {
146            if due <= now {
147                cmd.clear(slot);
148                task::Poll::Ready(())
149            } else {
150                cmd.arm(slot, due, &target);
151                task::Poll::Pending
152            }
153        })
154    }
155
156    fn poll(
157        self,
158        slot: &heap::Lease<'d, Tag>,
159        now: time::Instant,
160        target: wake::Waker<'d>,
161    ) -> task::Poll<()> {
162        self.command(|mut cmd| {
163            let state = cmd.heap.state(slot);
164            let due = state == heap::State::Fired
165                || cmd.heap.priority(slot).is_some_and(|due| *due <= now);
166            if due {
167                cmd.clear(slot);
168                task::Poll::Ready(())
169            } else if state == heap::State::Armed {
170                cmd.set(slot, &target);
171                task::Poll::Pending
172            } else {
173                task::Poll::Pending
174            }
175        })
176    }
177
178    fn clear(self, slot: &heap::Lease<'d, Tag>) {
179        self.command(|mut cmd| cmd.clear(slot));
180    }
181
182    fn expire_one(self, now: time::Instant) -> timer::Expiry {
183        let ready = self.command(|mut cmd| {
184            let min = cmd.heap.minimum()?;
185            if *min.priority() > now {
186                return None;
187            }
188            let fired = min.fire();
189            fired.resident().wake_once(cmd.driver);
190            Some(())
191        });
192        let Some(()) = ready else {
193            return timer::Expiry::Pending;
194        };
195        timer::Expiry::Fired
196    }
197
198    fn command<T>(self, op: impl for<'turn> FnOnce(Command<'turn, 'd>) -> T) -> T {
199        let shared = self.driver.shared;
200        // SAFETY: this fixed owner cell supplies every `'d` lease; Command's
201        // higher-ranked exclusive loan is linear, sealed, and non-reentrant.
202        unsafe {
203            shared.context().timer().loan(|heap| {
204                op(Command {
205                    heap,
206                    driver: self.driver,
207                })
208            })
209        }
210    }
211}
212
213impl<'d> Alarm<'d> {
214    const fn new(access: Access<'d>, slot: heap::Lease<'d, Tag>) -> Self {
215        Self {
216            lease: Lease::new(access, slot),
217        }
218    }
219
220    pub fn is_armed(&self) -> bool {
221        self.lease.sink().has_alarm(self.slot())
222    }
223
224    pub fn arm(&self, due: clock::Deadline<'d>, target: wake::Waker<'d>) {
225        self.lease.sink().arm(self.slot(), due.into_raw(), target);
226    }
227
228    /// Replaces this deadline and polls it in one command, retaining the slot.
229    pub fn arm_and_poll(
230        &self,
231        due: clock::Deadline<'d>,
232        now: clock::Moment<'d>,
233        target: wake::Waker<'d>,
234    ) -> task::Poll<()> {
235        self.lease
236            .sink()
237            .arm_and_poll(self.slot(), due.into_raw(), now.into_raw(), target)
238    }
239
240    /// Replaces and polls this deadline, retaining exclusive proof while pending.
241    pub fn arm_owned<'a>(
242        &'a mut self,
243        due: clock::Deadline<'d>,
244        now: clock::Moment<'d>,
245        target: wake::Waker<'d>,
246    ) -> Poll<'a, 'd> {
247        if self.arm_and_poll(due, now, target).is_pending() {
248            Poll::Pending(Armed { alarm: self })
249        } else {
250            Poll::Ready
251        }
252    }
253
254    pub fn poll(&self, now: clock::Moment<'d>, target: wake::Waker<'d>) -> task::Poll<()> {
255        self.lease.sink().poll(self.slot(), now.into_raw(), target)
256    }
257
258    pub fn clear(&self) {
259        self.lease.sink().clear(self.slot());
260    }
261
262    fn slot(&self) -> &heap::Lease<'d, Tag> {
263        self.lease.item()
264    }
265}
266
267impl<'a, 'd> Armed<'a, 'd> {
268    /// Polls without losing the proof when the alarm remains live.
269    pub fn poll(self, now: clock::Moment<'d>, target: wake::Waker<'d>) -> Poll<'a, 'd> {
270        if self.alarm.poll(now, target).is_pending() {
271            Poll::Pending(self)
272        } else {
273            Poll::Ready
274        }
275    }
276
277    /// Cancels an alarm proven not to be idle.
278    pub fn cancel(self) {
279        self.alarm.clear();
280    }
281}
282
283impl<'d> Return for Access<'d> {
284    type Item = heap::Lease<'d, Tag>;
285
286    fn return_item(self, slot: Self::Item) {
287        self.command(|mut cmd| {
288            cmd.heap.release(slot).clear();
289        });
290    }
291}
292
293const _: () = {
294    assert!(mem::size_of::<heap::Lease<'static, Tag>>() == mem::size_of::<u32>());
295    assert!(mem::align_of::<heap::Lease<'static, Tag>>() == mem::align_of::<u32>());
296    assert!(mem::size_of::<Access<'static>>() == mem::size_of::<usize>());
297    assert!(mem::size_of::<Command<'static, 'static>>() == 2 * mem::size_of::<usize>());
298    assert!(mem::size_of::<Alarm<'static>>() == 2 * mem::size_of::<usize>());
299    assert!(mem::align_of::<Alarm<'static>>() == mem::align_of::<usize>());
300    assert!(mem::size_of::<Armed<'static, 'static>>() == mem::size_of::<usize>());
301    assert!(mem::size_of::<Option<Armed<'static, 'static>>>() == mem::size_of::<usize>());
302    assert!(mem::size_of::<Poll<'static, 'static>>() == mem::size_of::<usize>());
303};