Asupersync
Spec-first, cancel-correct, capability-secure async for Rust
TL;DR
The Problem: Rust's async ecosystem gives you tools but not guarantees. Cancellation silently drops data. Spawned tasks can orphan. Cleanup is best-effort. Testing concurrent code is non-deterministic. You write correct code by convention, and discover bugs in production.
The Solution: Asupersync is an async runtime where correctness is structural, not conventional. Tasks are owned by regions that close to quiescence. Cancellation is a protocol with bounded cleanup. Effects require capabilities. The lab runtime makes concurrency deterministic and replayable.
Why Asupersync?
| Guarantee | What It Means |
|---|---|
| No orphan tasks | Every spawned task is owned by a region; region close waits for all children |
| Cancel-correctness | Cancellation is request → drain → finalize, never silent data loss |
| Bounded cleanup | Cleanup budgets are sufficient conditions, not hopes |
| No silent drops | Two-phase effects (reserve/commit) make data loss impossible for primitives |
| Deterministic testing | Lab runtime: virtual time, deterministic scheduling, trace replay |
| Capability security | All effects flow through explicit Cx; no ambient authority |
Quick Example
use ;
// Structured concurrency: scope guarantees quiescence
async
// Cancellation is a protocol, not a flag
async
// Lab runtime: deterministic testing
Design Philosophy
1. Structured Concurrency by Construction
Tasks don't float free. Every task is owned by a region. Regions form a tree. When a region closes, it guarantees all children are complete, all finalizers have run, all obligations are resolved. This is the "no orphans" invariant, enforced by the type system and runtime rather than by discipline.
// Typical executors: what happens when this scope exits?
spawn;
// Asupersync: scope guarantees quiescence
scope.region.await;
// ← guaranteed: nothing from inside is still running
2. Cancellation as a First-Class Protocol
Cancellation operates as a multi-phase protocol, not a silent drop:
Running → CancelRequested → Cancelling → Finalizing → Completed(Cancelled)
↓ ↓ ↓
(bounded) (cleanup) (finalizers)
- Request: propagates down the tree
- Drain: tasks run to cleanup points (bounded by budgets)
- Finalize: finalizers run (masked, budgeted)
- Complete: outcome is
Cancelled(reason)
Primitives publish cancellation responsiveness bounds. Budgets are sufficient conditions for completion.
3. Two-Phase Effects Prevent Data Loss
Anywhere cancellation could lose data, Asupersync uses reserve/commit:
let permit = tx.reserve.await?; // ← cancel-safe: nothing committed yet
permit.send; // ← linear: must happen or abort
Dropping a permit aborts cleanly. Message never partially sent.
4. Capability Security (No Ambient Authority)
All effects flow through explicit capability tokens:
async
Swap Cx to change interpretation: production vs. lab vs. distributed.
5. Deterministic Testing is Default
The lab runtime provides:
- Virtual time: sleeps complete instantly, time is controlled
- Deterministic scheduling: same seed → same execution
- Trace capture/replay: debug production issues locally
- Schedule exploration: DPOR-class coverage of interleavings
Concurrency bugs become reproducible test failures.
How Asupersync Compares
| Feature | Asupersync | async-std | smol |
|---|---|---|---|
| Structured concurrency | ✅ Enforced | ❌ Manual | ❌ Manual |
| Cancel-correctness | ✅ Protocol | ⚠️ Drop-based | ⚠️ Drop-based |
| No orphan tasks | ✅ Guaranteed | ❌ spawn detaches | ❌ spawn detaches |
| Bounded cleanup | ✅ Budgeted | ❌ Best-effort | ❌ Best-effort |
| Deterministic testing | ✅ Built-in | ❌ External tools | ❌ External tools |
| Obligation tracking | ✅ Linear tokens | ❌ None | ❌ None |
| Ecosystem | 🔜 Growing | ⚠️ Medium | ⚠️ Small |
| Maturity | 🔜 Active dev | ✅ Production | ✅ Production |
When to use Asupersync:
- Internal applications where correctness > ecosystem
- Systems where cancel-correctness is non-negotiable (financial, medical, infrastructure)
- Projects that need deterministic concurrency testing
- Distributed systems with structured shutdown requirements
When to consider alternatives:
- You need broad ecosystem library compatibility (we're building native equivalents)
- Rapid prototyping where correctness guarantees aren't yet critical
Installation
From Git (Recommended)
# Add to Cargo.toml
# Or manually add:
# [dependencies]
# asupersync = { git = "https://github.com/Dicklesworthstone/asupersync" }
From Source
Minimum Supported Rust Version
Asupersync requires Rust 1.75+ and uses Edition 2021.
Core Types Reference
Outcome — Four-Valued Result
// Severity lattice: Ok < Err < Cancelled < Panicked
// HTTP mapping: Ok→200, Err→4xx/5xx, Cancelled→499, Panicked→500
Budget — Resource Constraints
// Semiring: meet(a, b) = tighter constraint wins
let effective = outer_budget.meet;
CancelReason — Structured Context
// Severity: User < Timeout < FailFast < ParentCancelled < Shutdown
// Cleanup budgets scale inversely with severity
Cx — Capability Context
Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXECUTION TIERS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ FIBERS │ │ TASKS │ │ ACTORS │ │ REMOTE │ │
│ │ │ │ │ │ │ │ │ │
│ │ • Borrow- │ │ • Parallel │ │ • Long- │ │ • Named │ │
│ │ friendly │ │ • Send │ │ lived │ │ compute │ │
│ │ • Same- │ │ • Work- │ │ • Super- │ │ • Leases │ │
│ │ thread │ │ stealing │ │ vised │ │ • Idempotent│ │
│ │ • Region- │ │ • Region │ │ • Region- │ │ • Saga │ │
│ │ pinned │ │ heap │ │ owned │ │ cleanup │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │ │
│ └────────────────┴────────────────┴────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ REGION TREE │ │
│ │ │ │
│ │ Root Region ──┬── Child Region ──┬── Task │ │
│ │ │ ├── Task │ │
│ │ │ └── Subregion ── Task │ │
│ │ └── Child Region ── Actor │ │
│ │ │ │
│ │ Invariant: close(region) → quiescence(all descendants) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ OBLIGATION REGISTRY │ │
│ │ │ │
│ │ SendPermit ──→ send() or abort() │ │
│ │ Ack ──→ commit() or nack() │ │
│ │ Lease ──→ renew() or expire() │ │
│ │ IoOp ──→ complete() or cancel() │ │
│ │ │ │
│ │ Invariant: region_close requires all obligations resolved │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ SCHEDULER │ │
│ │ │ │
│ │ Cancel Lane ──→ Timed Lane (EDF) ──→ Ready Lane │ │
│ │ ↑ │ │
│ │ (priority) Lyapunov-guided: V(Σ) must decrease │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Scheduler Priority Lanes
| Lane | Purpose | Priority |
|---|---|---|
| Cancel Lane | Tasks in cancellation states | 200-255 (highest) |
| Timed Lane | Deadline-driven tasks (EDF) | Based on deadline |
| Ready Lane | Normal runnable tasks | Default priority |
Mathematical Foundations
Asupersync has formal semantics backing its engineering.
| Concept | Math | Payoff |
|---|---|---|
| Outcomes | Severity lattice: Ok < Err < Cancelled < Panicked |
Monotone aggregation, no "recovery" from worse states |
| Concurrency | Near-semiring: join (⊗) and race (⊕) with laws |
Lawful rewrites, DAG optimization |
| Budgets | Tropical semiring: (ℝ∪{∞}, min, +) |
Critical path computation, budget propagation |
| Obligations | Linear logic: resources used exactly once | No leaks, static checking possible |
| Traces | Mazurkiewicz equivalence (partial orders) | Optimal DPOR, stable replay |
| Cancellation | Two-player game with budgets | Completeness theorem: sufficient budgets guarantee termination |
See asupersync_v4_formal_semantics.md for the complete operational semantics.
Using Asupersync as a Dependency
Cargo.toml
[]
# Git dependency until crates.io publish
= { = "https://github.com/Dicklesworthstone/asupersync", = "0.1" }
Feature Flags
Asupersync is feature-light by default; the lab runtime is available without flags.
| Feature | Description | Default |
|---|---|---|
test-internals |
Expose test-only helpers (not for production) | No |
metrics |
OpenTelemetry metrics provider | No |
tracing-integration |
Tracing spans/logging integration | No |
proc-macros |
scope!, spawn!, join!, race! proc macros |
No |
tower |
Tower Service adapter support |
No |
trace-compression |
LZ4 compression for trace files | No |
debug-server |
Debug HTTP server for runtime inspection | No |
config-file |
TOML config file loading for RuntimeBuilder |
No |
io-uring |
Linux io_uring reactor (kernel 5.1+) | No |
tls |
TLS support via rustls | No |
tls-native-roots |
TLS with native root certs | No |
tls-webpki-roots |
TLS with webpki root certs | No |
cli |
CLI tools (trace inspection) | No |
Minimum Supported Rust Version
Rust 1.75+ (Edition 2021).
Semver Policy
- 0.x.y: Breaking changes may ship in 0.(x+1).0
- 1.x.y: Breaking changes only in (1+1).0.0
See docs/api_audit.md for the current public API audit and stability notes.
Core Exports
use ;
Wrapping Cx for Frameworks
Framework authors (e.g., HTTP servers) should wrap Cx:
/// Framework-specific request context
HTTP Status Mapping
// Recommended HTTP status mapping:
// - Outcome::Ok(_) → 200 OK
// - Outcome::Err(_) → 4xx/5xx based on error type
// - Outcome::Cancelled(_) → 499 Client Closed Request
// - Outcome::Panicked(_) → 500 Internal Server Error
Configuration
Lab Runtime Configuration
let config = default
// Seed for deterministic scheduling (same seed = same execution)
.seed
// Maximum steps before timeout (prevents infinite loops)
.max_steps
// Enable futurelock detection (tasks holding obligations without progress)
.futurelock_max_idle_steps
// Enable trace capture for replay
.capture_trace;
let lab = new;
Budget Configuration
// Request timeout with poll budget
let request_budget = new
.with_deadline_secs // 30 second timeout
.with_poll_quota // Max 10k polls
.with_priority; // Normal priority
// Cleanup budget (tighter for faster shutdown)
let cleanup_budget = new
.with_deadline_secs
.with_poll_quota;
Troubleshooting
"ObligationLeak detected"
Your task completed while holding an obligation (permit, ack, lease).
// Wrong: permit dropped without send/abort
let permit = tx.reserve.await?;
return ok; // Leak!
// Right: always resolve obligations
let permit = tx.reserve.await?;
permit.send; // Resolved
"RegionCloseTimeout"
A region is stuck waiting for children that won't complete.
// Check for: infinite loops without checkpoints
loop
"FuturelockViolation"
A task is holding obligations but not making progress.
// Check for: awaiting something that will never resolve
// while holding a permit/lock
let permit = tx.reserve.await?;
other_thing.await; // If this blocks forever → futurelock
permit.send;
Deterministic test failures
Same seed should give same execution. If not:
// Check for: time-based operations
// WRONG: uses wall-clock time
let now = now;
// RIGHT: uses virtual time through Cx
let now = cx.now;
Also check for ambient randomness:
// WRONG: ambient entropy breaks determinism
let id = ;
// RIGHT: use capability-based entropy
let id = cx.random_u64;
To enforce deterministic collections in lab code, consider a clippy rule that
disallows std::collections::HashMap/HashSet in favor of util::DetHashMap/DetHashSet.
Limitations
Current Phase (0-1) Constraints
| Capability | Current State | Planned |
|---|---|---|
| Multi-threaded runtime | 🔜 In progress | Phase 1 |
| I/O reactor integration | ❌ Not yet | Phase 2 |
| Actor supervision | ❌ Planned | Phase 3 |
| Distributed runtime | ❌ Designed | Phase 4 |
| DPOR exploration | ⚠️ Hooks exist | Phase 5 |
What Asupersync Doesn't Do
- Cooperative cancellation only: Non-cooperative code requires explicit escalation boundaries
- Not a drop-in replacement for other runtimes: Different API, different guarantees
- No ecosystem compatibility: Can't directly use runtime-specific libraries (adapters planned)
Design Trade-offs
| Choice | Trade-off |
|---|---|
| Explicit checkpoints | More verbose, but cancellation is observable |
| Capability tokens | Extra parameter threading, but testable and auditable |
| Two-phase effects | More complex primitives, but no data loss |
| Region ownership | Can't detach tasks, but no orphans |
Roadmap
| Phase | Focus | Status |
|---|---|---|
| Phase 0 | Single-thread deterministic kernel | ✅ Complete |
| Phase 1 | Parallel scheduler + region heap | 🔜 In Progress |
| Phase 2 | I/O integration | Planned |
| Phase 3 | Actors + session types | Planned |
| Phase 4 | Distributed structured concurrency | Planned |
| Phase 5 | DPOR + TLA+ tooling | Planned |
FAQ
Why "Asupersync"?
"A super sync": structured concurrency done right.
Why not just use existing runtimes with careful conventions?
Conventions don't compose. The 100th engineer on your team will spawn a detached task. The library you depend on will drop a future holding a lock. Asupersync makes incorrect code unrepresentable (or at least detectable).
How does this compare to structured concurrency in other languages?
Similar goals to Kotlin coroutines, Swift structured concurrency, and Java's Project Loom. Asupersync goes further with:
- Formal operational semantics
- Two-phase effects for cancel-safety
- Obligation tracking (linear resources)
- Deterministic lab runtime
Can I use this with existing async Rust code?
Asupersync has its own runtime with explicit capabilities. For code that needs to interop with external async libraries, we provide boundary adapters that preserve our cancel-correctness guarantees.
Is this production-ready?
Phase 0 (single-threaded deterministic kernel) is complete and tested. Phase 1 (multi-threaded) is in progress. Use for internal applications where correctness matters more than ecosystem breadth.
How do I report bugs?
Open an issue at https://github.com/Dicklesworthstone/asupersync/issues
Documentation
| Document | Purpose |
|---|---|
asupersync_plan_v4.md |
Design Bible: Complete specification, invariants, philosophy |
asupersync_v4_formal_semantics.md |
Operational Semantics: Small-step rules, TLA+ sketch |
asupersync_v4_api_skeleton.rs |
API Skeleton: Rust types and signatures |
docs/integration.md |
Integration Docs: Architecture, API orientation, tutorials |
docs/macro-dsl.md |
Macro DSL: scope!/spawn!/join!/race! usage, patterns, examples |
docs/cancellation-testing.md |
Cancellation Testing: deterministic injection + oracles |
docs/replay-debugging.md |
Replay Debugging: Record/replay for debugging async bugs |
docs/security_threat_model.md |
Security Review: Threat model and security invariants |
TESTING.md |
Testing Guide: unit, conformance, E2E, fuzzing, CI |
AGENTS.md |
AI Guidelines: Rules for AI coding agents |
Contributing
About Contributions: Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via
ghand independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
License
MIT