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 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}