
Async Io Model
Implement or review Turso database core I/O using IOResult state machines and CompletionGroup instead of async/await.
Overview
Async I/O Model is an agent skill for the Build phase that teaches Turso core's IOResult state machines, CompletionGroup aggregation, and cooperative yield patterns for engine I/O.
Install
npx skills add https://github.com/tursodatabase/turso --skill async-io-modelWhat is this skill?
- Documents Turso's IOResult Done vs IO yield loop instead of Rust async/await
- Completion and CompletionGroup patterns for multi-operation waits
- Covers re-entrancy pitfalls and mandatory io_yield usage in core
- Nested CompletionGroup cancellation via group.cancel()
- State-machine calling convention: repeat until IOResult::Done
- Two IOResult variants: Done(T) and IO(IOCompletions)
- CompletionGroup supports nested groups and cancel()
Adoption & trust: 711 installs on skills.sh; 19.1k GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You are changing Turso core I/O but async/await intuition leads to broken re-entrant state machines and incomplete completion handling.
Who is it for?
Rust contributors working in tursodb core who must match the project's cooperative I/O design.
Skip if: App developers consuming Turso via hosted SQL or ORMs without touching the Rust engine source.
When should I use this skill?
Always use these patterns in tursodb core when doing anything I/O, per SKILL.md.
What do I get? / Deliverables
New or reviewed core code follows IOResult loops, CompletionGroup waits, and io_yield rules until operations return Done.
- Core patches or reviews that comply with IOResult and CompletionGroup usage
- Correct io_yield integration for multi-completion waits
Recommended Skills
Journey fit
How it compares
Engine-internal Rust patterns—not a Turso Cloud setup or migration skill.
Common Questions / FAQ
Who is async-io-model for?
Developers modifying Turso's Rust core who need the canonical IOResult and CompletionGroup model spelled out for agent-assisted patches.
When should I use async-io-model?
During Build/backend work on tursodb core whenever you add or refactor any I/O path—the skill says to always use these patterns in core.
Is async-io-model safe to install?
It is documentation-only for coding guidance; review the Security Audits panel on this page before trusting third-party skill sources.
SKILL.md
READMESKILL.md - Async Io Model
# Async I/O Model Guide Turso uses cooperative yielding with explicit state machines instead of Rust async/await. ## Core Types ```rust pub enum IOCompletions { Single(Completion), } #[must_use] pub enum IOResult<T> { Done(T), // Operation complete, here's the result IO(IOCompletions), // Need I/O, call me again after completions finish } ``` Functions returning `IOResult` must be called repeatedly until `Done`. ## Completion and CompletionGroup A `Completion` tracks a single I/O operation: ```rust pub struct Completion { /* ... */ } impl Completion { pub fn finished(&self) -> bool; pub fn succeeded(&self) -> bool; pub fn get_error(&self) -> Option<CompletionError>; } ``` To wait for multiple I/O operations, use `CompletionGroup`: ```rust let mut group = CompletionGroup::new(|_| {}); // Add individual completions group.add(&completion1); group.add(&completion2); // Build into single completion that finishes when all complete let combined = group.build(); io_yield_one!(combined); ``` `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: ```rust let result = return_if_io!(some_io_operation()); // Only reaches here if operation returned Done ``` ### `io_yield_one!` Yields a single completion: ```rust io_yield_one!(completion); // Returns Ok(IOResult::IO(Single(completion))) ``` ## State Machine Pattern Operations that may yield use explicit state enums: ```rust enum MyOperationState { Start, WaitingForRead { page: PageRef }, Processing { data: Vec<u8> }, Done, } ``` The function loops, matching on state and transitioning: ```rust fn my_operation(&mut self) -> Result<IOResult<Output>> { loop { match &mut self.state { MyOperationState::Start => { let (page, completion) = start_read(); self.state = MyOperationState::WaitingForRead { page }; io_yield_one!(completion); } MyOperationState::WaitingForRead { page } => { let data = page.get_contents(); self.state = MyOperationState::Processing { data: data.to_vec() }; // No yield, continue loop } MyOperationState::Processing { data } => { let result = process(data); self.state = MyOperationState::Done; return Ok(IOResult::Done(result)); } MyOperationState::Done => unreachable!(), } } } ``` ## Re-Entrancy: The Critical Pitfall **State mutations before yield points cause bugs on re-entry.** ### Wrong ```rust fn bad_example(&mut self) -> Result<IOResult<()>> { self.counter += 1; // Mutates state return_if_io!(something_that_might_yield()); // If yields, re-entry will increment again! Ok(IOResult::Done(())) } ``` 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 ```rust fn good_example(&mut self) -> Result<IOResult<()>> { return_if_io!(something_that_might_yield()); self.counter += 1; // Only reached once, after IO completes Ok(IOResult::Done(())) } ``` ### Correct: Use State Machine ```rust enum State { Start, AfterIO } fn good_example(&mut self) -> Result<IOResult<()>> { loop { match self.state { State::Start => { // Don't mutate shared state here self.state = St