async-io-model — Claude Code skill: what it does, how to install
Teaches the agent Turso's cooperative async I/O patterns: explicit state machines, IOResult, and avoiding re-entrancy bugs.
npx skills add tursodatabase/turso --skill async-io-model -a claude-codeWhat async-io-model does
Turso avoids Rust's native async/await inside its database engine, using explicit state machine enums and an IOResult type instead. This skill loads those exact patterns into the agent context. It covers tracking pending disk operations through Completion, aggregating batches via CompletionGroup, and driving control loops with return_if_io! and io_yield_one!.
The critical pitfall covered in this guide is re-entrancy. Mutating data structures like advancing an index or calling push before return_if_io! causes duplicate work because callers re-enter the function once I/O completes. The rules direct the agent to mutate state only after yield points or to encode progress directly into state enum variants.
These conventions apply to Turso's core directory, and developers test them using deterministic simulation under testing/simulator/. For applications running on standard Tokio runtimes, these patterns are irrelevant.
What it helps with
- convert asynchronous Rust logic into explicit state machines using IOResult
- detect state mutations placed before return_if_io! yield points
- aggregate concurrent I/O operations with CompletionGroup
- structure state enum variants to preserve iteration progress across yields
Example prompts for the agent
- Refactor this I/O loop into an explicit state machine using IOResult
- Audit this storage function for re-entrancy bugs around return_if_io!
- Combine these Completion operations using CompletionGroup
How to install async-io-model
1Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a claude-code2Manually: put the files in place
~/.claude/skills/async-io-model/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/async-io-model/
1Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a codex2Manually: put the files in place
.agents/skills/async-io-model/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a cursor2Manually: put the files in place
.cursor/skills/async-io-model/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install tursodatabase/turso async-io-model2Manually: put the files in place
.github/skills/async-io-model/SKILL.md1Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a gemini-cli2Platform CLI
gemini skills install https://github.com/tursodatabase/turso --path .claude/skills/async-io-modelManually: put the files in place
.devin/skills/async-io-model/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set10
The whole set →Similar
Questions about async-io-model
How to install async-io-model in Claude Code?
Run npx skills add tursodatabase/turso --skill async-io-model -a claude-code in your terminal.
What does async-io-model do?
It equips the coding agent with Turso's core asynchronous model, replacing Rust async/await with IOResult, completion tracking, and cooperative state loops.
Why are mutations before return_if_io! problematic?
If an operation yields IO, the caller pauses and invokes the function again later. Any mutation executed before that yield runs twice, corrupting collections and indices.
Source text
SKILL.md · MIT
---
name: async-io-model
description: Explanations of common asynchronous patterns used in tursodb. Involves IOResult, state machines, re-entrancy pitfalls, CompletionGroup. Always use these patterns in `core` when doing anything IO
---
# 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 = State::AfterIO;
return_if_io!(something_that_might_yield());
}
State::AfterIO => {
self.counter += 1; // Safe: only entered once
return Ok(IOResult::Done(()));
}
}
}
}
```
## Common Re-Entrancy Bugs
| Pattern | Problem |
|---------|---------|
| `vec.push(x); return_if_io!(...)` | Vec grows on each re-entry |
| `idx += 1; return_if_io!(...)` | Index advances multiple times |
| `map.insert(k,v); return_if_io!(...)` | Duplicate inserts or overwrites |
| `flag = true; return_if_io!(...)` | Usually ok, but check logic |
## State Enum Design
Encode progress in state variants:
```rust
// Good: index is part of state, preserved across yields
enum ProcessState {
Start,
ProcessingItem { idx: usize, items: Vec<Item> },
Done,
}
// Loop advances idx only when transitioning states
ProcessingItem { idx, items } => {
return_if_io!(process_item(&items[idx]));
if idx + 1 < items.len() {
self.state = ProcessingItem { idx: idx + 1, items };
} else {
self.state = Done;
}
}
```
## Turso Implementation
Key files:
- `core/types.rs` - `IOResult`, `IOCompletions`, `return_if_io!`, `return_and_restore_if_io!`
- `core/io/completions.rs` - `Completion`, `CompletionGroup`
- `core/util.rs` - `io_yield_one!` macro
- `core/state_machine.rs` - Generic `StateMachine` wrapper
- `core/storage/btree.rs` - Many state machine examples
- `core/storage/pager.rs` - `CompletionGroup` usage 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.md` section on I/O
© tursodatabase · MIT · tursodatabase/turso