Skip to main content

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

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