Skip to main content

nexus_async_rt/
context.rs

1//! Thread-local runtime context.
2//!
3//! Two access shapes for runtime state, by intent:
4//!
5//! - **Handles for the current runtime** — [`IoHandle::current`](crate::IoHandle::current),
6//!   [`WorldCtx::current`](crate::WorldCtx::current),
7//!   [`ShutdownSignal::current`](crate::ShutdownSignal::current). Inherent
8//!   `current()` methods on the type, mirroring `tokio::runtime::Handle::current()`.
9//!   Use when you need the handle/future itself.
10//! - **Future factories and value getters** — free functions [`sleep`],
11//!   [`sleep_until`], [`interval`], [`interval_at`], [`after`],
12//!   [`after_delay`], [`timeout`], [`timeout_at`], [`yield_now`],
13//!   [`event_time`]. These produce a value and don't fit the `Type::current()`
14//!   shape (the future is the API; there's no enclosing handle to fetch).
15//!
16//! All readers panic if called outside a [`Runtime::block_on`](crate::Runtime::block_on)
17//! context. The TLS slots are installed by `block_on` and cleared on exit;
18//! const-initialized for zero first-access cost.
19//!
20//! ```ignore
21//! use nexus_async_rt::{spawn_boxed, sleep, WorldCtx, ShutdownSignal, TcpListener};
22//!
23//! rt.block_on(async {
24//!     spawn_boxed(async {
25//!         WorldCtx::current().with_world(|world| { /* ... */ });
26//!         sleep(Duration::from_secs(1)).await;
27//!         let listener = TcpListener::bind(addr);  // fetches IoHandle::current() internally
28//!     });
29//!     ShutdownSignal::current().await;
30//! });
31//! ```
32
33use std::cell::Cell;
34use std::sync::Arc;
35use std::sync::atomic::AtomicBool;
36use std::time::{Duration, Instant};
37
38use crate::io::IoDriver;
39use crate::timer::{TimerDriver, TimerHandle};
40
41// =============================================================================
42// TLS slots — const-initialized, zero first-access cost
43// =============================================================================
44
45thread_local! {
46    static CTX_WORLD: Cell<*mut nexus_rt::World> =
47        const { Cell::new(std::ptr::null_mut()) };
48    static CTX_IO: Cell<*mut IoDriver> =
49        const { Cell::new(std::ptr::null_mut()) };
50    static CTX_TIMER: Cell<*mut TimerDriver> =
51        const { Cell::new(std::ptr::null_mut()) };
52    static CTX_EVENT_TIME: Cell<*const Cell<Instant>> =
53        const { Cell::new(std::ptr::null()) };
54    static CTX_SHUTDOWN: Cell<*const AtomicBool> =
55        const { Cell::new(std::ptr::null()) };
56    static CTX_SHUTDOWN_WAKER: Cell<*const Arc<std::sync::Mutex<Option<std::task::Waker>>>> =
57        const { Cell::new(std::ptr::null()) };
58}
59
60// =============================================================================
61// Install / clear (called by Runtime::block_on)
62// =============================================================================
63
64/// Install runtime context into TLS. Called by both `Runtime::block_on`
65/// (root execution path) and `Runtime::shutdown_quiesce` (so cross-thread
66/// wakes that fire during quiesce still find a runtime context).
67/// The context stays installed until the returned guard is dropped.
68pub(crate) fn install(
69    world: *mut nexus_rt::World,
70    io: *mut IoDriver,
71    timer: *mut TimerDriver,
72    event_time: *const Cell<Instant>,
73    shutdown_flag: *const AtomicBool,
74    shutdown_waker: *const Arc<std::sync::Mutex<Option<std::task::Waker>>>,
75) -> ContextGuard {
76    let prev = PrevContext {
77        world: CTX_WORLD.with(|c| c.replace(world)),
78        io: CTX_IO.with(|c| c.replace(io)),
79        timer: CTX_TIMER.with(|c| c.replace(timer)),
80        event_time: CTX_EVENT_TIME.with(|c| c.replace(event_time)),
81        shutdown: CTX_SHUTDOWN.with(|c| c.replace(shutdown_flag)),
82        shutdown_waker: CTX_SHUTDOWN_WAKER.with(|c| c.replace(shutdown_waker)),
83    };
84    ContextGuard { prev }
85}
86
87struct PrevContext {
88    world: *mut nexus_rt::World,
89    io: *mut IoDriver,
90    timer: *mut TimerDriver,
91    event_time: *const Cell<Instant>,
92    shutdown: *const AtomicBool,
93    shutdown_waker: *const Arc<std::sync::Mutex<Option<std::task::Waker>>>,
94}
95
96pub(crate) struct ContextGuard {
97    prev: PrevContext,
98}
99
100impl Drop for ContextGuard {
101    fn drop(&mut self) {
102        CTX_WORLD.with(|c| c.set(self.prev.world));
103        CTX_IO.with(|c| c.set(self.prev.io));
104        CTX_TIMER.with(|c| c.set(self.prev.timer));
105        CTX_EVENT_TIME.with(|c| c.set(self.prev.event_time));
106        CTX_SHUTDOWN.with(|c| c.set(self.prev.shutdown));
107        CTX_SHUTDOWN_WAKER.with(|c| c.set(self.prev.shutdown_waker));
108    }
109}
110
111/// Assert that we're inside a runtime context. Panics with `msg` if not.
112pub(crate) fn assert_in_runtime(msg: &str) {
113    let ptr = CTX_WORLD.with(Cell::get);
114    assert!(!ptr.is_null(), "{msg}");
115}
116
117// =============================================================================
118// pub(crate) TLS readers — back the inherent `Type::current()` methods on
119// `IoHandle`, `WorldCtx`, and `ShutdownSignal`. Kept in this module so the
120// `CTX_*` thread-locals don't need to be exposed elsewhere in the crate.
121// =============================================================================
122
123/// Returns the raw `IoDriver` pointer installed for the current runtime, or
124/// null if outside a runtime context.
125pub(crate) fn current_io_ptr() -> *mut IoDriver {
126    CTX_IO.with(Cell::get)
127}
128
129/// Returns the raw `World` pointer installed for the current runtime, or null
130/// if outside a runtime context.
131pub(crate) fn current_world_ptr() -> *mut nexus_rt::World {
132    CTX_WORLD.with(Cell::get)
133}
134
135/// Returns `(flag, waker)` pointers for the current runtime's shutdown
136/// machinery, or `(null, null)` if outside a runtime context. Both pointers
137/// are non-null whenever the runtime is installed (`install` writes them
138/// together).
139pub(crate) fn current_shutdown_ptrs() -> (
140    *const AtomicBool,
141    *const Arc<std::sync::Mutex<Option<std::task::Waker>>>,
142) {
143    let flag = CTX_SHUTDOWN.with(Cell::get);
144    let waker = CTX_SHUTDOWN_WAKER.with(Cell::get);
145    (flag, waker)
146}
147
148/// Create a [`Sleep`](crate::Sleep) future that completes after `duration`.
149///
150/// # Panics
151///
152/// Panics if called outside a runtime context.
153pub fn sleep(duration: Duration) -> crate::Sleep {
154    let ptr = CTX_TIMER.with(Cell::get);
155    assert!(!ptr.is_null(), "sleep() called outside Runtime::block_on");
156    // SAFETY: ptr valid for Runtime lifetime.
157    let handle = TimerHandle::new(unsafe { &mut *ptr });
158    handle.sleep(duration)
159}
160
161/// Create a [`Sleep`](crate::Sleep) future that completes at `deadline`.
162pub fn sleep_until(deadline: Instant) -> crate::Sleep {
163    let ptr = CTX_TIMER.with(Cell::get);
164    assert!(
165        !ptr.is_null(),
166        "sleep_until() called outside Runtime::block_on"
167    );
168    let handle = TimerHandle::new(unsafe { &mut *ptr });
169    handle.sleep_until(deadline)
170}
171
172/// Timestamp taken after the most recent IO poll cycle.
173///
174/// All events dispatched within the same cycle share this timestamp.
175/// One clock read per cycle, not per event.
176pub fn event_time() -> Instant {
177    let ptr = CTX_EVENT_TIME.with(Cell::get);
178    assert!(
179        !ptr.is_null(),
180        "event_time() called outside Runtime::block_on"
181    );
182    // SAFETY: ptr valid for Runtime lifetime.
183    unsafe { &*ptr }.get()
184}
185
186/// Wrap a future with a deadline. Returns `Err(Elapsed)` if the
187/// deadline expires before the future completes.
188///
189/// # Panics
190///
191/// Panics if called outside a runtime context.
192pub fn timeout<F: std::future::Future>(duration: Duration, future: F) -> crate::timer::Timeout<F> {
193    crate::timer::Timeout::new(future, sleep(duration))
194}
195
196/// Create an interval that ticks at a fixed period.
197///
198/// The first tick completes after `period`. Subsequent ticks are
199/// spaced `period` apart. If processing takes longer than `period`,
200/// behavior is controlled by [`MissedTickBehavior`](crate::MissedTickBehavior).
201///
202/// # Panics
203///
204/// Panics if `period` is zero. Polling the interval (via `tick().await`)
205/// requires an active runtime context and will panic otherwise.
206pub fn interval(period: Duration) -> crate::timer::Interval {
207    crate::timer::Interval::new(period)
208}
209
210/// Run a future no earlier than `deadline`.
211///
212/// Waits until `deadline`, then polls the future. Useful for
213/// scheduling deferred work at a specific time.
214///
215/// Polling requires an active runtime context.
216pub async fn after<F: std::future::Future>(deadline: Instant, future: F) -> F::Output {
217    sleep_until(deadline).await;
218    future.await
219}
220
221/// Run a future after `duration` elapses.
222///
223/// Waits for `duration`, then polls the future.
224///
225/// Polling requires an active runtime context.
226pub async fn after_delay<F: std::future::Future>(duration: Duration, future: F) -> F::Output {
227    sleep(duration).await;
228    future.await
229}
230
231/// Wrap a future with an absolute deadline. Returns `Err(Elapsed)` if
232/// the deadline passes before the future completes.
233///
234/// Like [`timeout`] but takes an [`Instant`] instead of a [`Duration`].
235///
236/// # Panics
237///
238/// Panics if called outside a runtime context.
239pub fn timeout_at<F: std::future::Future>(
240    deadline: Instant,
241    future: F,
242) -> crate::timer::Timeout<F> {
243    crate::timer::Timeout::new(future, sleep_until(deadline))
244}
245
246/// Create an interval that starts ticking at `start`, then every `period`.
247///
248/// If `start` is in the past, the first tick fires immediately.
249///
250/// # Panics
251///
252/// Panics if `period` is zero. Polling the interval requires an active
253/// runtime context.
254pub fn interval_at(start: Instant, period: Duration) -> crate::timer::Interval {
255    crate::timer::Interval::new_at(start, period)
256}
257
258/// Cooperatively yield the current task.
259///
260/// Returns `Pending` once, wakes itself, then completes on the next
261/// poll. Other ready tasks get a turn before this task resumes.
262pub fn yield_now() -> crate::timer::YieldNow {
263    crate::timer::YieldNow(false)
264}