Skip to main content

nexus_timer/
entry.rs

1//! Timer wheel entry — the node stored in each slab slot.
2//!
3//! Each entry carries DLL links (for the per-slot entry list), a deadline,
4//! a lightweight refcount, and the user's value wrapped in `UnsafeCell<Option<T>>`.
5
6use std::cell::{Cell, UnsafeCell};
7use std::ptr;
8
9use nexus_slab::SlotCell;
10
11/// Raw pointer to a `SlotCell<WheelEntry<T>>`.
12///
13/// This is the currency of the intrusive DLL — entries link to each other
14/// through their slab slot pointers.
15pub(crate) type EntryPtr<T> = *mut SlotCell<WheelEntry<T>>;
16
17/// Sentinel for null entry pointers.
18/// Returns a null `EntryPtr<T>`.
19#[inline]
20pub(crate) fn null_entry<T>() -> EntryPtr<T> {
21    ptr::null_mut()
22}
23
24/// Timer wheel entry stored inside a slab slot.
25///
26/// This type appears in the public type signature of [`TimerWheel`](crate::TimerWheel)
27/// as the slab's item type, but is opaque — users never construct or access it directly.
28///
29/// # Layout
30///
31/// DLL links and deadline are hot (touched every list walk / poll check).
32/// The refcount is accessed on schedule/cancel/fire. Level and slot index
33/// are set at insertion and read on cancel — they replace the recomputation
34/// that was unsound after time advances. The value is cold relative to the
35/// links — only read on fire or cancel.
36///
37/// `UnsafeCell<Option<T>>` provides interior mutability for `.take()` through
38/// shared references (slab gives `&self` access). `Option<T>` makes Drop safe:
39/// `drop_in_place` on `None` is a no-op. Single-threaded (`!Send`), so
40/// `UnsafeCell` is sound.
41#[repr(C)]
42pub struct WheelEntry<T> {
43    // DLL links — touched every list walk (hot)
44    prev: Cell<EntryPtr<T>>,
45    next: Cell<EntryPtr<T>>,
46    // Deadline — compared during poll (hot)
47    deadline_ticks: Cell<u64>,
48    // Refcount — 1=wheel only (fire-and-forget), 2=wheel+handle
49    refs: Cell<u8>,
50    // Location — set at insertion, read at cancel. Avoids recomputing level
51    // from delta (which is unsound after current_ticks advances).
52    level: Cell<u8>,
53    slot_idx: Cell<u16>,
54    // Value — read only on fire/cancel (cold relative to links)
55    value: UnsafeCell<Option<T>>,
56}
57
58impl<T> WheelEntry<T> {
59    /// Creates a new entry with the given deadline and value.
60    ///
61    /// `refs` starts at the given count (1 for fire-and-forget, 2 for handle-bearing).
62    #[inline]
63    pub(crate) fn new(deadline_ticks: u64, value: T, refs: u8) -> Self {
64        WheelEntry {
65            prev: Cell::new(null_entry()),
66            next: Cell::new(null_entry()),
67            deadline_ticks: Cell::new(deadline_ticks),
68            refs: Cell::new(refs),
69            level: Cell::new(0),
70            slot_idx: Cell::new(0),
71            value: UnsafeCell::new(Some(value)),
72        }
73    }
74
75    // =========================================================================
76    // DLL link accessors
77    // =========================================================================
78
79    #[inline]
80    pub(crate) fn prev(&self) -> EntryPtr<T> {
81        self.prev.get()
82    }
83
84    #[inline]
85    pub(crate) fn next(&self) -> EntryPtr<T> {
86        self.next.get()
87    }
88
89    #[inline]
90    pub(crate) fn set_prev(&self, ptr: EntryPtr<T>) {
91        self.prev.set(ptr);
92    }
93
94    #[inline]
95    pub(crate) fn set_next(&self, ptr: EntryPtr<T>) {
96        self.next.set(ptr);
97    }
98
99    // =========================================================================
100    // Deadline
101    // =========================================================================
102
103    #[inline]
104    pub(crate) fn deadline_ticks(&self) -> u64 {
105        self.deadline_ticks.get()
106    }
107
108    /// Updates the deadline ticks for rescheduling.
109    #[inline]
110    pub(crate) fn set_deadline_ticks(&self, ticks: u64) {
111        self.deadline_ticks.set(ticks);
112    }
113
114    // =========================================================================
115    // Refcount
116    // =========================================================================
117
118    #[inline]
119    pub(crate) fn refs(&self) -> u8 {
120        self.refs.get()
121    }
122
123    /// Decrements the refcount by 1 and returns the new value.
124    #[inline]
125    pub(crate) fn dec_refs(&self) -> u8 {
126        let r = self.refs.get() - 1;
127        self.refs.set(r);
128        r
129    }
130
131    // =========================================================================
132    // Location (level + slot index) — set at insertion, read at cancel
133    // =========================================================================
134
135    /// Returns the level index this entry was placed in.
136    #[inline]
137    pub(crate) fn level(&self) -> u8 {
138        self.level.get()
139    }
140
141    /// Returns the slot index within the level this entry was placed in.
142    #[inline]
143    pub(crate) fn slot_idx(&self) -> u16 {
144        self.slot_idx.get()
145    }
146
147    /// Records the level and slot index this entry was placed in.
148    #[inline]
149    pub(crate) fn set_location(&self, level: u8, slot_idx: u16) {
150        self.level.set(level);
151        self.slot_idx.set(slot_idx);
152    }
153
154    // =========================================================================
155    // Value access
156    // =========================================================================
157
158    /// Takes the value out, leaving `None` in its place.
159    ///
160    /// # Safety
161    ///
162    /// Must be called from a single thread (enforced by `!Send` on the wheel).
163    #[inline]
164    pub(crate) unsafe fn take_value(&self) -> Option<T> {
165        // SAFETY: single-threaded access guaranteed by !Send on TimerWheel
166        unsafe { (*self.value.get()).take() }
167    }
168}
169
170/// Dereferences an `EntryPtr<T>` to a `&WheelEntry<T>`.
171///
172/// # Safety
173///
174/// - `ptr` must be non-null.
175/// - `ptr` must point to an occupied `SlotCell<WheelEntry<T>>` within a live slab.
176/// - The returned reference must not outlive the slab allocation (i.e. must not
177///   be held across a `free_ptr` call on the same slot).
178#[inline]
179pub(crate) unsafe fn entry_ref<'a, T>(ptr: EntryPtr<T>) -> &'a WheelEntry<T> {
180    // SAFETY: SlotCell is a union; when occupied, the `value` field is active.
181    // The value is `ManuallyDrop<MaybeUninit<WheelEntry<T>>>`, and we know it's
182    // initialized because the slot was allocated via slab.alloc/try_alloc.
183    unsafe { (*ptr).value.assume_init_ref() }
184}