Asupersync Browser Edition · 21 live exhibits

The lifecycle contract, made visible.

Explore the ownership, cancellation, outcome, and capability model behind Asupersync. Every lifecycle mutation in this lab crosses the shipped WebAssembly boundary; the browser supplies the promises, timers, and event loop.

Exhibit 1

ABI Handshake & Version Negotiation

Before lifecycle work begins, Asupersync's WASM module and JavaScript host perform a version handshake with a 64-bit fingerprint. Incompatible consumers are rejected at the first API call, before any state is mutated. That makes ABI drift an explicit contract failure instead of a late runtime surprise.

JavaScript
// Initialize the WASM module await init('./packages/browser-core/asupersync_bg.wasm'); // Query the ABI version contract const version = abiVersion(); console.log(version); // → { major: 1, minor: 0 } // Verify fingerprint hasn't drifted const fp = abiFingerprint(); console.log(`Fingerprint: ${fp}`); // → 4558451663113424898n
Live ABI Status
Click Run or Initialize Runtime to see live results
Console Output
Exhibit 2

Structured Concurrency: The Region Tree

Asupersync makes task ownership explicit: every task belongs to a region, and regions form a tree rooted at the runtime. Closing a region drives its recorded children toward quiescence and resolves their lifecycle obligations. Choose any region to watch the host-timed phases while the corresponding scope handles close in WASM.

JavaScript
// Enter a scope from the runtime; this creates a child region const root = rt.enterScope('http-server'); const httpRegion = root.value; // Nest deeper. Regions form a tree. const reqA = httpRegion.enterScope('request-A').value; const reqB = httpRegion.enterScope('request-B').value; // Spawn tasks inside regions const task1 = reqA.spawnTask({ label: 'parse-headers' }).value; const task2 = reqA.spawnTask({ label: 'read-body' }).value; // Close the child scopes before their parent reqA.close(); // → descendant lifecycle records close reqB.close(); httpRegion.close();
Interactive Region Tree
Running Cancel Requested Draining Quiescent
The design difference: Other ecosystems offer strong building blocks for cancellation and task tracking, but the ownership policy is often assembled from several primitives. Asupersync puts the region tree and its close protocol directly in the runtime contract.
Region Events
Exhibit 3

Four-Valued Outcome Algebra

Asupersync uses a four-valued Outcome with a severity lattice: Ok < Err < Cancelled < Panicked. Combinators can aggregate by severity, keeping application errors, attributed cancellation, and panics distinct without flattening them into a single failure channel.

JavaScript
// Click an outcome card above to see its code
Severity Lattice
Exhibit 4

Cancellation Is a Protocol, Not a Silent Drop

Cancellation flows through a multi-phase protocol: Running → Requested → Draining → Finalizing → Completed. Budget is consumed during drain, so cleanup policy is visible and bounded instead of being implicit. The comparison below contrasts a detached-drop pattern with Asupersync's explicit close protocol.

Cancel State Machine
Shutdown Budget 100%

All tasks executing normally.

Illustrative resource labels; the Asupersync side records and joins three WASM task obligations.

Detached task pattern Running
DB Connectionactive
File Handleactive
Network Socketactive

Tasks running...

Asupersync Running
DB Connectionactive
File Handleactive
Network Socketactive

Tasks running...

JavaScript
// Cancel a task with structured protocol const task = scope.spawnTask({ label: 'worker' }).value; // Request cancellation to begin the multi-phase protocol const result = task.cancel( 'user', // kind: user | timeout | fail_fast | race_lost | shutdown 'User clicked stop' // optional message for attribution ); // The cancel outcome carries full attribution // { outcome: "cancelled", cancellation: { // kind: "user", // phase: "completed", // origin_region: "browser-sdk", // message: "User clicked stop" // }}
Exhibit 5

Budget Algebra with Semiring Composition

Asupersync makes cleanup constraints first-class. Its budgets compose as a semiring: combine(b1, b2) takes componentwise min for quotas/deadlines and max for priority. That turns nested constraints into an operation the runtime can validate and inspect.

JavaScript
// Create budgets with validated bounds const outer = createBudget({ pollQuota: 2048, deadlineMs: 30000, priority: 100, cleanupQuota: 512 }); const inner = createBudget({ pollQuota: 512, deadlineMs: 5000, priority: 200, cleanupQuota: 128 }); // Semiring meet: tighter constraint wins // combine(outer, inner) → // pollQuota: min(2048, 512) = 512 // deadlineMs: min(30000, 5000) = 5000 // priority: max(100, 200) = 200 // cleanupQuota: min(512, 128) = 128
Budget Composition Visualizer
Click Run to see budget algebra in action
Exhibit 6

