nuclei/compat.rs
1//! The executor API of earlier nuclei versions: [`init`], the
2//! [`GlobalExecutorConfig`] builder, and growing or shrinking the default
3//! runtime's workers. Each maps onto the process default
4//! [`Runtime`](crate::Runtime).
5//!
6//! It stays at the crate root, with [`Proactor`](crate::Proactor) and
7//! [`drive`](crate::drive), so code written against those versions builds;
8//! new code reaches for [`Builder`](crate::Builder) (with
9//! [`build_global`](crate::Builder::build_global), which answers its
10//! error), [`RuntimeHandle`](crate::RuntimeHandle) and
11//! [`block_on`](crate::block_on). Its shapes are kept as they were:
12//! [`spawn_more_threads`] is async though nothing in it waits, and
13//! [`stop_thread`] answers a [`Task`] that dropping cancels before it stops
14//! anything.
15
16use std::fmt;
17use std::io;
18
19use crate::rt::runtime::{self, Builder};
20use crate::rt::task::Task;
21use crate::rt::workers::Workers;
22
23/// How the process default runtime is set up by [`init_with_config`].
24#[derive(Default)]
25pub struct GlobalExecutorConfig {
26 env_var: Option<&'static str>,
27 min_threads: Option<usize>,
28 max_threads: Option<usize>,
29 thread_name_fn: Option<Box<dyn Fn() -> String + Send + Sync>>,
30}
31
32impl fmt::Debug for GlobalExecutorConfig {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.debug_struct("GlobalExecutorConfig")
35 .field("env_var", &self.env_var)
36 .field("min_threads", &self.min_threads)
37 .field("max_threads", &self.max_threads)
38 .finish_non_exhaustive()
39 }
40}
41
42impl GlobalExecutorConfig {
43 /// Reads the number of workers to start from the environment variable
44 /// `env_var`, before [`with_min_threads`](Self::with_min_threads).
45 pub fn with_env_var(mut self, env_var: &'static str) -> Self {
46 self.env_var = Some(env_var);
47 self
48 }
49
50 /// Starts this many workers (default: one per core), the fewest
51 /// [`stop_thread`] leaves running.
52 pub fn with_min_threads(mut self, min_threads: usize) -> Self {
53 self.min_threads = Some(min_threads);
54 self
55 }
56
57 /// The most workers the runtime may run, [`spawn_more_threads`]
58 /// included (default: four times the minimum). Zero runs every task on
59 /// the thread inside `block_on`, with no worker at all.
60 pub fn with_max_threads(mut self, max_threads: usize) -> Self {
61 self.max_threads = Some(max_threads);
62 self
63 }
64
65 /// Names each worker thread.
66 pub fn with_thread_name_fn(
67 mut self,
68 thread_name_fn: impl Fn() -> String + Send + Sync + 'static,
69 ) -> Self {
70 self.thread_name_fn = Some(Box::new(thread_name_fn));
71 self
72 }
73
74 /// The runtime builder this configuration describes.
75 fn builder(self) -> Builder {
76 let from_env = self
77 .env_var
78 .and_then(|name| std::env::var(name).ok())
79 .and_then(|threads| threads.parse::<usize>().ok());
80 let min = from_env
81 .or(self.min_threads)
82 .unwrap_or_else(|| std::thread::available_parallelism().map_or(1, usize::from));
83 let mut builder = match self.max_threads {
84 Some(0) => Builder::new().worker_threads(0),
85 max => {
86 let min = min.max(1);
87 Builder::new()
88 .worker_threads(min)
89 .max_worker_threads(max.unwrap_or(min.saturating_mul(4)).max(min))
90 }
91 };
92 if let Some(name) = self.thread_name_fn {
93 builder = builder.thread_name_fn(name);
94 }
95 builder
96 }
97}
98
99/// Starts the process default runtime now, with every setting at its
100/// default, unless it is running already.
101pub fn init() {
102 runtime::default_runtime();
103}
104
105/// Starts the process default runtime now, as `config` says, unless it is
106/// running already (then `config` is not used).
107///
108/// A runtime that cannot start is reported at the `error` level of the
109/// [`log`](https://docs.rs/log) facade, and the default runtime stays
110/// unstarted: the next call that needs it starts it with every setting at
111/// its default. [`Builder::build_global`](crate::Builder::build_global)
112/// answers the error instead.
113pub fn init_with_config(config: GlobalExecutorConfig) {
114 match config.builder().build_global() {
115 Ok(_) => {}
116 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {}
117 Err(err) => log::error!("the default runtime could not start as configured: {err}"),
118 }
119}
120
121/// Starts up to `count` more workers on the runtime this thread is inside
122/// (or the default one), within its maximum; returns how many started.
123///
124/// # Errors
125///
126/// When a worker's poller cannot be made or its thread cannot start.
127pub async fn spawn_more_threads(count: usize) -> io::Result<usize> {
128 Workers::start(&runtime::current(), count)
129}
130
131/// Stops one worker of the runtime this thread is inside (or the default
132/// one), unless only the runtime's minimum runs: it takes no new task from
133/// now on and exits once the tasks it has are done. The task completes with
134/// whether a worker was stopped, once it takes no new task.
135pub fn stop_thread() -> Task<bool> {
136 crate::runtime::spawn(async { runtime::current().workers.stop_one() })
137}
138
139/// Stops the worker this task runs on, as [`stop_thread`] stops one: false
140/// on a thread that is not a worker of its runtime, or when only the
141/// minimum runs.
142pub fn stop_current_thread() -> Task<bool> {
143 crate::runtime::spawn_local(async {
144 let inner = runtime::current();
145 crate::rt::context::current(|current| {
146 current
147 .filter(|shard| shard.worker_index(inner.id()).is_some())
148 .is_some_and(|shard| inner.workers.stop(&shard.shared))
149 })
150 })
151}