Async I/O Model Guide
Turso uses cooperative yielding with explicit state machines instead of Rust async/await.
Core Types
Functions returning IOResult must be called repeatedly until Done.
Completion and CompletionGroup
A Completion tracks a single I/O operation:
To wait for multiple I/O operations, use CompletionGroup:
CompletionGroup features:
- Aggregates multiple completions into one
- Calls callback when all complete (or any errors)
- Can nest groups (add a group's completion to another group)
- Cancellable via
group.cancel()
Helper Macros
return_if_io!
Unwraps IOResult, propagates IO variant up the call stack:
io_yield_one!
Yields a single completion:
State Machine Pattern
Operations that may yield use explicit state enums:
The function loops, matching on state and transitioning:
Re-Entrancy: The Critical Pitfall
State mutations before yield points cause bugs on re-entry.
Wrong
If something_that_might_yield() returns IO, caller waits for completion, then calls bad_example() again. counter gets incremented twice (or more).
Correct: Mutate After Yield
Correct: Use State Machine
Common Re-Entrancy Bugs
State Enum Design
Encode progress in state variants:
Turso Implementation
Key files:
core/types.rs-IOResult,IOCompletions,return_if_io!,return_and_restore_if_io!core/io/completions.rs-Completion,CompletionGroupcore/util.rs-io_yield_one!macrocore/state_machine.rs- GenericStateMachinewrappercore/storage/btree.rs- Many state machine examplescore/storage/pager.rs-CompletionGroupusage examples
Testing Async Code
Re-entrancy bugs often only manifest under specific IO timing. Use:
- Deterministic simulation (
testing/simulator/) - Whopper concurrent DST (
testing/concurrent-simulator/) - Fault injection to force yields at different points
References
docs/manual.mdsection on I/O


