Skip to main content

Builder

Struct Builder 

Source
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

Source

pub fn new() -> Self

A builder with every setting at its default.

Source

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.

Source

pub fn thread_name(self, name: impl Into<String>) -> Self

The name of every thread the runtime starts (default nuclei-worker and nuclei-blocking).

Source

pub fn thread_name_fn( self, f: impl Fn() -> String + Send + Sync + 'static, ) -> Self

A function naming each thread the runtime starts.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn build_global(self) -> Result<RuntimeHandle>

Starts the runtime and makes it the process default, the one the free functions (spawn, block_on, …) use outside any other runtime.

§Errors

As for build, and AlreadyExists when the default runtime is already running (it was built, or a free function started it).

Source§

impl Builder

Source

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.

Source

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

Source

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.

Source

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.

Source

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

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn rng_seed(self, seed: RngSeed) -> Self

Seeds the runtime’s random choices, so that they repeat run after run; see RngSeed.

Source

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");

Trait Implementations§

Source§

impl Debug for Builder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Builder

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> MaybeSend for T
where T: Send + ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.