Chapter 16 of 17

Errors

The messages you may meet, what each means, and the fix.

A spawned task never ran, or stopped halfway

No message at all: the Task was dropped, and dropping a task cancels it. A loop that spawns per connection and throws the handle away cancels every connection at once.

Fix: call .detach() on tasks nobody waits for, keep them in a JoinSet, or keep the handles and await them. See Tasks.

the task has no output

panic
the task has no output: it was cancelled or its runtime shut down before it completed, or it was awaited again after completing

A Task was awaited where it has no output to give: it was aborted, or its runtime was dropped before it completed.

Fix: keep the runtime alive until its tasks are done, or await task.join() (a JoinError says why) or task.fallible() (None) instead.

names no backend of this platform

panic
the default runtime could not start: NUCLEI_BACKEND=kqueue names no backend of this platform (use auto, epoll or io_uring)

NUCLEI_BACKEND holds a name this system has no backend for. The message lists the names that work here.

Fix: set one of the names listed, or unset the variable.

asks for io_uring, but

panic
the default runtime could not start: NUCLEI_BACKEND=io_uring asks for io_uring, but this build has no io_uring; leave it unset to use the poller

io_uring was asked for by name (NUCLEI_BACKEND, Builder::backend or NucleiConfig) and this process cannot have it; the reason follows but. Here the crate was built without the iouring feature; elsewhere it may be the kernel or a sandbox refusing the ring. A backend named by hand never falls back.

Fix: leave the backend unset, so auto picks io_uring where it can; turn the iouring feature back on; or run where io_uring is allowed (Linux 6.1 or later, and a sandbox that permits it).

the proactor is configured already

panic
the proactor is configured already: call `with_config` once, before any IO

Proactor::with_config was called twice, or after the first IO, spawn or block_on, which configures the defaults.

Fix: call it once, first thing in a plain fn main, then enter the runtime with nuclei::block_on, as in Production. Under #[nuclei::main] the body already runs on the runtime.

the default runtime is already running

error
the default runtime is already running

Builder::build_global returned this: a free function, the attribute or an earlier build_global started the default runtime first.

Fix: call build_global before anything spawns, or build a Runtime of your own and use its methods.

a setting is out of range

error
io_uring queue_len is 32; it must be at least 64

Builder::build (or build_global) returned this as InvalidInput: a setting holds a value no runtime can work with, or one that does not fit with another (max_worker_threads below worker_threads, on_long_poll with handoff_after(None)). The message names the setting, the value and what it must be. The io_uring sizes set with Proactor::with_config are checked by the first runtime built with them.

Fix: set a value in the range the message gives, or leave the setting unset for its default. Every setting states its range.

worker does not exist

error
worker 4 does not exist: the runtime has started 4 workers

spawn_on_worker, spawn_local_on_worker or task::Builder::worker named a worker the runtime does not have; a worker that stopped answers has stopped and takes no new task. Both are InvalidInput, and the task was not spawned.

Fix: name a worker from 0 to workers() less one, or use on_each_worker to reach every worker there is. See Runtime.

no shard-local value

panic
this code runs on no worker, so it has no shard-local value: use it in a task

A shard_local! value was used off the workers: in block_on's own future, on the blocking pool, or on a thread of its own. Each worker has its own value and these places have none. with panics with this message; try_with answers it as a ShardAccessError.

Fix: use the value in a task spawned on the runtime, or handle the error of try_with. See Runtime.

block_on is nested too deep

panic
block_on is nested 2 deep on one thread, the most allowed: each level runs on the thread's stack (raise the limit with `Builder::max_nesting`)

(Here the limit was set to 2.) Each nested block_on runs on the thread's stack, and one level past the limit panics before the stack would overflow.

Fix: await instead of nesting where you can; raise Builder::max_nesting where you cannot.

block_on never returns

No message: a task called block_on on a future that waits for that very task. The task is not polled again until the call returns, so neither can finish.

Fix: inside a task, wait for the other work with .await, not with block_on.

Data seems lost, or an error came late

Writes are taken into the stream's write-behind and sent by the driver, so a failed write can surface on a later write or on flush.

Fix: flush().await (or shutdown().await) where you need to know the bytes reached the kernel. See How it works.

A call of your own answers ResourceBusy

try_io or async_io on a socket found bytes the handle had read ahead: a read of your own would skip them.

Fix: read what the handle holds first (try_read until WouldBlock), then make your call. See Readiness.

A child's wait fails with ECHILD

Something other than Nuclei reaped the child: the program ignores SIGCHLD, or calls waitpid itself.

Fix: leave SIGCHLD at its default and let Child::wait reap your children.