Chapter 17 of 17

Migrate

Coming from a readiness-based runtime: where each piece is, and what behaves differently.

The map

Most names carry over; the import path changes. The last column is what to watch.

What you use todayIn NucleiMind
An attribute on an async main or test#[nuclei::main], #[nuclei::test]threads = N and start_paused = true give a runtime of its own.
Run a future from synchronous codenuclei::block_onAllowed inside a task too: it nests and keeps the shard running.
Spawn and keep a join handlenuclei::spawn gives a Task<T>Awaiting gives T itself; join() gives Result<T, JoinError>.
Drop the handle to let the task run on.detach()Dropping a Task cancels it.
Abort a task, a join setabort(), abort_handle(), nuclei::task::JoinSetcancel().await also gives back an output that was ready.
Blocking work, tasks that are not Sendspawn_blocking, spawn_localNo local set to create: a local task runs on the calling thread's shard.
Spawn on a chosen worker, or on eachRuntimeHandle::spawn_on_worker, on_each_workerA placed task stays on its worker for its whole life. See Runtime.
A value per worker threadnuclei::shard_local!It lives in the worker's shard and moves with it on a handoff; off the workers there is none.
Task-local values, yielding, the budgetnuclei::task_local!, task::yield_now, task::coopA task-local value is set with scope around the task's future.
A runtime builder and handlenuclei::Builder, RuntimeHandleA handle does not keep the runtime running. See every setting.
TCP, UDP and Unix socket typesnuclei::netstd's own sockets inside; std's methods work through them.
Socket builders, stream halvesTcpSocket, UdpSocketBuilder, UnixSocket, split, into_splitA shared reference to a stream reads and writes too.
Shut one direction of a streamshut(how), abort()stream.shutdown(how) is std's call through the handle, which goes around the bytes written behind. See Networking.
Read and write extension traits, copies, buffersnuclei::ioOn the futures-io traits; closing a write side is shutdown.
Wait until readable, then try a readreadable(), try_read()Rarely needed: recv().await completes the read for you.
A descriptor of your own, by readinessnuclei::io::unix::AsyncFdThe descriptor must be in non-blocking mode.
Files and directoriesnuclei::fsread_dir gives entries with next_entry().await.
Reads and writes at an offsetread_at, write_at on &FileThe handle's own calls are async; std's FileExt calls through it block the worker. See Files.
Sleep, timeout, interval, paused timenuclei::timeA millisecond wheel; missed ticks burst by default.
Locks, semaphores, channels, a cancellation tokennuclei::syncThe established names and signatures; they work under any executor.
Channels and locks for tasks of one threadnuclei::sync::localNot Send, and cheaper for it: for spawn_local tasks of one shard. See Sync.
select!, join!, try_join!nuclei::select!, join!, try_join!select! starts at a random branch unless biased;.
Ctrl-C, Unix signals, child processesnuclei::signal, nuclei::processA signal is a stream: recv().await.

Pitfalls

Dropping a task cancels it

The most common bug after a move. A task whose Task is dropped stops at its next .await, and never runs if it had not started. Every fire-and-forget spawn needs .detach(). See Tasks.

A write is taken, not delivered

A write returns once its bytes are copied into the stream's write-behind. Code that writes and then exits, or expects an error right away, should flush().await or shutdown().await. See How it works.

No work stealing

A task stays on its shard, and no idle core takes work from a busy one. A poll that runs past handoff_after (10 ms by default) has the shard's other tasks go on on another thread of the same core (Tasks), but the long poll still shares that core: give such work to spawn_blocking, or break it up.

Buffers are copied, unless you lend them

recv and read copy from the shard's buffer into your slice; writes copy into the write-behind. Where the copy matters, read_owned and write_owned lend the driver your own buffer, a vector or any IoBuf.

std's calls through the handle

A socket or file is std's own object in a Handle, so std's methods are reachable through it. The ones that read, write or shut a stream go around the bytes the handle read ahead and wrote behind, and the blocking ones block the worker. Use the handle's own calls; Networking lists what each of std's does.

Configure io_uring before any IO

Proactor::with_config runs once, before the first IO, spawn or block_on; it panics otherwise. Configure from a plain fn main, then enter the runtime.