pub struct Builder { /* private fields */ }Expand description
Builds a Runtime. Every setting is optional: an unset one takes the
default, which needs no configuration to work well.
let runtime = nuclei::Builder::new().worker_threads(2).build().unwrap();
assert_eq!(runtime.block_on(async { 1 + 1 }), 2);Implementations§
Source§impl Builder
impl Builder
Sourcepub fn worker_threads(self, n: usize) -> Self
pub fn worker_threads(self, n: usize) -> Self
Worker threads, each running one shard. The default is one per core
of worker_cores when it is set, else one per
core the process may run on, held to
max_worker_threads when that is set.
Zero runs every task on the thread that spawns it, while that thread
is inside block_on.
Sourcepub fn thread_name(self, name: impl Into<String>) -> Self
pub fn thread_name(self, name: impl Into<String>) -> Self
The name of every thread the runtime starts (default nuclei-worker
and nuclei-blocking).
Sourcepub fn thread_name_fn(
self,
f: impl Fn() -> String + Send + Sync + 'static,
) -> Self
pub fn thread_name_fn( self, f: impl Fn() -> String + Send + Sync + 'static, ) -> Self
A function naming each thread the runtime starts.
Sourcepub fn thread_stack_size(self, bytes: usize) -> Self
pub fn thread_stack_size(self, bytes: usize) -> Self
The stack size of every thread the runtime starts, in bytes (default
8 MiB). At least 128 KiB; build fails with InvalidInput
below.
Sourcepub fn max_nesting(self, depth: usize) -> Self
pub fn max_nesting(self, depth: usize) -> Self
How deep block_on may nest on one thread (default 256). A call one
level deeper panics instead of running the thread out of stack. At least
1; build fails with InvalidInput below.
Sourcepub fn max_blocking_threads(self, n: usize) -> Self
pub fn max_blocking_threads(self, n: usize) -> Self
The most threads the blocking pool runs at once (default 512). At least 1;
build fails with InvalidInput below.
Sourcepub fn thread_keep_alive(self, keep_alive: Duration) -> Self
pub fn thread_keep_alive(self, keep_alive: Duration) -> Self
How long an idle blocking thread waits for work before it exits (default 10 seconds).
Sourcepub fn local_spawn_margin(self, margin: usize) -> Self
pub fn local_spawn_margin(self, margin: usize) -> Self
How many more live tasks than another shard a spawning thread’s shard may hold and still take a task it spawns (default 32). A task spawned on a worker stays on it (so a task that waits for what it spawned keeps it on its own thread), and a task spawned on any other thread goes to that thread’s home shard (where its spawns went last), unless that shard holds that many more tasks than a shard picked at random; then it goes to the other, which becomes the home. Zero balances strictly; a larger margin keeps more work together.
Sourcepub fn max_worker_threads(self, n: usize) -> Self
pub fn max_worker_threads(self, n: usize) -> Self
The most worker threads the runtime may start over its life, the
ones it starts with included (default four times the workers it
starts with): what spawn_more_threads may add up to. A stopped
worker’s place is not taken again. Below an explicit
worker_threads, build fails
with InvalidInput; with worker_threads unset, the runtime starts
with at most this many.
Sourcepub fn io_buffer_cap(self, bytes: usize) -> Self
pub fn io_buffer_cap(self, bytes: usize) -> Self
The most bytes each worker’s IO buffers hold (default 64 MiB). Reads
that had to wait land in these buffers, and writes that had to wait
are copied into them; at the cap, IO waits for buffers to come back,
so a worker’s IO memory never grows past it. On io_uring the worker’s
receive buffers count against the cap too
(recv_buffers of
recv_buffer_size
bytes, 4 MiB by default, with their descriptors), and the rest is
left for the others. A cap that leaves less than 512 bytes beside the
receive buffers is refused with InvalidInput when the worker starts;
otherwise any value is accepted, and a request that cannot fit under a
cap too small for it fails with an error that names this setting.
Sourcepub fn backend(self, backend: IoBackend) -> Self
pub fn backend(self, backend: IoBackend) -> Self
The kernel interface the workers run their IO on. Unset, it is the
process’s: the backend of
NucleiConfig, or io_uring where the
kernel lets this process have it and the platform’s poller otherwise.
A backend this process cannot have makes build fail
with the reason. A non-empty NUCLEI_BACKEND overrides it.
Sourcepub fn io_uring(self, config: IoUringConfiguration) -> Self
pub fn io_uring(self, config: IoUringConfiguration) -> Self
The settings the workers’ io_uring rings are made with, for this
runtime alone (default: the process’s, from
Proactor::with_config). Checked in
build, which fails with InvalidInput naming the
setting a ring cannot work with, on every platform; only the workers
of a runtime whose IO runs on io_uring make rings. Whether io_uring
is chosen when no backend is named is decided with these settings.
The shard of a thread inside block_on keeps the process’s settings:
it serves every runtime the thread enters.
Sourcepub fn start_paused(self, paused: bool) -> Self
pub fn start_paused(self, paused: bool) -> Self
Whether the runtime’s clock starts paused (default false): see
time::pause. Its tasks then see time move only
as they advance it, or as it jumps to the
next timer whenever every shard of the runtime is idle.
Sourcepub fn handoff_after(self, after: Option<Duration>) -> Self
pub fn handoff_after(self, after: Option<Duration>) -> Self
How long one poll of a task may run on a worker before the worker’s
other tasks go on without it (default 10 ms; None: never).
A task that blocks inside its poll (a blocking call, a long loop
without an .await) then holds up only itself: its worker’s shard,
with every other task, timer and IO of it, goes to another thread on
the same core, and the blocked task carries on once its poll returns.
At least 1 ms; build fails with InvalidInput below.
Tasks that spawn_local keeps on the blocked thread wait for it. The
watch costs each poll two plain stores and a load, and each call a
task makes into its worker (a wake, a spawn, a send or a receive) two
loads and two stores; with it on, an io_uring worker’s ring takes
submissions from any thread.
Sourcepub fn pin_workers(self, pin: bool) -> Self
pub fn pin_workers(self, pin: bool) -> Self
Whether each worker thread is pinned to its own core (Linux and
Windows; default true). A pinned shard keeps its cache warm; unpinned,
the scheduler may move it. With
worker_cores set, each worker is pinned to its
core of that list, or, unpinned, runs on any core of the list.
Sourcepub fn record_latencies(self, record: bool) -> Self
pub fn record_latencies(self, record: bool) -> Self
Counts how long each task poll runs, and (with the io-stats feature)
how long each IO operation waits for the kernel, by kind, in log2
histograms each worker’s ShardStats
carries (default false: no clock is read for it). The poll times cost
two clock reads and a call around every poll, of every runtime of the
process while one records them; the IO latencies two clock reads per
operation of this runtime’s workers.
Sourcepub fn count_io(self, count: bool) -> Self
pub fn count_io(self, count: bool) -> Self
Counts each worker’s IO operations (reads, writes, the bytes of each,
accepts) in its ShardStats::io
(default false: io is None). With the io-stats feature only,
which costs every IO call a few instructions, counted or not: off, a
count is one load of a flag per operation; on, a few plain
thread-local adds per operation and a drain each time a shard
publishes its counts, on every thread of the process while one
runtime counts.
Sourcepub fn build(self) -> Result<Runtime>
pub fn build(self) -> Result<Runtime>
Starts the runtime.
§Errors
InvalidInput naming a setting that no runtime can work with, or
that does not fit with another; the reason when the backend cannot
be had, a worker’s poller cannot be made or its thread cannot start.
Sourcepub fn build_global(self) -> Result<RuntimeHandle>
pub fn build_global(self) -> Result<RuntimeHandle>
Source§impl Builder
impl Builder
Sourcepub fn worker_cores(self, cores: impl IntoIterator<Item = usize>) -> Self
pub fn worker_cores(self, cores: impl IntoIterator<Item = usize>) -> Self
The cores the workers run on, in the order the workers take them:
worker k runs on cores[k % cores.len()], pinned to it, or, with
pin_workers(false), on any core of the list.
The worker count defaults to the list’s length. Every core must be
one the process may run on and the list must not be empty;
build fails with InvalidInput otherwise. Pinning
takes effect on Linux and Windows; elsewhere the list sets the
worker count and is checked against the cores the machine has.
Sourcepub fn blocking_cores(self, cores: impl IntoIterator<Item = usize>) -> Self
pub fn blocking_cores(self, cores: impl IntoIterator<Item = usize>) -> Self
The cores the blocking pool’s threads run on, any of them (default:
the cores of the thread that builds the runtime). Checked as
worker_cores are, and in effect on Linux and
Windows.
Source§impl Builder
impl Builder
Sourcepub fn write_behind_linger(self, linger: Duration) -> Self
pub fn write_behind_linger(self, linger: Duration) -> Self
How long the bytes a dropped handle wrote behind may take to reach the kernel (default
one minute): a task delivers them after the drop, as the kernel delivers what a closed
socket holds. Past it the rest is given up, and a TCP stream’s connection is reset, so its
peer reads an error rather than the end of a stream cut short. get_mut and
into_inner wait for the bytes as long at most, and the runtime’s drop waits for the
tasks. More than zero; build fails with InvalidInput at zero.
Sourcepub fn zero_copy_threshold(self, bytes: usize) -> Self
pub fn zero_copy_threshold(self, bytes: usize) -> Self
The least a write_zero_copy must have left
to send for its next send to go zero-copy, in bytes (default 1 MiB): below it, the kernel
copies the bytes, as it does for every write. Copying costs less than pinning the pages
for small writes; where the threshold pays depends on the machine and its network. Only
io_uring sends zero-copy, on the runtime’s workers: every value is accepted, and 0
sends every write zero-copy, usize::MAX none.
Sourcepub fn send_file_chunk(self, bytes: usize) -> Self
pub fn send_file_chunk(self, bytes: usize) -> Self
The most bytes one call of a send_file sends
(default 1 MiB). Each call runs on the blocking pool and may stall on a cold page there,
so this bounds how long a pool thread is held, and how much a cancelled send_file still
sends after its future went; where the system has no call for it, each chunk is read
into a buffer of this size. From 1 byte to 0x7ffff000 (what one sendfile(2) sends);
build fails with InvalidInput outside.
Source§impl Builder
impl Builder
Sourcepub fn on_thread_start(self, f: impl Fn() + Send + Sync + 'static) -> Self
pub fn on_thread_start(self, f: impl Fn() + Send + Sync + 'static) -> Self
Called on each thread the runtime starts (workers and blocking-pool threads), on that thread, before it does anything else.
Sourcepub fn on_thread_stop(self, f: impl Fn() + Send + Sync + 'static) -> Self
pub fn on_thread_stop(self, f: impl Fn() + Send + Sync + 'static) -> Self
Called on each thread the runtime starts, on that thread, as it ends (after a panic too).
Sourcepub fn on_thread_park(self, f: impl Fn() + Send + Sync + 'static) -> Self
pub fn on_thread_park(self, f: impl Fn() + Send + Sync + 'static) -> Self
Called on a worker thread just before it sleeps with nothing to run.
Sourcepub fn on_thread_unpark(self, f: impl Fn() + Send + Sync + 'static) -> Self
pub fn on_thread_unpark(self, f: impl Fn() + Send + Sync + 'static) -> Self
Called on a worker thread just after it wakes from a sleep.
Sourcepub fn on_task_spawn(
self,
f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static,
) -> Self
pub fn on_task_spawn( self, f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static, ) -> Self
Called with each task this runtime spawns (not the blocking pool’s jobs), on the spawning thread, once the task exists.
Sourcepub fn on_before_task_poll(
self,
f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static,
) -> Self
pub fn on_before_task_poll( self, f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static, ) -> Self
Called just before each poll of a task of this runtime, on the thread that polls it.
Sourcepub fn on_after_task_poll(
self,
f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static,
) -> Self
pub fn on_after_task_poll( self, f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static, ) -> Self
Called just after each poll of a task of this runtime, on the thread that polled it.
Sourcepub fn on_task_terminate(
self,
f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static,
) -> Self
pub fn on_task_terminate( self, f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static, ) -> Self
Called when a task of this runtime is done with: its future completed or was dropped by a cancel, on the thread that dropped it.
Sourcepub fn on_long_poll(
self,
f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static,
) -> Self
pub fn on_long_poll( self, f: impl Fn(&TaskMeta<'_>) + Send + Sync + 'static, ) -> Self
Called with each poll of a task of this runtime that its worker’s
watch sees run past handoff_after, on the
watch’s own thread: the task’s id and name say which one holds its
worker up. The task is the innermost one whose poll was running when
the watch looked; the poll may have ended by the time the hook runs.
A poll is told once, unless a block_on inside it ran a long poll of
its own and the shard stayed on that thread: the outer poll may then
be told again as it goes on. The shard is handed on first, so a slow
hook delays no rescue, but every other worker waits for its next look
while the hook runs: keep it short. A panic in it is caught (after the
panic hook reported it) and the watch goes on. Only workers are
watched; with handoff_after(None) build fails
with InvalidInput.
Sourcepub fn event_interval(self, runs: u32) -> Self
pub fn event_interval(self, runs: u32) -> Self
Task runs between a worker’s looks at its timers and IO when it is
busy (default 61): lower answers IO sooner, higher runs tasks with
fewer interruptions. At least 1; build fails with
InvalidInput at 0.
Sourcepub fn global_queue_interval(self, runs: u32) -> Self
pub fn global_queue_interval(self, runs: u32) -> Self
Task runs between the times a worker takes from its inbox (tasks
woken by other threads) before its own queue (default 31). At least 1;
build fails with InvalidInput at 0.
Sourcepub fn max_io_events_per_tick(self, events: usize) -> Self
pub fn max_io_events_per_tick(self, events: usize) -> Self
The most IO events a worker takes from its driver in one look
(default: all that are ready). At least 1; build
fails with InvalidInput at 0.
Sourcepub fn disable_lifo_slot(self) -> Self
pub fn disable_lifo_slot(self) -> Self
Turns off each worker’s LIFO slot, where a task woken by the task running on the worker goes to run next. Without it the woken task goes to the back of the worker’s queue: fairer when the queue is long, slower for a task that hands work to another and waits for it.
Sourcepub fn rng_seed(self, seed: RngSeed) -> Self
pub fn rng_seed(self, seed: RngSeed) -> Self
Seeds the runtime’s random choices, so that they repeat run after
run; see RngSeed.
Sourcepub fn unhandled_panic(self, policy: UnhandledPanic) -> Self
pub fn unhandled_panic(self, policy: UnhandledPanic) -> Self
What the runtime does when one of its tasks panics (default
UnhandledPanic::Ignore: the panic goes to the task’s handle).
use nuclei::runtime::{Runtime, UnhandledPanic};
let rt = Runtime::builder()
.unhandled_panic(UnhandledPanic::ShutdownRuntime)
.build()
.unwrap();
let ran = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
rt.block_on(async {
let _task = nuclei::spawn(async { panic!("a task fails") });
core::future::pending::<()>().await
})
}));
assert!(ran.is_err(), "the block_on panics once the task did");