arty 0.6.0

Thread-aware async runtime that keeps each task on one worker thread.
Documentation

Arty

crate.io docs.rs MSRV CI Coverage License

Single-threaded, thread-aware runtime for async applications.

Each async task stays on one worker thread for its entire life, even when Arty manages multiple workers. Tasks can keep thread-local and non-Send state across awaits.

Arty can pass values between workers without moving running tasks. The ThreadAware trait lets a value update its state for the worker that receives it. This update is called relocation.

Quickstart

Add Arty with its default runtime and macro features:

cargo add arty
use arty::task::{Context, JoinError};

#[arty::main]
async fn main(cx: Context) -> 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 task fails. Await child tasks before returning. Shutdown cancels pending async tasks.

Execution model

Each worker is one operating-system thread with a cooperative executor. The worker thread polls the tasks assigned to it. When one task awaits an operation that is not ready, the worker can poll another assigned task. After a task starts, it stays on that worker thread for its entire life.

Choose the initial worker for a new task:

  • Scheduler::spawn uses the worker bound to the scheduler. A cloned scheduler keeps the same binding, even when another thread calls it.
  • Scheduler::spawn_anywhere lets the runtime select a worker. This initial placement is similar to Tokio task placement.
  • Scheduler::spawn_at uses a worker that you select from the same runtime.

Unlike a Tokio task, an Arty task does not move between workers after it starts.

A task factory creates the task future on its destination worker. A payload is a separate value passed to that factory. If the factory starts, Arty relocates a ThreadAware payload before the factory uses it. Rejected tasks and tasks cancelled before their factory starts can drop the payload without relocation.

Cloned values can refer to the same resource while keeping separate state on each worker. ThreadAware updates that state, but it does not share ownership of a resource or store local state for you.

This example compares fixed placement with runtime-selected placement:

let parent_worker = cx.thread().id();
let child_worker = cx
    .scheduler()
    .spawn(async |child| child.thread().id())
    .await?;
assert_eq!(child_worker, parent_worker);

// `cx.clone()` is the payload. The closure is the task factory.
cx.scheduler()
    .spawn_anywhere(cx.clone(), |moved| async move {
        assert_eq!(moved.thread().id(), std::thread::current().id());
    })
    .await?;

spawn_anywhere can select the original worker. The relocated context always reports the worker that runs the new task.

Each runtime owns its workers and services. Arty does not use a single runtime shared by the whole process. Each worker tracks its own state.

Arty runs tasks, provides clocks, and runs blocking functions on separate threads. It can also run I/O drivers, which handle input and output. I/O libraries connect to Arty through arty_io_core. Related I/O crates ship alongside Arty.

API overview

  • runtime explains how to create, configure, and stop a runtime.
  • task explains how to start tasks, choose their workers, and receive results.
  • time provides clocks, delays, timeouts, and control over time in tests.
  • io connects I/O libraries to the runtime.
  • core describes workers and how values update their state for a worker.
  • main and test start and stop a runtime for an async function or test.

Features

The default feature set enables rt, macros, and monitor.

  • rt: Enables the runtime, task, and I/O APIs.
  • macros: Enables main and test. It also enables rt.
  • monitor: Enables the Seismograph monitor dependency. It forwards monitoring support for performables when performables is enabled. It does not start monitoring or emit new telemetry.
  • test-util: Enables testing utilities, including ClockControl. Enable it in dev-dependencies, not production dependencies.