Generation Counters Reject Stale Handles

Integer IDs are easy to recycle incorrectly: an old reference can accidentally address new state in the same slot. Asupersync's handles carry a generation counter that increments on every slot recycle. Stale handles are rejected with a precise error message.

JavaScript
// Create and close a scope; its slot can be recycled const s1 = rt.enterScope('first').value; // s1 = { slot: N, generation: 0 } s1.close(); // slot N freed, generation → 1 // New scope reuses the same slot const s2 = rt.enterScope('second').value; // s2 = { slot: N, generation: 1 } // Try using the old handle: REJECTED const staleResult = scopeClose(s1); // → { outcome: "err", failure: { // code: "invalid_handle", // message: "StaleGeneration ..." // }}
Handle Slot Timeline
Click Run to see real handle recycling with generation counters
What the generation proves: A slot can be reused without letting an old generation address its new occupant. The exhibit shows the stale call being rejected at the ABI boundary.
Console Output
Exhibit 7

Deep Cascade Close

Asupersync regions form a tree with cascade close: close a node and its recorded descendants close children-first. This exhibit builds a four-level scope tree in WASM, closes a middle node, and probes each handle after the cascade.

WASM Scope Tree
Click Build Tree to create a real 4-level scope hierarchy
Why cascade close matters: Resource ownership stays aligned with the scope tree. One close operation reaches the descendants registered beneath that scope, reducing the number of cleanup paths application code must coordinate.
Cascade Events
Exhibit 8

Cancel Is Not Enough. You Must Join.

In Asupersync, task_cancel() requests cancellation. The task stays pinned. You must call task_join() to record the outcome and release the handle. The ledger makes this two-step protocol directly observable.

JavaScript
// Spawn a task. Its handle is PINNED. const t = scope.spawnTask({ label: 'worker' }).value; // Cancel transitions to Cancelling; the obligation remains live t.cancel('user', 'stop requested'); // Double-cancel fails because the task is already Cancelling const bad = t.cancel('timeout'); // → { outcome: "err", code: "invalid_handle" } // MUST join to release the handle and acknowledge t.join(Outcome.cancelled({ kind: 'user', phase: 'completed', origin_region: 'demo', message: 'stop requested' })); // → Handle unpinned, slot released. Clean.
Task State Machine (Live)
Click Run to exercise the cancel→join protocol in the WASM ledger
Protocol Events
Exhibit 9 · Interactive

Live WASM Lifecycle REPL

Type JavaScript below to call the shipped WASM lifecycle API directly. Handle allocation, scope transitions, task outcomes, and cancellation records cross the compiled Rust boundary; the browser remains responsible for scheduling JavaScript work. Open DevTools to inspect each ABI call.

Try:
REPL Output
Ready. Type code and click Run (or press Ctrl+Enter).
Exhibit 10 · Stress test

Lifecycle Stress Run

Rapid create, cancel, join, and close sequences exercise the places where stale generations and unresolved obligations tend to surface. This browser run performs 200 randomized operations against WASM handles, closes the root, then probes the handles it created to report what the ledger accepted or rejected.

Runtime Stress Test
Operations
0
Peak Handles
0
Max Generation
0
Errors Caught
0
Live after close
Not run
Ready 0%
Chaos Log
Exhibit 11 · Browser I/O

Scope-Bounded HTTP

In vanilla JavaScript, cancelling an in-flight fetch() requires manually creating an AbortController, threading its signal through the call, and remembering to call abort() on teardown. Browser Edition binds each host request to a region-owned fetch handle. Closing the region invalidates the associated lifecycle handles while the WASM host bridge aborts the browser requests.

Region-Scoped HTTP Lifecycle
Start 3 parallel requests, then close their parent region to cancel them all at once
Scope Events
Exhibit 12 · Outcome algebra

Outcome Severity as Algebra

Asupersync's four-valued Outcome forms a severity lattice: Ok < Err < Cancelled < Panicked. This exhibit records five mixed task outcomes in WASM and applies the same ordering used by the API.

Severity Lattice Composition
Click Run to spawn 5 tasks with different outcomes and watch severity propagate
Exhibit 13 · Diagnostics

Cancellation Forensics with Attribution

Asupersync's cancellation outcome carries a structured attribution record: the cancel kind (user/timeout/fail_fast/race_lost/shutdown), the phase (requested/draining/finalizing/completed), the originating region, task, timestamp, message, and whether the chain was truncated. The result keeps operational context attached to the terminal outcome.

