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 at runtime;
40/// `Send` when `T: Send` (for World storage). `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
58// SAFETY: WheelEntry<T> is never exposed directly to user code; it is only
59// owned and accessed via TimerWheel, which is !Sync and therefore cannot be
60// shared between threads. All mutation of the DLL links (prev/next), the
61// refcount, and the UnsafeCell<Option<T>> happens while the owning
62// TimerWheel has exclusive access on a single thread — no concurrent access
63// to a given WheelEntry<T>. The T: Send bound is required because
64// TimerWheel (and its slab storage) may be moved between threads as a whole;
65// it does not permit cross-thread aliasing of WheelEntry<T> itself.
66#[allow(clippy::non_send_fields_in_send_ty)]
67unsafe impl<T: Send> Send for WheelEntry<T> {}
68
69impl<T> WheelEntry<T> {
70 /// Creates a new entry with the given deadline and value.
71 ///
72 /// `refs` starts at the given count (1 for fire-and-forget, 2 for handle-bearing).
73 #[inline]
74 pub(crate) fn new(deadline_ticks: u64, value: T, refs: u8) -> Self {
75 WheelEntry {
76 prev: Cell::new(null_entry()),
77 next: Cell::new(null_entry()),
78 deadline_ticks: Cell::new(deadline_ticks),
79 refs: Cell::new(refs),
80 level: Cell::new(0),
81 slot_idx: Cell::new(0),
82 value: UnsafeCell::new(Some(value)),
83 }
84 }
85
86 // =========================================================================
87 // DLL link accessors
88 // =========================================================================
89
90 #[inline]
91 pub(crate) fn prev(&self) -> EntryPtr<T> {
92 self.prev.get()
93 }
94
95 #[inline]
96 pub(crate) fn next(&self) -> EntryPtr<T> {
97 self.next.get()
98 }
99
100 #[inline]
101 pub(crate) fn set_prev(&self, ptr: EntryPtr<T>) {
102 self.prev.set(ptr);
103 }
104
105 #[inline]
106 pub(crate) fn set_next(&self, ptr: EntryPtr<T>) {
107 self.next.set(ptr);
108 }
109
110 // =========================================================================
111 // Deadline
112 // =========================================================================
113
114 #[inline]
115 pub(crate) fn deadline_ticks(&self) -> u64 {
116 self.deadline_ticks.get()
117 }
118
119 /// Updates the deadline ticks for rescheduling.
120 #[inline]
121 pub(crate) fn set_deadline_ticks(&self, ticks: u64) {
122 self.deadline_ticks.set(ticks);
123 }
124
125 // =========================================================================
126 // Refcount
127 // =========================================================================
128
129 #[inline]
130 pub(crate) fn refs(&self) -> u8 {
131 self.refs.get()
132 }
133
134 /// Decrements the refcount by 1 and returns the new value.
135 #[inline]
136 pub(crate) fn dec_refs(&self) -> u8 {
137 let r = self.refs.get() - 1;
138 self.refs.set(r);
139 r
140 }
141
142 // =========================================================================
143 // Location (level + slot index) — set at insertion, read at cancel
144 // =========================================================================
145
146 /// Returns the level index this entry was placed in.
147 #[inline]
148 pub(crate) fn level(&self) -> u8 {
149 self.level.get()
150 }
151
152 /// Returns the slot index within the level this entry was placed in.
153 #[inline]
154 pub(crate) fn slot_idx(&self) -> u16 {
155 self.slot_idx.get()
156 }
157
158 /// Records the level and slot index this entry was placed in.
159 #[inline]
160 pub(crate) fn set_location(&self, level: u8, slot_idx: u16) {
161 self.level.set(level);
162 self.slot_idx.set(slot_idx);
163 }
164
165 // =========================================================================
166 // Value access
167 // =========================================================================
168
169 /// Takes the value out, leaving `None` in its place.
170 ///
171 /// # Safety
172 ///
173 /// Must be called from a single thread (enforced by `!Send` on the wheel).
174 #[inline]
175 pub(crate) unsafe fn take_value(&self) -> Option<T> {
176 // SAFETY: single-threaded access guaranteed by !Send on TimerWheel
177 unsafe { (*self.value.get()).take() }
178 }
179}
180
181/// Dereferences an `EntryPtr<T>` to a `&WheelEntry<T>`.
182///
183/// # Safety
184///
185/// - `ptr` must be non-null.
186/// - `ptr` must point to an occupied `SlotCell<WheelEntry<T>>` within a live slab.
187/// - The returned reference must not outlive the slab allocation (i.e. must not
188/// be held across a `free_ptr` call on the same slot).
189#[inline]
190pub(crate) unsafe fn entry_ref<'a, T>(ptr: EntryPtr<T>) -> &'a WheelEntry<T> {
191 // SAFETY: Slot is occupied (allocated via slab.alloc/try_alloc).
192 unsafe { (*ptr).value_ref() }
193}