async-io-model
Implements Turso-specific cooperative I/O patterns to manage asynchronous operations without standard async/await.
Install
mkdir -p .claude/skills/async-io-model && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/7243" && unzip -o skill.zip -d .claude/skills/async-io-model && rm skill.zipInstalls to .claude/skills/async-io-model
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
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 IOKey capabilities
- →Define state enums for operations that yield
- →Implement functions that loop and match on state
- →Use `return_if_io!` to propagate IO variants
- →Use `io_yield_one!` to yield a single completion
- →Aggregate multiple completions with `CompletionGroup`
- →Avoid state mutations before yield points
How it works
Operations that may yield use explicit state enums, and functions loop, matching on state and transitioning until the operation is done.
Inputs & outputs
When to use async-io-model
- →Implementing database I/O operations
- →Managing async completion groups
- →Refactoring blocking code to Turso async model
About this skill
Async I/O Model Guide
Turso uses cooperative yielding with explicit state machines instead of Rust async/await.
Core Types
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:
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:
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:
let result = return_if_io!(some_io_operation());
// Only reaches here if operation returned Done
io_yield_one!
Yields a single completion:
io_yield_one!(completion); // Returns Ok(IOResult::IO(Single(completion)))
State Machine Pattern
Operations that may yield use explicit state enums:
enum MyOperationState {
Start,
WaitingForRead { page: PageRef },
Processing { data: Vec<u8> },
Done,
}
The function loops, matching on state and transitioning:
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
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
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
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:
// 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,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
Limitations
- →Requires careful management of state mutations to avoid re-entrancy bugs
- →Requires repeated calls to functions returning IOResult until Done is returned
How it compares
This model uses cooperative yielding with explicit state machines instead of Rust's async/await, requiring manual state management for asynchronous operations.
Compared to similar skills
async-io-model side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| async-io-model (this skill) | 1 | 6mo | Review | Advanced |
| drizzle-orm | 32 | 2mo | No flags | Intermediate |
| django-pro | 20 | 4mo | No flags | Intermediate |
| event-store-design | 5 | 2mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by tursodatabase
View all by tursodatabase →You might also like
drizzle-orm
EpicenterHQ
Drizzle ORM patterns for type branding and custom types. Use when working with Drizzle column definitions, branded types, or custom type conversions.
django-pro
sickn33
Master Django 5.x with async views, DRF, Celery, and Django Channels. Build scalable web applications with proper architecture, testing, and deployment. Use PROACTIVELY for Django development, ORM optimization, or complex Django patterns.
event-store-design
wshobson
Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implementing event persistence patterns.
senior-backend
davila7
Comprehensive backend development skill for building scalable backend systems using NodeJS, Express, Go, Python, Postgres, GraphQL, REST APIs. Includes API scaffolding, database optimization, security implementation, and performance tuning. Use when designing APIs, optimizing database queries, implementing business logic, handling authentication/authorization, or reviewing backend code.
backend-development
skillcreatorai
Backend API design, database architecture, microservices patterns, and test-driven development. Use for designing APIs, database schemas, or backend system architecture.
postgresql
sickn33
Design a PostgreSQL-specific schema. Covers best-practices, data types, indexing, constraints, performance patterns, and advanced features