RsCxCx

Get Started

Four short programs take you from a plain #[main] to a lab test that catches a leaked permit. Each one is a real file in the repository's examples/ directory.

Step 1

Install

v0.5.0 is the latest release on crates.io. The main branch carries the unreleased 0.6.0 line.

Latest release (0.5.0)
$cargo add asupersync
Tracking main (0.6.0, unreleased)
$cargo add asupersync --git https://github.com/Dicklesworthstone/asupersync
Rust versionDefault features build on stable Rust 1.95 or newer, edition 2024. The one nightly-only piece is ? on Outcome; on stable it's inactive and the crate builds without it. The repository pins nightly-2026-08-31 for its own tests.
Default featuresThe macros (#[main], join!, race!, and friends) and the lab runtime come with the default build. TLS, HTTP/3, databases, and io_uring are opt-in features, listed below.
Step 2

The on-ramp

Each level adds one layer. Run any of them from a checkout with cargo run --example onramp_level0 (through 3).

Level 0

Enter the runtime

The attribute entry point. No builder, executor handle, or capability to learn yet.

examples/onramp_level0.rs
onramp_level0.rs
01
use
asupersync::main;
02 03#[main]04
async
fn
main() {
05 println!("hello from asupersync");06}
Syntax_Validation_Active
UTF-8_ENCODED
Level 1

Cx, Outcome, and Budget

main receives the capability context. Budgets combine with meet, and the tighter one wins.

examples/onramp_level1.rs
onramp_level1.rs
01
use
asupersync::{main, prelude::*};
02 03#[main]04
async
fn
main(cx: &Cx) {
05
let
service = Budget::new().with_poll_quota(64);
06
let
request = Budget::new().with_poll_quota(16);
07
let
effective = service.meet(request);
08 assert_eq!(effective.remaining_polls(), 16);09 10 cx.checkpoint()11 .expect("budget and cancellation permit work");12
let
outcome: Outcome<u32, Error> = Outcome::ok(42);
13 assert_eq!(outcome.expect("work succeeds"), 42);14}
Syntax_Validation_Active
UTF-8_ENCODED
Level 2

Scopes and region-owned fan-out

A JoinSet owns dynamic fan-out inside one region; nothing outlives the join.

examples/onramp_level2.rs
onramp_level2.rs
01
use
asupersync::{main, prelude::*};
02 03#[main]04
async
fn
main(cx: &Cx) {
05
let
scope = cx.scope_with_budget(Budget::new().with_poll_quota(64));
06
let
mut
tasks = JoinSet::new(&scope);
07 08
for
value
in
1..=3_u32 {
09 tasks10 .spawn(cx,
move
|_|
async
move
{ Ok::<_, Error>(value) })
11 .expect("spawn region-owned task");12 }13 14
let
sum = tasks
15 .join_all(cx)16 .
await
17 .into_iter()18 .map(|outcome| outcome.expect("child succeeds"))19 .sum::<u32>();20 assert_eq!(sum, 6);21}
Syntax_Validation_Active
UTF-8_ENCODED
Level 3

Two-phase sends and a lab oracle

Reserve, then send. Then leak a permit on purpose inside the lab and watch the obligation-leak oracle name it.

examples/onramp_level3.rs
onramp_level3.rs
01
use
asupersync::{LabConfig, LabRuntime, main, prelude::*};
02 03#[main]04
async
fn
main(cx: &Cx) {
05
let
(tx,
mut
rx) = mpsc::channel::<u8>(1);
06
let
permit = tx.reserve(cx).
await
.expect("reserve channel capacity");
07 permit.send(7);08 assert_eq!(rx.recv(cx).
await
.expect("receive committed value"), 7);
09 10 // The same reservation inside a deterministic lab task, except that this11 // permit escapes without `send` or `abort`. Stock permits are runtime12 // obligations, so the lab's obligation-leak oracle catches it by kind13 // without any hand-built obligation record.14
let
mut
lab = LabRuntime::new(LabConfig::new(7).panic_on_leak(false));
15
let
region = lab.state.create_root_region(Budget::INFINITE);
16
let
(task, _handle) = lab
17 .state18 .create_task(region, Budget::INFINITE,
async
{
19
let
cx = Cx::current().expect("lab task installs a current Cx");
20
let
(tx, _rx) = mpsc::channel::<u8>(1);
21
let
permit = tx.reserve(&cx).
await
.expect("reserve channel capacity");
22 std::mem::forget(permit); // deliberate on-ramp leak23 })24 .expect("create lab task");25 lab.scheduler.lock().schedule(task, 0);26 lab.run_until_quiescent();27 28
let
report = lab.report();
29
let
leak = report
30 .oracle_report31 .entry("obligation_leak")32 .expect("obligation leak oracle is registered");33 assert!(!leak.passed, "the lab must catch the deliberate leak");34
let
violation = leak.violation.as_deref().unwrap_or_default();
35 assert!(36 violation.contains("SendPermit"),37 "the oracle names the leaked permit kind: {violation}"38 );39}
Syntax_Validation_Active
UTF-8_ENCODED

The full walkthrough, with what each level's output means, is the on-ramp guide in the spec docs.

Step 3

Two more worth reading

Fibers for cheap fan-out over borrowed data, and the lab runtime proving a run replays.

fiber::scope runs its fibers inside the calling task, so they can borrow from the stack and need no 'static bound. The scope returns only after every fiber finishes.

fibers_borrowing.rs
01
use
asupersync::cx::fiber;
02
use
asupersync::main;
03 04#[main]05
async
fn
main() {
06
let
words = vec!["structured", "concurrency", "over", "borrowed", "data"];
07
let
words = &words;
08
let
letters = fiber::scope(|scope|
async
move
{
09
let
handles: Vec<_> = words
10 .iter()11 .map(|word| scope.spawn(
async
move
{ word.len() }))
12 .collect();13
let
mut
letters = 0;
14
for
handle
in
handles {
15 letters += handle.
await
.expect("fiber finished");
16 }17 letters18 })19 .
await
;
20 assert_eq!(letters, words.iter().map(|word| word.len()).sum::<usize>());21 println!("{letters} letters counted by {} fibers", words.len());22}
Syntax_Validation_Active
UTF-8_ENCODED

The same seed replays the same execution, and the report says whether the run reached quiescence and whether any invariant was violated.

deterministic_test.rs
01
use
asupersync::lab::{LabRunReport, run_async_under_lab};
02
use
asupersync::prelude::*;
03 04
fn
main() {
05
let
(total, report) = fanout(7);
06 assert_eq!(total, fanout(7).0, "same seed must replay identically");07 assert!(report.quiescent, "region close implies quiescence");08 assert!(report.invariant_violations.is_empty(), "nothing leaked");09 println!("deterministic: replayed {total} under seed 7, quiescent, 0 violations");10}11 12/// Sums a fan-out of 8 children under the deterministic lab runtime.13
fn
fanout(seed: u64) -> (u32, LabRunReport) {
14 run_async_under_lab(seed, |cx|
async
move
{
15
let
mut
set = JoinSet::in_cx(&cx);
16
for
i
in
0..8_u32 {
17 set.spawn(&cx,
move
|_|
async
move
{ Ok::<_, ()>(i) })
18 .expect("spawn");19 }20
let
outcomes = set.join_all(&cx).
await
;
21 outcomes.into_iter().map(|o| o.expect("member ok")).sum()22 })23}
Syntax_Validation_Active
UTF-8_ENCODED
Habits to unlearn

What trips people up

Most early surprises come from tokio habits that are harmless there and break a guarantee here.

Instead of
A long loop that never awaits anything cancel-aware
Write
cx.checkpoint()? inside the loop

Cancellation is cooperative. A task that never reaches a cancellation point is never stopped, and its region can't close.

Instead of
std::time::Instant::now()
Write
cx.now()

The lab runtime runs on virtual time. Reading the wall clock makes a seed stop reproducing its schedule.

Instead of
rand::random::<u64>()
Write
cx.random_u64()

Ambient entropy breaks determinism the same way the wall clock does.

Instead of
Awaiting something unrelated while holding a permit
Write
Reserve as late as you can, send right away

A task parked on other work while it holds a permit or semaphore slot is what the lab reports as a futurelock.

Instead of
Detaching a background task
Write
Spawn it into a scope or a JoinSet you own

There's no detached spawn. Decide which region owns the work, and that region's close will wait for it.

Feature flags

Turning on the rest

The default build is deliberately small. These are the features applications reach for most.

Common Cargo features
FeatureWhat it enables
tlsTLS through rustls with the ring provider. Add tls-native-roots or tls-webpki-roots for root certificates.
http2-streamingLive HTTP/2 request bodies with bounded queues and consumption-based flow control.
quic, http3Native QUIC and HTTP/3 (http3 implies quic). Partial: single-connection and listener paths are tested; deployment interop isn't.
postgres, mysql, sqliteDatabase clients. PostgreSQL and MySQL speak the wire protocol directly; SQLite runs on the blocking pool.
io-uringThe Linux io_uring reactor (kernel 5.1+).
metrics, tracing-integrationOpenTelemetry metrics and tracing spans. Neither pulls in tokio.
towerAdapters for tower's Service trait.
atp-cliThe standalone atp file-transfer binary.

The full list, including the browser profiles and the test-only features, is in the upstream README. Migrating an existing tokio service? Start with the Tokio migration playbook and its read-only readiness planner, and the repository's agent skill if you work with Claude Code or Codex.

FAQ

Common questions

Including the ones with unflattering answers.

When should I pick Asupersync over tokio?
When structured shutdown, obligation tracking, and reproducible concurrency bugs matter more to you than the last microsecond per task. It suits internal systems that can test their own adapters and cancellation boundaries against their workload. If you need the lowest per-task overhead, or drop-in compatibility with crates hard-wired to tokio, use tokio.
How does performance compare to tokio?
It's slower per task. Every task carries a region membership, a cancellation state machine, and a terminal-result channel, and that bookkeeping costs time. In a same-process comparison from October 2026, spawn + join was 9–27× slower than tokio and an mpsc round trip 3–7× slower, while a yield on four workers was within 2×. Loopback TCP request/response was 1.1–1.7× slower, and the HTTP/1.1 server handled about half of hyper's requests per second. For fan-out inside one task, fiber::scope costs about the same as a tokio task. For most servers a few microseconds per task disappears next to network and disk latency; for millions of tiny tasks per second, it doesn't.
Is it production-ready?
Not in the sense tokio is. It's pre-1.0 and experimental. The author's own projects run on it in production, and no independent production user is known. v0.5.0 is on crates.io; main carries the unreleased 0.6.0 line.
Can I use tokio crates with it?
Not directly. A crate that needs tokio's runtime traits needs a boundary adapter, and the separate asupersync-tokio-compat crate has them for hyper, reqwest, tonic, tower, and axum stacks. The intended order is native Asupersync first, adapters only where a third-party crate insists on tokio. The default build of the runtime itself has no normal dependency on tokio.
What does "cancel-correct" actually mean here?
Cancelling a task asks it to stop. The task keeps running until it reaches a cancellation point, either cx.checkpoint() or a cancel-aware await, then finishes its own async cleanup and can still return a value. The runtime runs finalizers and publishes Cancelled(reason) with the cause chain. Surfaces like channel sends use reserve/commit, so a cancelled send never half-happens. What it won't do is forcibly stop a task that never checks in: that task holds up its region's close.
Are cleanup budgets enforced?
On the production runtime they're advisory. A task that runs past its budget isn't killed; Runtime::shutdown_timeout bounds how long the caller waits for teardown. Budgets still compose predictably when scopes nest (the earlier deadline, the smaller quota, and the higher priority win), and the scheduler and lab use them.
How much of my code has to change?
Async functions take &Cx. Spawns go through cx.spawn, a scope, or a JoinSet. sleep and timeout take the current time. Channel sends reserve first, or use the one-call send(). It's still ordinary async/await. The on-ramp covers it in four levels, each a complete program, starting from a plain #[main] that needs no runtime concepts at all.
What Rust version do I need?
Default features build on stable Rust 1.95 or newer, edition 2024. The one nightly-only piece is the ? operator on Outcome, from the default nightly-outcome-try feature; on stable it's simply inactive and the crate builds without it. The repository's own tests use the nightly pinned in rust-toolchain.toml.
How does the lab runtime find bugs?
It runs your code on virtual time with a seeded scheduler, so a seed reproduces a schedule exactly. The schedule explorer derives new seeds from the races it detects and skips runs whose traces are equivalent to one it has seen, up to reordering of independent events. On every run, oracles check for task leaks, obligation leaks, quiescence, loser drain, finalizers, the region tree, deadline monotonicity, the cancellation protocol, and DOWN-message order. It's race-guided search, not exhaustive model checking: the number of classes explored is a campaign metric, not a completeness proof.
What is formally verified?
There's a small-step operational semantics, and a Lean project checks six invariants of that abstract model: single-owner structured concurrency, quiescence at region close, the cancellation protocol, race loser drain, no obligation leaks, and no ambient authority. The Rust runtime hasn't been proved to refine the model. Lab traces can be exported to TLA+ and checked by TLC. Everything else is tests, oracles, and conformance suites.
Does it run in the browser?
Partly. The Browser Edition compiles to wasm32 and ships JS/TS packages (@asupersync/browser, with React and Next adapters) for the main thread and dedicated workers. They're a release candidate and not yet on npm. In the browser it isn't the native scheduler: it's a ledger of regions, scopes, and task handles over the host's own promises, with fetch, WebSocket, and WebTransport behind capabilities. There's a live WASM demo linked from the home page.
Are the fancy algorithms on by default?
Mostly not. The adaptive cancel-streak selector (discounted UCB1) and the Lyapunov governor are opt-in; when the adaptive selector was measured against the fixed limit, it didn't win, so the fixed limit stays the default. The spectral wait-graph monitor is a diagnostic you call. E-processes, conformal calibration, and Foata fingerprints live in the lab runtime. They exist to make testing and debugging auditable, not to speed up the production scheduler.
Why the name?
"A super sync": structured concurrency done right.