ntex_rt/rt/mod.rs
1use std::cell::{Cell, RefCell};
2use std::future::poll_fn;
3use std::sync::atomic::Ordering;
4use std::task::Poll;
5use std::time::{Duration, Instant};
6use std::{future::Future, io, marker::PhantomData, panic::Location};
7use std::{pin::Pin, rc::Rc, sync::Arc};
8
9use crate::driver::{Driver, Notify, PollResult};
10use crate::handle::{JoinHandle, RemoteJoinHandle, SpawnLocation};
11use crate::metrics::Histogram;
12use crate::remote::{Job, RemoteJoin, Runner};
13
14mod queue;
15pub(crate) mod task;
16
17use self::queue::RunnableQueue;
18
19/// Log target of the scheduler, also used by its submodules.
20const LOG_TARGET: &str = module_path!();
21
22// marks a reported self-wake streak
23pub(crate) const REPORTED: u32 = 1 << 31;
24
25/// Self-wake bookkeeping of a task, used on the runtime thread only.
26pub(crate) trait TaskStats: Copy {
27 /// Returns the task's spawn location for diagnostics.
28 fn location(self) -> SpawnLocation;
29 /// Consecutive self-wakes, `REPORTED` marks a reported streak.
30 fn self_wakes(self) -> u32;
31 /// Stores the self-wake count and its `REPORTED` marker.
32 fn set_self_wakes(self, wakes: u32);
33 /// Start of the self-wake streak, milliseconds since the runtime epoch.
34 fn streak_start(self) -> u32;
35 /// Records the streak's start in milliseconds since the runtime epoch.
36 fn set_streak_start(self, start: u32);
37}
38
39scoped_tls::scoped_thread_local!(static CURRENT_RUNTIME: Runtime);
40
41/// Index of a thread that owns a runtime, never reused within the process.
42#[cfg(not(ntex_loom))]
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub(crate) struct ThreadId(std::num::NonZeroU16);
45
46// loom threads share the OS thread, and its thread locals
47#[cfg(ntex_loom)]
48pub(crate) type ThreadId = loom::thread::ThreadId;
49
50/// The last issued thread index.
51#[cfg(not(ntex_loom))]
52static THREAD_INDEX: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(0);
53
54#[cfg(not(ntex_loom))]
55thread_local!(static THREAD_ID: Cell<Option<ThreadId>> = const { Cell::new(None) });
56
57/// Returns the index of the current thread, assigning one on first use.
58///
59/// Only threads that own a runtime call this, indexes are limited to
60/// `u16::MAX` per process and the process aborts when they run out.
61#[cfg(not(ntex_loom))]
62#[inline]
63pub(crate) fn current_thread_id() -> ThreadId {
64 /// Assigns a never-reused index on the thread's first use.
65 #[cold]
66 fn initialize(slot: &Cell<Option<ThreadId>>) -> ThreadId {
67 let id = THREAD_INDEX
68 .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
69 .ok()
70 .and_then(|n| std::num::NonZeroU16::new(n + 1))
71 .unwrap_or_else(|| {
72 log::error!(target: LOG_TARGET, "Thread indexes are exhausted, {} runtime-owning threads per process", u16::MAX);
73 std::process::abort()
74 });
75 let id = ThreadId(id);
76 slot.set(Some(id));
77 id
78 }
79
80 THREAD_ID.with(|slot| slot.get().unwrap_or_else(|| initialize(slot)))
81}
82
83/// Returns the index of the current thread without assigning one.
84#[cfg(not(ntex_loom))]
85#[inline]
86pub(crate) fn try_current_thread_id() -> Option<ThreadId> {
87 THREAD_ID.with(Cell::get)
88}
89
90/// Sets the last issued thread index, to test exhaustion.
91#[cfg(all(test, not(ntex_loom)))]
92pub(crate) fn set_thread_index(last: u16) {
93 THREAD_INDEX.store(last, Ordering::Relaxed);
94}
95
96/// Returns the current logical Loom thread's identity.
97#[cfg(ntex_loom)]
98pub(crate) fn current_thread_id() -> ThreadId {
99 loom::thread::current().id()
100}
101
102/// Returns the current logical Loom thread's identity.
103#[cfg(ntex_loom)]
104pub(crate) fn try_current_thread_id() -> Option<ThreadId> {
105 Some(loom::thread::current().id())
106}
107
108#[derive(Debug)]
109/// The async runtime for ntex.
110///
111/// This is a thread-local runtime and cannot be sent to other threads, use
112/// [`Handle`] to spawn tasks from other threads.
113///
114/// Dropping the runtime cancels its tasks. If cleanup panics, shutdown finishes
115/// before the panic is resumed. A second cleanup panic, or a cleanup panic
116/// while the runtime is dropped during an existing unwind, aborts the process.
117///
118/// A process can create at most 65,535 runtimes, and at most 65,535 threads can
119/// create one. Neither is reused, and the process aborts when either runs out.
120///
121/// ```compile_fail
122/// fn assert_send<T: Send>() {}
123/// assert_send::<ntex_rt::Runtime>();
124/// ```
125pub struct Runtime {
126 stop: Cell<bool>,
127 queue: Arc<RunnableQueue>,
128 // tasks are polled and dropped on the thread that created the runtime
129 _not_send: PhantomData<*const ()>,
130}
131
132impl Runtime {
133 /// Creates a runtime with default configuration.
134 pub fn new(handle: Box<dyn Notify>) -> Self {
135 Self::builder().build(handle)
136 }
137
138 /// Creates a runtime builder.
139 pub fn builder() -> RuntimeBuilder {
140 RuntimeBuilder::new()
141 }
142
143 /// Creates the owner-thread queue and registers its remote wake endpoint.
144 #[allow(clippy::arc_with_non_send_sync)]
145 fn with_builder(builder: &RuntimeBuilder, handle: Box<dyn Notify>) -> Self {
146 let queue = Arc::new(RunnableQueue::new(builder, handle));
147 task::wake::register(&queue);
148 Self {
149 stop: Cell::new(false),
150 queue,
151 _not_send: PhantomData,
152 }
153 }
154
155 /// Runs a closure with the runtime active on the current thread.
156 ///
157 /// ## Panics
158 ///
159 /// Panics if no runtime is active on the current thread.
160 pub fn with_current<T, F: FnOnce(&Self) -> T>(f: F) -> T {
161 /// Reports an operation that requires an active runtime.
162 #[cold]
163 fn not_in_neon_runtime() -> ! {
164 panic!("not in a neon runtime")
165 }
166
167 if CURRENT_RUNTIME.is_set() {
168 CURRENT_RUNTIME.with(f)
169 } else {
170 not_in_neon_runtime()
171 }
172 }
173
174 /// Returns [`Runtime::now`] of the runtime active on the current thread,
175 /// or [`Instant::now()`] if no runtime is active.
176 pub(crate) fn current_now() -> Instant {
177 if CURRENT_RUNTIME.is_set() {
178 CURRENT_RUNTIME.with(Runtime::now)
179 } else {
180 Instant::now()
181 }
182 }
183
184 #[inline]
185 /// Returns a handle to this runtime.
186 pub fn handle(&self) -> Handle {
187 Handle {
188 queue: self.queue.clone(),
189 }
190 }
191
192 /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
193 ///
194 /// Spawning a task enables the task to execute concurrently to other tasks.
195 /// There is no guarantee that a spawned task will execute to completion.
196 #[track_caller]
197 pub fn spawn<F: Future + 'static>(&self, future: F) -> JoinHandle<F::Output> {
198 unsafe { self.spawn_at(future, Location::caller()) }
199 }
200
201 /// Spawns a new asynchronous task, returning a [`JoinHandle`] for it.
202 ///
203 /// # Safety
204 ///
205 /// The caller should ensure the captured lifetime is long enough.
206 #[track_caller]
207 pub unsafe fn spawn_unchecked<F: Future>(&self, future: F) -> JoinHandle<F::Output> {
208 unsafe { self.spawn_at(future, Location::caller()) }
209 }
210
211 /// Spawns a task, `location` is reported by [`Handle::current_task`].
212 ///
213 /// # Safety
214 ///
215 /// The caller should ensure the captured lifetime is long enough.
216 pub(crate) unsafe fn spawn_at<F: Future>(
217 &self,
218 future: F,
219 location: SpawnLocation,
220 ) -> JoinHandle<F::Output> {
221 unsafe { self.queue.spawn(future, location) }
222 }
223
224 /// Spawns a new asynchronous task without a [`JoinHandle`].
225 ///
226 /// The output is dropped when the task completes.
227 #[track_caller]
228 pub fn spawn_detached<F: Future + 'static>(&self, future: F) {
229 unsafe { self.spawn_detached_at(future, Location::caller()) }
230 }
231
232 /// Spawns a detached task, `location` is reported by
233 /// [`Handle::current_task`].
234 ///
235 /// # Safety
236 ///
237 /// The caller should ensure the captured lifetime is long enough.
238 pub(crate) unsafe fn spawn_detached_at<F: Future>(&self, future: F, location: SpawnLocation) {
239 unsafe { self.queue.spawn_detached::<_, true>(future, location) }
240 }
241
242 /// Marks the runtime as awake.
243 ///
244 /// Drivers call this after returning from a blocking wait, before handling
245 /// events that wake tasks. Tasks scheduled from the runtime thread do not
246 /// notify the driver until [`poll`](Self::poll) finds no more tasks to run,
247 /// the driver must call `poll` before blocking again.
248 ///
249 /// Starts a busy period, see [`busy_duration`](Self::busy_duration).
250 #[inline]
251 pub fn awake(&self) {
252 if self.queue.idle.get() {
253 self.queue.start_busy();
254 }
255 }
256
257 /// Polls the runtime and runs scheduled tasks.
258 pub fn poll(&self) -> PollResult {
259 let start = Instant::now();
260 // tasks scheduled from other threads do not end the idle state
261 if self.queue.idle.get() {
262 self.queue.busy.start(start);
263 }
264
265 let result = if self.stop.get() {
266 PollResult::Ready
267 } else if self.queue.run() {
268 self.queue.add_poll(true);
269 PollResult::PollAgain
270 } else {
271 self.queue.add_poll(false);
272 PollResult::Pending
273 };
274
275 // a runtime that never idles stays visible, its busy period is read
276 // from its start
277 let now = Instant::now();
278 if result != PollResult::Ready {
279 self.queue
280 .poll_histogram
281 .record(now.saturating_duration_since(start));
282 }
283 if result == PollResult::PollAgain {
284 self.queue.set_poll_start(now);
285 self.queue.busy.next_turn(now);
286 } else {
287 self.queue.idle.set(true);
288 self.queue.busy.stop(now);
289 }
290 result
291 }
292
293 /// Returns the start of the current runtime turn.
294 ///
295 /// While the runtime is busy, returns the time the driver woke up or the
296 /// previous [`poll`](Self::poll) finished, tasks polled in the same turn
297 /// observe the same instant. Returns [`Instant::now()`] while the runtime
298 /// is idle.
299 #[inline]
300 pub fn now(&self) -> Instant {
301 if self.queue.idle.get() {
302 Instant::now()
303 } else {
304 self.queue.poll_start.get()
305 }
306 }
307
308 /// Returns the total time the runtime has been busy.
309 ///
310 /// The runtime is busy from [`awake`](Self::awake) until [`poll`](Self::poll)
311 /// finds no more tasks to run. This includes running tasks and handling
312 /// driver events, and excludes time the driver is blocked waiting for events.
313 /// The value is wall-clock time, not CPU time, and includes the current
314 /// busy period, so it can be read from other threads while a task runs.
315 ///
316 /// Utilization over an interval is the busy duration delta divided by the
317 /// elapsed time of the interval.
318 pub fn busy_duration(&self) -> Duration {
319 self.queue.busy.busy_duration()
320 }
321
322 /// Returns how long the current runtime turn has been running, `None`
323 /// while the runtime is idle.
324 ///
325 /// A turn ends when [`poll`](Self::poll) returns, a long turn means a task
326 /// or a driver event handler keeps the runtime from making progress.
327 pub fn current_turn(&self) -> Option<Duration> {
328 self.queue.busy.current_turn()
329 }
330
331 /// Returns the number of times [`poll`](Self::poll) ran scheduled tasks.
332 pub fn poll_count(&self) -> u64 {
333 self.queue.polls.load(Ordering::Relaxed)
334 }
335
336 /// Returns the number of times [`poll`](Self::poll) returned
337 /// [`PollResult::PollAgain`].
338 ///
339 /// `poll` runs a limited number of tasks, and returns `PollAgain` if
340 /// scheduled tasks remain. The `poll_again_count` delta divided by the
341 /// [`poll_count`](Self::poll_count) delta over an interval is the share of
342 /// polls that left a backlog; a value close to 1 means tasks are scheduled
343 /// faster than the runtime runs them.
344 pub fn poll_again_count(&self) -> u64 {
345 self.queue.polls_again.load(Ordering::Relaxed)
346 }
347
348 /// Returns the number of times a task woke itself while it was polled.
349 ///
350 /// A task that wakes itself, for example with `wake_by_ref()` or a yield,
351 /// is scheduled again without waiting for an event. A steadily growing
352 /// count points to tasks that poll in a loop, see
353 /// [`RuntimeBuilder::self_wake_threshold`].
354 pub fn self_wake_count(&self) -> u64 {
355 self.queue.self_wakes.load(Ordering::Relaxed)
356 }
357
358 /// Returns the histogram of [`poll`](Self::poll) durations.
359 ///
360 /// A poll lasts from the start of the runtime turn, see [`now`](Self::now),
361 /// until it finishes running tasks. Tasks woken during a poll wait at least
362 /// until it finishes, long polls mean high scheduling latency.
363 pub fn poll_histogram(&self) -> Histogram {
364 self.queue.poll_histogram.snapshot()
365 }
366
367 /// Runs the provided future.
368 ///
369 /// Blocks the current thread until the future completes.
370 ///
371 /// # Panics
372 ///
373 /// Panics if the driver fails to run the provided future, or if a runtime
374 /// is already active on the current thread. Nested calls would block the
375 /// running task and its runtime until the inner future completes.
376 #[track_caller]
377 pub fn block_on<F: Future>(&self, future: F, driver: &dyn Driver) -> F::Output {
378 /// Rejects blocking from within an already active runtime.
379 #[cold]
380 #[track_caller]
381 fn nested_block_on() -> ! {
382 panic!("Cannot start a runtime from within a runtime")
383 }
384
385 if CURRENT_RUNTIME.is_set() {
386 nested_block_on()
387 }
388
389 let location = Location::caller();
390 self.stop.set(false);
391 self.queue.set_poll_start(Instant::now());
392
393 CURRENT_RUNTIME.set(self, || {
394 let mut result = None;
395
396 // the task outlives this call if the driver fails or panics, it
397 // reaches the borrowing future only through the slot, which the
398 // guard empties before the borrows end
399 let slot = BlockOnSlot(Rc::new(RefCell::new(Some(Box::pin(async {
400 result = Some(future.await);
401 self.stop.set(true);
402 self.queue.notify();
403 })))));
404 let task = slot.0.clone();
405 unsafe {
406 self.spawn_detached_at(
407 poll_fn(move |cx| {
408 let mut fut = task.borrow_mut();
409 if let Some(f) = fut.as_mut()
410 && f.as_mut().poll(cx).is_pending()
411 {
412 return Poll::Pending;
413 }
414 *fut = None;
415 Poll::Ready(())
416 }),
417 location,
418 );
419 }
420
421 ntex_error::set_backtrace_start_alt("src/rt/task.rs", 0);
422 let res = driver.run(self);
423 drop(slot);
424 res.expect("Driver failed");
425 result.expect("Driver failed to poll")
426 })
427 }
428}
429
430/// Drops the [`Runtime::block_on`] future when the call returns or unwinds.
431struct BlockOnSlot<F>(Rc<RefCell<Option<Pin<Box<F>>>>>);
432
433impl<F> Drop for BlockOnSlot<F> {
434 /// Removes the borrowing future before the `block_on` scope ends.
435 fn drop(&mut self) {
436 let fut = self.0.borrow_mut().take();
437 drop(fut);
438 }
439}
440
441impl Drop for Runtime {
442 /// Cancels outstanding tasks with this runtime active during cleanup.
443 fn drop(&mut self) {
444 CURRENT_RUNTIME.set(self, || {
445 self.queue.shutdown();
446 });
447 }
448}
449
450#[derive(Debug)]
451/// A thread-safe handle used to schedule work on a runtime.
452pub struct Handle {
453 queue: Arc<RunnableQueue>,
454}
455
456impl Handle {
457 /// Returns a handle to the runtime active on the current thread.
458 ///
459 /// # Panics
460 ///
461 /// Panics if no runtime is active on the current thread.
462 pub fn current() -> Handle {
463 Runtime::with_current(Runtime::handle)
464 }
465
466 /// Wakes the runtime's driver.
467 ///
468 /// Returns notification errors to the caller without retrying, unlike
469 /// internal scheduler notifications.
470 pub fn notify(&self) -> io::Result<()> {
471 self.queue.handle.notify()
472 }
473
474 /// Returns the total time the runtime has been busy.
475 ///
476 /// See [`Runtime::busy_duration`]. Always returns `Some`, the return type
477 /// matches the other runtime backends.
478 pub fn busy_duration(&self) -> Option<Duration> {
479 Some(self.queue.busy.busy_duration())
480 }
481
482 /// Returns how long the current runtime turn has been running, `None`
483 /// while the runtime is idle.
484 ///
485 /// See [`Runtime::current_turn`].
486 pub fn current_turn(&self) -> Option<Duration> {
487 self.queue.busy.current_turn()
488 }
489
490 /// Returns the number of runtime polls that ran scheduled tasks.
491 ///
492 /// See [`Runtime::poll_count`]. Always returns `Some`.
493 pub fn poll_count(&self) -> Option<u64> {
494 Some(self.queue.polls.load(Ordering::Relaxed))
495 }
496
497 /// Returns the number of runtime polls that left scheduled tasks.
498 ///
499 /// See [`Runtime::poll_again_count`]. Always returns `Some`.
500 pub fn poll_again_count(&self) -> Option<u64> {
501 Some(self.queue.polls_again.load(Ordering::Relaxed))
502 }
503
504 /// Returns the number of times a task woke itself while it was polled.
505 ///
506 /// See [`Runtime::self_wake_count`]. Always returns `Some`.
507 pub fn self_wake_count(&self) -> Option<u64> {
508 Some(self.queue.self_wakes.load(Ordering::Relaxed))
509 }
510
511 /// Returns the spawn location of the task the runtime is polling.
512 ///
513 /// Returns `None` while the runtime is idle, handles driver events or
514 /// between tasks. Can be read from other threads, for example to name the
515 /// task that blocks a stalled runtime.
516 pub fn current_task(&self) -> Option<&'static Location<'static>> {
517 let task = self.queue.current.load(Ordering::Relaxed);
518 // only null or a `&'static Location` is stored
519 unsafe { task.as_ref() }
520 }
521
522 /// Returns the histogram of runtime poll durations.
523 ///
524 /// See [`Runtime::poll_histogram`]. Always returns `Some`.
525 pub fn poll_histogram(&self) -> Option<Histogram> {
526 Some(self.queue.poll_histogram.snapshot())
527 }
528
529 /// Spawns a task on the handle's runtime and returns a [`RemoteJoinHandle`].
530 ///
531 /// Spawning a task enables the task to execute concurrently to other tasks.
532 /// There is no guarantee that a spawned task will execute to completion.
533 ///
534 /// On the runtime thread the task is spawned directly. From other threads
535 /// the future and output must be `Send`, the future is polled and dropped
536 /// on the runtime thread and its output is returned through the handle.
537 /// The handle is neither `Send` nor `Sync`, dropping it detaches the task.
538 ///
539 /// ```no_run
540 /// # async fn check(hnd: ntex_rt::Handle) {
541 /// let task = hnd.spawn(async { 42 });
542 /// assert_eq!(task.await.unwrap(), 42);
543 /// # }
544 /// ```
545 #[track_caller]
546 pub fn spawn<F>(&self, future: F) -> RemoteJoinHandle<F::Output>
547 where
548 F: Future + Send + 'static,
549 F::Output: Send + 'static,
550 {
551 let location = Location::caller();
552 if Some(self.queue.id) == try_current_thread_id() {
553 RemoteJoinHandle::local(unsafe { self.queue.spawn(future, location) })
554 } else {
555 let job = Job::new(future, true);
556 let join = RemoteJoin::new(job.clone());
557 // a rejected submission closes the job on this thread
558 unsafe {
559 self.queue.spawn_detached::<_, false>(Runner(job), location);
560 }
561 RemoteJoinHandle::remote(join)
562 }
563 }
564
565 /// Spawns a new asynchronous task without a join handle.
566 ///
567 /// The output is dropped on the runtime thread when the task completes,
568 /// so it does not have to be `Send`.
569 #[track_caller]
570 pub fn spawn_detached<F>(&self, future: F)
571 where
572 F: Future + Send + 'static,
573 {
574 unsafe {
575 self.queue
576 .spawn_detached::<_, false>(future, Location::caller());
577 }
578 }
579}
580
581impl Clone for Handle {
582 /// Shares the runtime's queue without creating another runtime.
583 fn clone(&self) -> Self {
584 Self {
585 queue: self.queue.clone(),
586 }
587 }
588}
589
590/// Builder for [`Runtime`].
591#[derive(Debug, Clone)]
592pub struct RuntimeBuilder {
593 event_interval: usize,
594 self_wake_limit: u32,
595 self_wake_time: Duration,
596}
597
598impl Default for RuntimeBuilder {
599 /// Uses the default scheduler budgets and self-wake thresholds.
600 fn default() -> Self {
601 Self::new()
602 }
603}
604
605impl RuntimeBuilder {
606 /// Create the builder with default config.
607 pub fn new() -> Self {
608 Self {
609 event_interval: 61,
610 self_wake_limit: 10_000,
611 self_wake_time: Duration::from_secs(1),
612 }
613 }
614
615 /// Sets the number of scheduler ticks after which the scheduler will poll
616 /// for external events (timers, I/O, and so on).
617 ///
618 /// A scheduler “tick” roughly corresponds to one poll invocation on a task.
619 /// Values below 1 are treated as 1.
620 pub fn event_interval(&mut self, val: usize) -> &mut Self {
621 self.event_interval = val.max(1);
622 self
623 }
624
625 /// Sets the self-wake streak that is reported as a possible busy loop.
626 ///
627 /// A task that wakes itself on every poll, for example a loop around
628 /// `wake_by_ref()` or a yield, never waits for an event. The runtime logs
629 /// a warning with the task spawn location once the task wakes itself
630 /// `count` times in a row and the streak lasts at least `duration`. Each
631 /// streak is reported once. A count of 0 disables the warning and per-task
632 /// streak tracking, but preserves [`Runtime::self_wake_count`].
633 ///
634 /// By default, 10 000 self-wakes over at least 1 second.
635 pub fn self_wake_threshold(&mut self, count: u32, duration: Duration) -> &mut Self {
636 self.self_wake_limit = count.min(!REPORTED);
637 self.self_wake_time = duration;
638 self
639 }
640
641 /// Build [`Runtime`].
642 pub fn build(&self, handle: Box<dyn Notify>) -> Runtime {
643 Runtime::with_builder(self, handle)
644 }
645}
646
647#[cfg(test)]
648mod task_tests;
649
650#[cfg(all(test, ntex_loom))]
651mod task_loom;
652
653#[cfg(test)]
654mod tests;