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}