Cancel Attribution Inspector
Click Run to cancel tasks with different reasons and inspect the full attribution
Exhibit 14 · Handle allocator

Handle Slot Recycling Under Pressure

Asupersync exposes deterministic slot allocation with LIFO free-list recycling. This exhibit performs 40 create/close cycles in one runtime instance and displays the observed slots, generation increments, and reuse order.

Slot Allocation Heatmap
Click Run to see 40 rapid create/close cycles with real slot tracking
Allocation Events
Exhibit 15 · Region structure

Sibling Scope Isolation

Sibling scopes have distinct handles and obligations. This exhibit creates four siblings with tasks, closes them one at a time, and probes each remaining sibling after every close. A successful probe is direct evidence that the living handle remains valid.

Sibling Scope Independence
Build four sibling scopes, then close them one-by-one and probe every survivor
Isolation Events
Exhibit 16 · Deterministic evidence

Checking LIFO Slot Reuse

Asupersync's handle allocator uses a deterministic LIFO free-list. This exhibit creates four scopes, frees them in order, then creates four more and compares the observed slots with the exact reverse sequence. The verdict comes from the returned handles, not a prewritten animation.

LIFO Recycling Check
Creates scopes, frees them, then verifies reuse follows LIFO order
Exhibit 17 · Obligation closure

Unjoined Task Handles at Scope Close

Spawned task handles are recorded as scope obligations. This exhibit intentionally leaves five handles unjoined, closes their parent scope, and probes the old handles afterward. The returned outcomes show whether the scope close invalidated each outstanding lifecycle record.

Obligation Enforcement
Spawns 5 tasks and intentionally "forgets" to join them
Leak Detection
Exhibit 18 · Host deadline

A Browser Deadline Closing One Scope

In vanilla JavaScript, implementing a timeout for a group of concurrent operations requires creating an AbortController, a setTimeout, wiring the signal to every fetch(), handling the AbortError in every .catch(), and clearing the timer on success. Here a host setTimeout closes one Asupersync scope. The deadline still comes from the browser; the scope supplies one ownership boundary for the three lifecycle handles beneath it.

Structural Timeout (2s deadline)
Records 3 task handles; a two-second browser timer closes their scope
Timeout Events
Exhibit 19 · Browser channel

Scoped Channel Lifecycle

Browser resources need an explicit ownership policy. This exhibit creates a browser MessageChannel, records each end as a task handle in a WASM scope, sends messages, then closes both the native ports and their lifecycle scope through one UI action.

Scoped Channel Lifecycle
Opens a MessageChannel with WASM-tracked tasks; one action closes both layers
Channel Events
Exhibit 20 · Coordination surface

Explicit Signals vs Scope-Owned Handles

Here is one three-request deadline written two ways. The left example wires a shared abort signal and timer directly. The right records request handles under one scope and closes that ownership boundary. This is an illustrative comparison of coordination surfaces, not a universal JavaScript benchmark.

Direct AbortController wiring
const controller = new AbortController(); // 1. create controller const signal = controller.signal; // 2. extract signal // 3. wire timeout to abort const timer = setTimeout(() => { controller.abort(); // 4. abort on timeout }, 5000); try { const results = await Promise.all([ fetch(url1, { signal }), // 5. pass signal fetch(url2, { signal }), // 6. pass signal fetch(url3, { signal }), // 7. pass signal ]); clearTimeout(timer); // 8. clear on success return results; } catch (err) { clearTimeout(timer); // 9. clear on error too! if (err.name === 'AbortError') { // 10. distinguish timeout from real error throw new Error('Request timed out'); } throw err; // 11. re-throw others }
Explicit coordination: the timer, signal propagation, success cleanup, and error classification all remain visible in application code.
Asupersync Browser Edition
const scope = rt.enterScope('requests').value; // The adapter associates each request with this scope scope.fetchRequest({ url: url1, method: 'GET' }); scope.fetchRequest({ url: url2, method: 'GET' }); scope.fetchRequest({ url: url3, method: 'GET' }); // A host timer closes the shared ownership boundary setTimeout(() => scope.close(), 5000);
One visible ownership boundary. The Browser Edition host bridge maps scope closure to request cancellation and lifecycle cleanup.
Exhibit 21 · ABI throughput

Rapid Scope Churn in This Browser

This local sample measures synchronous WASM ABI bookkeeping: one child-scope create plus close per cycle, including handle allocation, generation tracking, and parent registration. It does not measure native task scheduling, I/O throughput, or end-to-end application latency.

WASM ABI Bookkeeping Sample
Measures scope create/close calls without blocking the page for a full second