Skip to main content

nuclei/rt/
runtime.rs

1//! Runtimes: worker shards, the blocking pool, and where `spawn` puts a task.
2//!
3//! A runtime has one worker shard per thread it was built with, each a thread
4//! running its shard's loop until shutdown, and a blocking pool. `spawn` on a
5//! worker keeps the task there unless the worker holds more than a margin
6//! of live tasks beyond a shard picked at random, which then takes it. Any
7//! other thread has a home shard, where its spawns went last, kept by the
8//! same rule (its first spawn takes the less loaded of two distinct shards
9//! picked at random, the power of two choices), so tasks spawned together
10//! run together. A task never moves after.
11//! With no worker threads, a task goes to the shard of the thread that
12//! spawns it, and runs while that thread is inside `block_on`.
13//!
14//! Any thread may call `block_on`. On a worker it runs a nested loop on the
15//! worker's shard; elsewhere it runs on the thread's own shard, made on
16//! first use, with the runtime set as the one `spawn` uses meanwhile.
17//!
18//! The process default runtime starts on first use with one worker per core
19//! the process may run on and every setting at its default;
20//! [`Builder::build_global`] replaces those settings before that.
21
22use core::future::Future;
23use std::fmt;
24use std::io;
25use std::sync::{Arc, OnceLock};
26use std::time::Duration;
27
28use kovan_channel::flavors::RecvDeadline;
29
30use super::blocking::Pool;
31use super::context;
32use super::sched::Sched;
33use super::shard::{Shard, Shared};
34use super::task::{self, ScheduleInfo, Task};
35use super::workers::Workers;
36
37mod builder;
38mod cores;
39mod enter;
40mod fallible;
41pub(crate) mod io_knobs;
42mod knobs;
43mod panic;
44mod placement;
45mod stats;
46pub use self::builder::Builder;
47pub use self::enter::{EnterGuard, RuntimeId, TryCurrentError, worker_index};
48pub(crate) use self::fallible::{try_current, try_spawn, try_spawn_blocking, try_spawn_local};
49pub use super::rng::RngSeed;
50pub use super::tuning::{TaskMeta, UnhandledPanic};
51
52/// What a runtime's threads share.
53pub(crate) struct Inner {
54    /// Tells this runtime's workers from another's.
55    id: u64,
56    pub(crate) workers: Workers,
57    pub(crate) pool: Arc<Pool>,
58    /// How many live tasks more than the other choice the spawning shard
59    /// may hold and still keep a task it spawns.
60    local_margin: usize,
61    /// The workers' IO runs on rings.
62    #[cfg(ring)]
63    rings: bool,
64    /// The time its tasks and timers read.
65    pub(crate) clock: crate::time::clock::Clock,
66    /// The shards of threads inside this runtime's `block_on` while its clock
67    /// is paused, by address: with the workers, the shards a paused clock
68    /// waits for before it moves by itself.
69    guests: OnceLock<kovan_map::HashMap<usize, Arc<Shared>>>,
70    /// The task hooks every task of this runtime carries, if any is set.
71    task_hooks: Option<Arc<super::tuning::TaskHooks>>,
72    /// The runtime shuts down when a task panics; and once one did.
73    shutdown_on_panic: bool,
74    panicked: std::sync::atomic::AtomicBool,
75    /// Wakes the runtime's `block_on` calls when a task panicked.
76    on_panic: crate::sync::Notify,
77    /// Seeds the generator of a thread in this runtime's `block_on`.
78    rng_seed: Option<RngSeed>,
79    /// What dropped handles left to finish, once no worker takes it.
80    lingering: super::linger::Lingering,
81    /// What the runtime's handles read of its builder.
82    pub(crate) io: io_knobs::IoKnobs,
83}
84
85/// A runtime: worker shards and a blocking pool. Dropping it stops the
86/// workers, drops the tasks they still hold, and waits for the threads to
87/// exit; then, on the dropping thread, for what the handles those tasks held
88/// left behind (the bytes a socket's write-behind holds, a file's writes) to
89/// land, each for at most a minute, so nothing they accepted is lost.
90pub struct Runtime {
91    inner: Arc<Inner>,
92    /// One message per worker that exited.
93    exits: kovan_channel::unbounded::Receiver<()>,
94}
95
96/// A cloneable handle to a [`Runtime`], to spawn on it from anywhere. It
97/// does not keep the runtime running.
98#[derive(Clone)]
99pub struct RuntimeHandle {
100    inner: Arc<Inner>,
101}
102
103impl Runtime {
104    /// A builder with every setting at its default.
105    pub fn builder() -> Builder {
106        Builder::new()
107    }
108
109    /// Starts a runtime with every setting at its default.
110    ///
111    /// # Errors
112    ///
113    /// As for [`Builder::build`].
114    pub fn new() -> io::Result<Self> {
115        Builder::new().build()
116    }
117
118    /// A handle to spawn on this runtime from anywhere.
119    pub fn handle(&self) -> RuntimeHandle {
120        RuntimeHandle {
121            inner: Arc::clone(&self.inner),
122        }
123    }
124
125    /// Runs `future` to its end on the calling thread; see [`block_on`].
126    pub fn block_on<F: Future>(&self, future: F) -> F::Output {
127        block_on_with(&self.inner, future)
128    }
129
130    /// Spawns a task on one of the runtime's shards; see [`spawn`].
131    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
132    where
133        F: Future + Send + 'static,
134        F::Output: Send + 'static,
135    {
136        spawn_with(&self.inner, future)
137    }
138
139    /// Runs `f` on the runtime's blocking pool; see [`spawn_blocking`].
140    pub fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
141    where
142        F: FnOnce() -> T + Send + 'static,
143        T: Send + 'static,
144    {
145        self.inner.pool.spawn(f)
146    }
147
148    /// Stops the workers and waits for them up to `timeout`; workers still
149    /// running a task then are left to exit on their own. What the handles
150    /// of the tasks dropped left behind gets the rest of `timeout` to land,
151    /// on this thread; what has not by then is given up.
152    pub fn shutdown_timeout(mut self, timeout: Duration) {
153        let deadline = std::time::Instant::now() + timeout;
154        self.stop_and_join(Some(timeout));
155        self.finish_lingering(Some(deadline));
156    }
157
158    /// Runs what dropped handles left behind until every part handed to the
159    /// runtime ended and nothing more can come (no file write the workers
160    /// issued is still to wake its task, no task woken on a stopped worker
161    /// is still to be dropped), for at most a linger's time (each part is
162    /// bounded by its own) or until `deadline`; on this thread, its runtime
163    /// set to this one, so a file's calls run on this blocking pool. Then
164    /// what is left, and the lingers handed later, run on threads of their
165    /// own.
166    fn finish_lingering(&self, deadline: Option<std::time::Instant>) {
167        // Once: a shutdown that ran it (or gave it up) leaves its drop none.
168        if self.inner.lingering.is_over() {
169            return;
170        }
171        if !self.inner.lingers_done() {
172            let linger = std::time::Instant::now() + self.inner.io.linger;
173            let deadline = deadline.map_or(linger, |d| d.min(linger));
174            let _ = block_on_here(
175                &self.inner,
176                crate::time::timeout_at(deadline, self.inner.run_lingers()),
177            );
178        }
179        self.inner.lingering.end();
180    }
181
182    fn stop_and_join(&mut self, timeout: Option<Duration>) {
183        // Lingers handed from now on wait for this drop, never for a worker.
184        self.inner.lingering.close();
185        self.inner.workers.stop_all();
186        let threads = self.inner.workers.take_threads();
187        if let Some(timeout) = timeout {
188            let deadline = std::time::Instant::now() + timeout;
189            // Workers that exited already sent theirs; one message each.
190            for thread in &threads {
191                if thread.is_finished() {
192                    continue;
193                }
194                if !matches!(self.exits.recv_deadline(deadline), RecvDeadline::Msg(())) {
195                    break;
196                }
197            }
198            // A worker that did not exit in time is left to exit on its own.
199            for thread in threads {
200                if thread.is_finished() {
201                    let _ = thread.join();
202                }
203            }
204        } else {
205            for thread in threads {
206                let _ = thread.join();
207            }
208        }
209    }
210}
211
212#[cfg(test)]
213impl Runtime {
214    pub(crate) fn inner(&self) -> &Arc<Inner> {
215        &self.inner
216    }
217}
218
219impl Drop for Runtime {
220    fn drop(&mut self) {
221        self.stop_and_join(None);
222        self.finish_lingering(None);
223    }
224}
225
226impl fmt::Debug for Runtime {
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        f.debug_struct("Runtime")
229            .field("worker_threads", &self.inner.workers.running())
230            .finish_non_exhaustive()
231    }
232}
233
234impl RuntimeHandle {
235    /// Runs `future` to its end on the calling thread; see [`block_on`].
236    pub fn block_on<F: Future>(&self, future: F) -> F::Output {
237        block_on_with(&self.inner, future)
238    }
239
240    /// Spawns a task on one of the runtime's shards; see [`spawn`].
241    pub fn spawn<F>(&self, future: F) -> Task<F::Output>
242    where
243        F: Future + Send + 'static,
244        F::Output: Send + 'static,
245    {
246        spawn_with(&self.inner, future)
247    }
248
249    /// Runs `f` on the runtime's blocking pool; see [`spawn_blocking`].
250    pub fn spawn_blocking<F, T>(&self, f: F) -> Task<T>
251    where
252        F: FnOnce() -> T + Send + 'static,
253        T: Send + 'static,
254    {
255        self.inner.pool.spawn(f)
256    }
257
258    /// The handle of the runtime this thread is inside, or of the default
259    /// runtime (started now if it is not running yet).
260    pub fn current() -> Self {
261        Self { inner: current() }
262    }
263
264    pub(crate) fn inner(&self) -> &Arc<Inner> {
265        &self.inner
266    }
267}
268
269impl fmt::Debug for RuntimeHandle {
270    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271        f.debug_struct("RuntimeHandle")
272            .field("worker_threads", &self.inner.workers.running())
273            .finish_non_exhaustive()
274    }
275}
276
277/// The process default runtime, started on first use.
278static DEFAULT: OnceLock<Runtime> = OnceLock::new();
279
280impl Inner {
281    /// The guests, made at the first join: a runtime whose clock never pauses has none.
282    pub(crate) fn guests(&self) -> &kovan_map::HashMap<usize, Arc<Shared>> {
283        self.guests
284            .get_or_init(|| kovan_map::HashMap::with_capacity(builder::GUESTS_CAPACITY))
285    }
286
287    /// The guests, if any ever joined.
288    pub(crate) fn guests_made(&self) -> Option<&kovan_map::HashMap<usize, Arc<Shared>>> {
289        self.guests.get()
290    }
291    pub(crate) fn id(&self) -> u64 {
292        self.id
293    }
294
295    /// What dropped handles left this runtime to finish.
296    pub(crate) fn lingering(&self) -> &super::linger::Lingering {
297        &self.lingering
298    }
299}
300
301/// The process default runtime, started now if it is not running.
302#[expect(
303    clippy::panic,
304    reason = "no caller to hand the error to; documented under `# Panics`"
305)]
306pub(crate) fn default_runtime() -> &'static Runtime {
307    fallible::try_default()
308        .unwrap_or_else(|err| panic!("the default runtime could not start: {err}"))
309}
310
311/// The runtime this thread is inside, or the default one.
312pub(crate) fn current() -> Arc<Inner> {
313    context::runtime().unwrap_or_else(|| Arc::clone(&default_runtime().inner))
314}
315
316/// Runs `f` on the clock of the runtime this thread is inside, or of the
317/// default runtime if it runs; `None` when there is neither.
318pub(crate) fn with_clock<R>(f: impl FnOnce(Option<&crate::time::clock::Clock>) -> R) -> R {
319    match context::runtime() {
320        Some(inner) => f(Some(&inner.clock)),
321        None => f(DEFAULT.get().map(|rt| &rt.inner.clock)),
322    }
323}
324
325/// The runtime this thread is inside, or the default one if it runs: never
326/// started here (a thread on its way out may ask).
327fn running() -> Option<Arc<Inner>> {
328    context::runtime().or_else(|| DEFAULT.get().map(|rt| Arc::clone(&rt.inner)))
329}
330
331/// [`running`], for a linger's hand-off.
332pub(crate) fn running_inner() -> Option<Arc<Inner>> {
333    running()
334}
335
336/// Spawns a linger on `inner`: on a worker that takes tasks, else (with
337/// `here`) on the shard of this thread (which runs it at its next
338/// `block_on`); the linger back where neither can take it.
339pub(crate) fn spawn_linger(
340    inner: &Arc<Inner>,
341    linger: super::linger::Guarded,
342    here: bool,
343) -> Result<(), super::linger::Guarded> {
344    if let Some(shard) = inner.workers.place(inner.id, inner.local_margin) {
345        spawn_on(inner, shard, linger).detach();
346        return Ok(());
347    }
348    // With no workers, the thread's own shard, if it has one: one is never
349    // made here (a thread on its way out may hand a linger).
350    let running = context::current(|current| current.is_some());
351    if here && (running || context::own_shard_if_made().is_some()) {
352        let _runtime = context::set_runtime(inner);
353        spawn_here(linger, false).detach();
354        return Ok(());
355    }
356    Err(linger)
357}
358
359/// A shard to serve a thread that runs none, of the runtime this thread is
360/// inside or of the default one, if one runs: it never starts one (a thread
361/// on its way out may ask).
362pub(crate) fn running_service_shard() -> Option<Arc<Shared>> {
363    let inner = running()?;
364    inner
365        .workers
366        .place(inner.id, inner.local_margin)
367        .map(Arc::clone)
368}
369
370/// Whether the shard [`service_shard`] hands out runs a ring: the current
371/// (or default) runtime's workers do, or with no workers, this thread's own
372/// shard.
373#[cfg(ring)]
374pub(crate) fn service_rings() -> bool {
375    let inner = current();
376    if inner.workers.running() > 0 {
377        inner.rings
378    } else {
379        super::backend::chosen().is_ok_and(|b| b.rings())
380    }
381}
382
383/// A shard to serve a thread that runs none (its timers, the registrations
384/// of its IO): one of the current (or default) runtime's workers, the less
385/// loaded of two; with no workers, the calling thread's own shard.
386pub(crate) fn service_shard() -> Arc<Shared> {
387    let inner = current();
388    match inner.workers.place(inner.id, inner.local_margin) {
389        Some(shard) => Arc::clone(shard),
390        None => Arc::clone(&own_shard().shared),
391    }
392}
393
394/// This thread's own shard, started now if it is not running.
395#[expect(
396    clippy::panic,
397    reason = "no caller to hand the error to; documented under `# Panics`"
398)]
399fn own_shard() -> std::rc::Rc<Shard> {
400    match context::own_shard() {
401        Ok(shard) => shard,
402        Err(err) => panic!("this thread's shard could not start: {err}"),
403    }
404}
405
406/// Spawns `future` on `inner`.
407#[inline(always)]
408fn spawn_with<F>(inner: &Inner, future: F) -> Task<F::Output>
409where
410    F: Future + Send + 'static,
411    F::Output: Send + 'static,
412{
413    let Some(shard) = inner.workers.place(inner.id, inner.local_margin) else {
414        // No worker takes tasks: the spawning thread's own shard runs it.
415        return spawn_here(future, false);
416    };
417    spawn_on(inner, shard, future)
418}
419
420/// Spawns `future` on `inner`'s worker `shard`.
421#[inline(always)]
422fn spawn_on<F>(inner: &Inner, shard: &Arc<Shared>, future: F) -> Task<F::Output>
423where
424    F: Future + Send + 'static,
425    F::Output: Send + 'static,
426{
427    let hooks = inner.task_hooks.as_ref();
428    let (runnable, task) = task::spawn(
429        future,
430        Sched::counted(Arc::clone(shard), None, hooks.cloned()),
431    );
432    spawned(hooks.map(|hooks| &**hooks), &task);
433    // As its schedule would, without the turn through the task's table.
434    if let Some(runnable) = super::sched::queue_here(shard, runnable, ScheduleInfo::new(false)) {
435        runnable.schedule();
436    }
437    task
438}
439
440/// Calls the spawn hook, if the task's runtime has one, with the name a
441/// `task::Builder` gives the task in place first, so every hook sees it.
442#[inline(always)]
443fn spawned<T>(hooks: Option<&super::tuning::TaskHooks>, task: &Task<T>) {
444    if let Some(hooks) = hooks {
445        spawned_with(hooks, task.id());
446    }
447}
448
449/// [`spawned`] for a runtime with task hooks: out of line, so a spawn with
450/// none carries none of it.
451#[cold]
452#[inline(never)]
453fn spawned_with(hooks: &super::tuning::TaskHooks, id: task::Id) {
454    crate::task::name_now(id);
455    super::tuning::call(hooks.spawn.as_ref(), id);
456}
457
458/// The task hooks of the runtime set on this thread, looked for only while
459/// some runtime of the process has any.
460fn context_hooks() -> Option<Arc<super::tuning::TaskHooks>> {
461    if !super::tuning::ANY_TASK_HOOKS.load(std::sync::atomic::Ordering::Acquire) {
462        return None;
463    }
464    context::runtime().and_then(|inner| inner.task_hooks.clone())
465}
466
467/// Spawns `future` on the shard of the calling thread: the worker it runs,
468/// or the thread's own. `bound`: the future is not `Send` and must stay.
469fn spawn_here<F>(future: F, bound: bool) -> Task<F::Output>
470where
471    F: Future + 'static,
472    F::Output: 'static,
473{
474    let place = |shard: &Shard| {
475        (
476            Arc::clone(&shard.shared),
477            bound.then(|| context::home_for(shard)),
478        )
479    };
480    let (shared, home) =
481        context::current(|current| current.map(place)).unwrap_or_else(|| place(&own_shard()));
482    let hooks = context_hooks();
483    let (runnable, task, bound_to) = if let Some(home) = home {
484        let at = Arc::as_ptr(&home);
485        // SAFETY: the task's schedule sends a woken bound task only to its
486        // home's thread (leaked rather than dropped if that thread is gone);
487        // the future is 'static.
488        let (runnable, task) = unsafe {
489            task::spawn_unchecked(future, Sched::counted(shared, Some(home), hooks.clone()))
490        };
491        (runnable, task, Some(at))
492    } else {
493        // SAFETY: the task's schedule sends a woken task of this shard back
494        // to it; the future is 'static, and `Send` (a caller with `bound`
495        // false is `spawn`'s, whose future is).
496        let (runnable, task) =
497            unsafe { task::spawn_unchecked(future, Sched::counted(shared, None, hooks.clone())) };
498        (runnable, task, None)
499    };
500    spawned(hooks.as_deref(), &task);
501    // A bound task as its schedule would queue it, without the turn through
502    // the task's table.
503    let left = match bound_to {
504        Some(home) => {
505            // SAFETY: the task's schedule holds its home while the runnable
506            // lives.
507            let home = unsafe { &*home };
508            super::sched::queue_home_here(home, runnable, ScheduleInfo::new(false))
509        }
510        None => Some(runnable),
511    };
512    if let Some(runnable) = left {
513        runnable.schedule();
514    }
515    task
516}
517
518/// Runs `future` on the calling thread with `inner` as the runtime `spawn`
519/// uses meanwhile.
520fn block_on_with<F: Future>(inner: &Arc<Inner>, future: F) -> F::Output {
521    if inner.shutdown_on_panic {
522        return block_on_here(inner, panic::watched(inner, future));
523    }
524    block_on_here(inner, future)
525}
526
527fn block_on_here<F: Future>(inner: &Arc<Inner>, future: F) -> F::Output {
528    let _runtime = context::set_runtime(inner);
529    // Pinned here once: the calls below take it by reference.
530    let mut future = core::pin::pin!(future);
531    // On the shard this thread runs, the call nests, inside the shard.
532    if let Some(output) =
533        context::current(|current| current.map(|shard| shard.block_on(future.as_mut())))
534    {
535        return output;
536    }
537    let shard = own_shard();
538    let _current = context::enter(&shard);
539    let _guest = super::paused::Guest::enter(inner, &shard.shared);
540    // A seeded runtime's choices made here repeat, call after call.
541    let _seeded = inner
542        .rng_seed
543        .map(|seed| super::rng::Reseeded::new(seed, 0));
544    #[cfg(port)]
545    shard.take_port_back();
546    shard.block_on_then_leave(future.as_mut())
547}
548
549/// Runs `future` to its end on the calling thread, running other tasks of
550/// the thread's shard meanwhile.
551///
552/// On a worker thread (inside a task) the call nests: the worker's shard
553/// keeps running its other tasks, timers and IO until `future` completes.
554/// The task that called it is not polled again until the call returns, so a
555/// `future` that waits for that very task never completes.
556///
557/// # Panics
558///
559/// When nested deeper than the runtime's `max_nesting` (256 by default), or
560/// when the thread's shard cannot start (its poller cannot be made).
561pub fn block_on<F: Future>(future: F) -> F::Output {
562    block_on_with(&current(), future)
563}
564
565/// Spawns a task on the runtime this thread is inside, or on the default
566/// runtime. Spawned on a worker, the task stays on that worker unless it
567/// holds more live tasks than another shard by the runtime's
568/// [`local_spawn_margin`](Builder::local_spawn_margin); spawned anywhere
569/// else, it goes to the spawning thread's home shard by the same rule (the
570/// first spawn takes the less loaded of two shards picked at random). The
571/// task stays on its shard.
572///
573/// Dropping the returned [`Task`] cancels it; call [`Task::detach`] to let
574/// it run on its own. Awaiting a task that was cancelled (through an
575/// [`AbortHandle`](crate::task::AbortHandle), or by its runtime's shutdown)
576/// panics; [`Task::join`] and [`Task::fallible`] answer it instead.
577///
578/// # Panics
579///
580/// When the default runtime or this thread's shard cannot start (its
581/// poller cannot be made); [`task::Builder::spawn`](crate::task::Builder::spawn)
582/// answers the error instead.
583#[inline]
584pub fn spawn<F>(future: F) -> Task<F::Output>
585where
586    F: Future + Send + 'static,
587    F::Output: Send + 'static,
588{
589    // With no task hooks nothing the spawn runs is the user's, so nothing
590    // sets or unsets the thread's runtime meanwhile: it is used in place.
591    let spawned = context::with_runtime(|inner| match inner {
592        Some(inner) if inner.task_hooks.is_none() => Ok(spawn_with(inner, future)),
593        _ => Err(future),
594    });
595    spawned.unwrap_or_else(spawn_current)
596}
597
598/// [`spawn`] on the runtime [`current`] finds: out of line, so the spawn of a
599/// thread whose runtime is set and has no hooks carries none of it.
600#[inline(never)]
601fn spawn_current<F>(future: F) -> Task<F::Output>
602where
603    F: Future + Send + 'static,
604    F::Output: Send + 'static,
605{
606    spawn_with(&current(), future)
607}
608
609/// Spawns a task that stays on the calling thread: a worker's shard, or the
610/// thread's own (where it runs while the thread is inside `block_on`). The
611/// future need not be `Send`.
612///
613/// # Panics
614///
615/// When this thread's shard cannot start (its poller cannot be made);
616/// [`task::Builder::spawn_local`](crate::task::Builder::spawn_local) answers
617/// the error instead.
618pub fn spawn_local<F>(future: F) -> Task<F::Output>
619where
620    F: Future + 'static,
621    F::Output: 'static,
622{
623    spawn_here(future, true)
624}
625
626/// Runs `f` on the blocking pool of the runtime this thread is inside, or of
627/// the default runtime, so it never holds a shard up.
628///
629/// # Panics
630///
631/// When the default runtime cannot start.
632pub fn spawn_blocking<F, T>(f: F) -> Task<T>
633where
634    F: FnOnce() -> T + Send + 'static,
635    T: Send + 'static,
636{
637    current().pool.spawn(f)
638}
639
640/// Runs `future` as a task of the runtime's own, left out of its shard's
641/// count of live tasks so it keeps no worker from draining: on the shard the
642/// calling thread runs, else on a shard that serves threads that run none.
643#[cfg(unix)]
644pub(crate) fn spawn_service<F>(future: F)
645where
646    F: Future<Output = ()> + Send + 'static,
647{
648    let shared = context::current(|current| current.map(|s| Arc::clone(&s.shared)))
649        .unwrap_or_else(service_shard);
650    // SAFETY: as in `spawn_here`: the schedule sends a woken task back to
651    // its shard; the future is `Send` and 'static.
652    let (runnable, task) = unsafe {
653        // The runtime's own service task: not a task of its user's.
654        task::spawn_unchecked(future, Sched::uncounted(shared))
655    };
656    runnable.schedule();
657    task.detach();
658}
659
660/// Runs `f` on the blocking pool of the runtime this thread is inside, if
661/// it is inside one (it never starts the default runtime, which a thread on
662/// its way out could not): whether `f` went.
663#[cfg(port)]
664pub(crate) fn spawn_blocking_inside(f: impl FnOnce() + Send + 'static) -> bool {
665    match blocking_pool_inside() {
666        Some(pool) => {
667            pool.spawn(f).detach();
668            true
669        }
670        None => false,
671    }
672}
673
674/// The blocking pool of the runtime this thread is inside, if it is inside one; never the
675/// default runtime's, which it does not start.
676#[cfg(completion)]
677pub(crate) fn blocking_pool_inside() -> Option<Arc<super::blocking::Pool>> {
678    context::runtime().map(|inner| Arc::clone(&inner.pool))
679}
680
681/// The cores this process may run on, in order (Linux; Windows, in the
682/// process's processor group); empty where that is not known, so nothing
683/// is pinned.
684pub(crate) fn allowed_cores() -> Vec<usize> {
685    #[cfg(target_os = "linux")]
686    {
687        current_mask().map_or_else(Vec::new, |set| {
688            (0..libc::CPU_SETSIZE as usize)
689                // SAFETY: a plain read of a set of our own.
690                .filter(|&cpu| unsafe { libc::CPU_ISSET(cpu, &set) })
691                .collect()
692        })
693    }
694    #[cfg(windows)]
695    {
696        use crate::syscore::windows::{GetCurrentProcess, GetProcessAffinityMask};
697        let (mut process, mut system) = (0usize, 0usize);
698        // SAFETY: plain out-parameters of our own.
699        if unsafe { GetProcessAffinityMask(GetCurrentProcess(), &mut process, &mut system) } != 0 {
700            return (0..usize::BITS as usize)
701                .filter(|&cpu| process & (1 << cpu) != 0)
702                .collect();
703        }
704        Vec::new()
705    }
706    #[cfg(not(any(target_os = "linux", windows)))]
707    {
708        Vec::new()
709    }
710}
711
712/// A thread's CPU affinity mask.
713#[cfg(target_os = "linux")]
714pub(crate) type CpuMask = libc::cpu_set_t;
715/// A thread's CPU affinity mask: one bit per core of the process's group.
716#[cfg(windows)]
717pub(crate) type CpuMask = usize;
718/// A thread's CPU affinity mask; none is kept where threads are not pinned.
719#[cfg(not(any(target_os = "linux", windows)))]
720pub(crate) type CpuMask = ();
721
722/// The calling thread's affinity mask, `None` where it is not known.
723pub(crate) fn current_mask() -> Option<CpuMask> {
724    #[cfg(target_os = "linux")]
725    {
726        // SAFETY: a plain syscall into a zeroed set of our own.
727        unsafe {
728            let mut set: libc::cpu_set_t = core::mem::zeroed();
729            (libc::sched_getaffinity(0, size_of::<libc::cpu_set_t>(), &mut set) == 0).then_some(set)
730        }
731    }
732    #[cfg(not(target_os = "linux"))]
733    None
734}
735
736/// Runs the calling thread on `mask`. A failure leaves the mask it had,
737/// which costs locality and nothing else.
738pub(crate) fn apply_mask(mask: &CpuMask) {
739    #[cfg(target_os = "linux")]
740    // SAFETY: a plain syscall with a set of our own.
741    let _ = unsafe { libc::sched_setaffinity(0, size_of::<libc::cpu_set_t>(), mask) };
742    #[cfg(windows)]
743    {
744        use crate::syscore::windows::{GetCurrentThread, SetThreadAffinityMask};
745        // SAFETY: the pseudo-handle of the calling thread, and a mask.
746        unsafe { SetThreadAffinityMask(GetCurrentThread(), *mask) };
747    }
748    #[cfg(not(any(target_os = "linux", windows)))]
749    let _ = mask;
750}
751
752/// Pins the calling thread to `core` (Linux, Windows). A failure leaves it
753/// unpinned, which costs locality and nothing else.
754pub(crate) fn pin_to(core: usize) {
755    #[cfg(target_os = "linux")]
756    // SAFETY: a plain syscall with a set of our own.
757    unsafe {
758        let mut set: libc::cpu_set_t = core::mem::zeroed();
759        libc::CPU_SET(core, &mut set);
760        let _ = libc::sched_setaffinity(0, size_of::<libc::cpu_set_t>(), &set);
761    }
762    #[cfg(windows)]
763    if core < usize::BITS as usize {
764        use crate::syscore::windows::{GetCurrentThread, SetThreadAffinityMask};
765        // SAFETY: the pseudo-handle of the calling thread, and a mask.
766        unsafe { SetThreadAffinityMask(GetCurrentThread(), 1 << core) };
767    }
768    #[cfg(not(any(target_os = "linux", windows)))]
769    let _ = core;
770}