Single-threaded, thread-aware application runtime.
An Arty runtime can have several workers, but each async task stays on one
worker throughout its life. This lets it use thread-local and non-Send
state across awaits. Thread-aware values can be explicitly relocated when
starting new work on another worker.
There is no process-global runtime: each instance owns its workers and services. Workers use thread-local bookkeeping rather than a global runtime singleton.
Arty provides task scheduling, clocks, telemetry, and pools for offloading blocking callbacks. It does not provide async I/O drivers or move running tasks between workers.
Add Arty with its default runtime and macro features:
cargo add artyuse arty::task::{Builtins, JoinError};
#[arty::main]
async fn main(cx: Builtins) -> Result<(), JoinError> {
let answer = cx.scheduler().spawn(async |_| 6 * 7).await?;
println!("{answer}");
Ok(())
}This prints 42. main starts and stops the runtime.
The ? returns a JoinError if the child fails.
Await child work before returning; shutdown cancels pending async tasks.
mainandtestmanage a runtime for an async entry point or test;Runtimegives you direct control over its lifetime and settings.Builtinsgives each task its scheduler, clock, and worker.RuntimeSchedulerlets the runtime place new work.Schedulerkeeps child work on its worker.Clockprovides timers and timeouts;ClockControlcontrols time in tests.Threaddescribes a worker, andThreadAwarevalues can relocate between workers.
Unlike Tokio’s multi-thread runtime, Arty keeps each task on one worker.
This supports thread-local and non-Send state, keeps worker-local data
nearby, and can reduce contention for that data. Shared data may still contend.
Choose Tokio for automatic task distribution or its async I/O ecosystem;
Arty does not provide Tokio’s I/O or timer drivers.
Join handles are futures: await them. Arty deliberately does not expose a synchronous join operation because async workers must stay free to poll tasks and advance timers. Blocking a worker stalls every task assigned to it. If the blocked code waits for work on that worker, or blocking callbacks wait on each other in an exhausted pool, the stall can become a deadlock.
This deadlocks: the worker waits synchronously for a child that only the same worker can poll.
#[arty::main]
async fn main(cx: arty::task::Builtins) {
let child = cx.scheduler().spawn(async |_| 42);
let _ = futures::executor::block_on(child);
}RuntimeScheduler::block_on is
the narrow blocking entry point for running async work from synchronous
code. It rejects calls from async workers. For synchronous I/O or library
calls, use
Scheduler::spawn_blocking or
RuntimeScheduler::spawn_blocking
and await the join. The callback runs in a blocking pool instead of on an
async worker.
The guides explain Arty’s capabilities in more detail:
- Scheduling explains where tasks run,
local non-
Sendwork, and blocking pools. - Configuration explains worker counts, blocking-pool policies, clocks, and telemetry sinks.
- Shutdown explains runtime ownership, task cancellation, and what stopping the workers waits for.
- Thread awareness explains stable task placement and explicit relocation of values.
- Time explains worker-driven timers, timeouts, and controlled time in tests.
- Telemetry explains runtime events
and links to
observedfor further details.
The documentation module is included only for docs.rs builds and
doc-test collection with all documentation features, including test-util;
it is not part of the public API available to applications.
The default feature set enables rt and macros.
rt- Enables the runtime and task APIs, and impliestime.macros- Enablesmainandtest, and impliesrt.time- Enables clocks, timers, and timeouts.test-util- Enables testing utilities, includingClockControlwithtime. Enable it in dev-dependencies, not production dependencies.
This crate was developed as part of The Oxidizer Project. Browse this crate's source code.