Suspension
Preview — Ranvier is pre-release; its APIs may change.
The pending channel is what lets a computation read an async source that has no value yet. On .NET, a transparent read of a Pending source aborts the reading body by throwing, and an exception is the most expensive single operation in the design. These benchmarks measure what that costs and how a boundary limits it. See Benchmarks for configuration, environment and caveats.
SuspensionBenchmarks
A chain of Depth frames (1, 4 or 16) between the suspending read and the reader. Each iteration writes a trigger that the chain's first memo reads, so every read re-runs the chain.
| Method | What it measures |
|---|---|
RecomputeSettledChain | Baseline. Re-running the chain to completion over a settled source. |
ThrowThroughChain | Re-running the chain and aborting it on a source that never settles. The difference from the baseline is the price of the throw. |
Recorded 2026-09-28. Source: Ranvier.Benchmarks.Suspension.SuspensionBenchmarks at commit d87920f.
| Method | Depth | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| RecomputeSettledChain | 1 | 18.30 ns | 0.232 ns | 0.217 ns | 54,656,258.3 | baseline | **** | - | - | NA |
| ThrowThroughChain | 1 | 1,460.60 ns | 11.381 ns | 10.089 ns | 684,650.1 | 79.84x slower | 1.06x | 0.0286 | 496 B | NA |
| RecomputeSettledChain | 4 | 64.20 ns | 0.850 ns | 0.795 ns | 15,577,452.1 | baseline | **** | - | - | NA |
| ThrowThroughChain | 4 | 1,517.24 ns | 14.803 ns | 13.847 ns | 659,091.4 | 23.64x slower | 0.35x | 0.0286 | 496 B | NA |
| RecomputeSettledChain | 16 | 235.95 ns | 2.367 ns | 1.977 ns | 4,238,127.7 | baseline | **** | - | - | NA |
| ThrowThroughChain | 16 | 1,637.23 ns | 17.495 ns | 16.365 ns | 610,786.5 | 6.94x slower | 0.09x | 0.0286 | 496 B | NA |
BoundaryBenchmarks
A boundary catches the pending channel instead of letting it propagate, so the cost of a suspended subtree is bounded by where the boundary sits rather than by the depth of the whole graph. Each iteration writes a trigger both bodies read, so every read re-runs the body.
| Method | What it measures |
|---|---|
CatchingPending | Re-running a body that is still waiting, with the boundary catching its throw. |
CleanBoundary | Baseline. Re-running the body over settled sources: the price of a boundary when nothing is in flight. |
Recorded 2026-09-28. Source: Ranvier.Benchmarks.Suspension.BoundaryBenchmarks at commit d87920f.
| Method | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| CatchingPending | 979.54 ns | 15.940 ns | 14.910 ns | 1,020,886.0 | 40.76x slower | 0.95x | 0.0153 | 256 B | 8.00x more |
| CleanBoundary | 24.04 ns | 0.450 ns | 0.442 ns | 41,600,534.4 | baseline | 0.0019 | 32 B |
SettleBenchmarks
Settling a source is where a pending subtree becomes live, and it is the only path in the library that can arrive from another thread.
| Method | What it measures |
|---|---|
CreateAndRead | Baseline. Constructing an AsyncSource and reading it: the floor for SettleInline. |
SettleInline | Constructing a source and settling it on the graph's own thread, which takes the inline path and never touches the dispatcher. |
Recorded 2026-09-28. Source: Ranvier.Benchmarks.Suspension.SettleBenchmarks at commit d87920f.
| Method | Mean | Error | StdDev | Op/s | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| SettleInline | 10.240 ns | 0.2031 ns | 0.1696 ns | 97,652,054.4 | 1.90x slower | 0.04x | 0.0076 | 128 B | 1.33x more |
| CreateAndRead | 5.402 ns | 0.0920 ns | 0.0816 ns | 185,130,337.5 | baseline | 0.0057 | 96 B |
FlightPolicyBenchmarks
The price of cancellation per async-memo flight. Each iteration writes a trigger the body reads, then reads the memo, which launches one flight. The body ignores its token, so the cancel runs no callbacks. Flight is Completed when the body returns a completed task, and Superseded when it returns a task that never completes, so every launch supersedes a flight in progress.
| Method | What it measures |
|---|---|
KeepLatest | Baseline. A launch under FlightPolicy.KeepLatest, where every flight shares the token source allocated at the first flight. |
CancelPrevious | A launch under FlightPolicy.CancelPrevious, which cancels and disposes the previous flight's token source and allocates a new one. |
Not yet recorded.
FlightBenchmarks
Repeated flight launches under each FlightPolicy, set by Policy. Writes applies to WritesDuringFlight only.
| Method | What it measures |
|---|---|
RelaunchSettled | Baseline. Writes a trigger the body reads and reads the memo. The body returns a completed task, so every flight settles on launch. |
WritesDuringFlight | Starts a flight whose task stays open, writes and reads Writes more times, then completes every open task, reading after each round. Under FinishCurrent the body runs twice whatever Writes is; under the other policies it runs Writes + 1 times. |
The counter scenario flight runs the same shape at one write during a flight, once per policy: two writes and reads, then a settle of every flight the writes started. Figures are retired instructions per operation at commit e13f159, .NET 10 with tiered compilation and PGO off (see Instruction counts):
| Policy | instr/op | bytes/op | Library counters/op |
|---|---|---|---|
CancelPrevious | 7,520 | 1,720 | 0 |
KeepLatest | 7,348 | 1,672 | 0 |
Queue | 15,065 | 3,184 | 0 |
FinishCurrent | 7,655 | 1,744 | 0 |
CancelPrevious, KeepLatest and FinishCurrent cost the same within about 4 %. Queue costs about twice as much in instructions and bytes. The timing cases are in the suite for local runs (dotnet run --project bench/Ranvier.Benchmarks -c Release -- --filter "*FlightBenchmarks*") and have no published figure yet. The full report is e13f159.md.
FailureBenchmarks
The error channel's cost on a recomputation. Each iteration writes a trigger the memo reads, so every read re-runs the body.
| Method | What it measures |
|---|---|
SucceedingRecompute | Baseline. Re-running a memo whose body succeeds. |
FailingRecompute | Re-running a memo whose body throws a fresh exception. The failure records the memo as its origin. |
The counter scenario fail-recompute measures the same recomputation with one reader, then reads the reader's ErrorOrigin. Figures are retired instructions per operation at commit e13f159, .NET 10 with tiered compilation and PGO off (see Instruction counts):
| Variant | instr/op | bytes/op | Library counters/op |
|---|---|---|---|
| Succeeding | 1,131 | 0 | MemoRecomputes 2 |
| Failing | 62,686 | 1,576 | MemoRecomputes 2 |
A failing recompute costs about 55 times a succeeding one, and the figure includes throwing a fresh .NET exception. The timing cases are in the suite for local runs (dotnet run --project bench/Ranvier.Benchmarks -c Release -- --filter "*FailureBenchmarks*") and have no published figure yet. The full report is e13f159.md.
FailureChainBenchmarks
A failure's cost per reader: a memo whose body throws a fresh exception, read through Depth memos (1, 4 or 16) that each read the one before with Value. Each reader adopts the upstream failure and its origin.
| Method | What it measures |
|---|---|
FailingRecomputeOneHop | Re-running the failing memo and every reader on the path, then reading the last reader's failure. |
The failing row of fail-recompute above measures Depth 1. Depth 4 and 16 are in the suite for local runs (dotnet run --project bench/Ranvier.Benchmarks -c Release -- --filter "*FailureChainBenchmarks*") and have no published figure yet.
The Ratio and Alloc Ratio columns, where present, compare each method with the baseline method of the same class on the same run. They describe the relative cost of two operations inside Ranvier, not a comparison with any other library.