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}