# Async I/O Patterns in Turso Core: Understanding IOResult and State Machines

> Explore async I/O patterns in Turso core, detailing IOResult and state machines for efficient, non-blocking database operations and cooperative multitasking.

- Repository: [Turso Database/turso](https://github.com/tursodatabase/turso)
- Tags: internals
- Published: 2026-06-22

---

**Turso implements a lightweight asynchronous I/O model using the `IOResult<T>` enum and `Completion` objects to interleave database computation with kernel I/O without blocking threads, enabling cooperative multitasking through poll-based state machines.**

The Turso database engine (`tursodatabase/turso`) uses a novel async I/O architecture that avoids traditional `async`/`await` syntax while still achieving non-blocking disk operations. At the heart of this system lies the `IOResult` state machine, which allows the virtual database engine (VDBE) to suspend execution when hitting disk I/O and resume seamlessly once the kernel signals completion.

## The IOResult Enum – Core of the State Machine

The `IOResult<T>` enum in [`core/types.rs`](https://github.com/tursodatabase/turso/blob/main/core/types.rs) provides the fundamental abstraction for distinguishing between synchronous completion and pending I/O operations. This enum acts as a discriminated union that propagates state through the entire call stack.

```rust
/// https://github.com/tursodatabase/turso/blob/main/core/types.rs#L3041-L3046
#[must_use]
#[derive(Debug, Clone)]
pub enum IOResult<T> {
    /// The operation finished synchronously; the value is ready.
    Done(T),

    /// The operation needs to wait for an asynchronous I/O completion.
    IO(IOCompletions),
}

```

- **`Done(T)`** represents the synchronous path where the value is immediately available.
- **`IO(IOCompletions)`** indicates that the operation must yield control to the scheduler until the kernel completes the underlying I/O.

Helper methods such as `is_io()`, `io()`, and `map()` make the enum ergonomic to work with throughout the codebase, allowing the VM to test for pending I/O without manual pattern matching in hot paths.

## Completion Objects and Kernel I/O Representation

`IOCompletions` wraps concrete kernel-level I/O requests and implements the `Future` trait, enabling integration with Rust's async ecosystem while maintaining Turso's poll-based approach. The `Completion` struct in [`core/io/completions.rs`](https://github.com/tursodatabase/turso/blob/main/core/io/completions.rs) encapsulates the internal state of an operation.

```rust
/// https://github.com/tursodatabase/turso/blob/main/core/io/completions.rs#L24-L38
#[must_use]
#[derive(Debug, Clone)]
pub struct Completion {
    /// Holds the internal state of the operation; `None` means a cooperative yield.
    pub(super) inner: Option<Arc<CompletionInner>>,
}

```

The `Future` implementation drives the state machine by checking whether the kernel has signaled completion:

```rust
/// https://github.com/tursodatabase/turso/blob/main/core/io/completions.rs#L31-L44
impl Future for Completion {
    type Output = Result<(), crate::LimboError>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.set_waker(cx.waker());
        if self.finished() {
            self.wake();
            let res = self.get_error()
                .map_or(Ok(()), |err| Err(crate::LimboError::CompletionError(err)));
            return Poll::Ready(res);
        }
        Poll::Pending
    }
}

```

When a kernel operation completes, `CompletionInner` stores the result or error and invokes the waker. The `IOResult::IO` variant carries this `Completion` upward through the call stack, allowing the scheduler to register it for wakeup when the I/O finishes.

## Propagating Pending I/O with `return_if_io!`

The `return_if_io!` macro in [`core/types.rs`](https://github.com/tursodatabase/turso/blob/main/core/types.rs) eliminates boilerplate when handling `IOResult` values throughout the VDBE. This macro extracts the inner result, returning early if the operation is pending I/O while preserving the synchronous coding style for the common case.

```rust
/// https://github.com/tursodatabase/turso/blob/main/core/types.rs#L71-L84
#[macro_export]
macro_rules! return_if_io {
    ($expr:expr) => {
        match $expr {
            Ok(IOResult::Done(v)) => v,
            Ok(IOResult::IO(io)) => return Ok(IOResult::IO(io)),
            Err(err) => {
                branches::mark_unlikely();
                return Err(err);
            }
        }
    };
}

```

Typical usage inside the VDBE executor appears in [`core/vdbe/execute.rs`](https://github.com/tursodatabase/turso/blob/main/core/vdbe/execute.rs):

```rust
// https://github.com/tursodatabase/turso/blob/main/core/vdbe/execute.rs#L1158-L1162
let value = return_if_io!(cursor.rowid());
// `value` is only reachable if the rowid was ready synchronously.

```

If `cursor.rowid()` needs to read a page from disk, it returns `IOResult::IO(completion)`. The macro immediately bubbles this variant up to the caller, which yields control to the cooperative scheduler. Once the kernel finishes the read, the scheduler re-invokes the VM, where the macro now matches `IOResult::Done` and execution proceeds.

## Platform-Specific I/O Backends

All I/O implementations—whether Linux `io_uring`, Unix syscalls, Windows IOCP, or generic fallbacks—implement the `IO` trait defined in [`core/io/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/io/mod.rs). This trait abstracts platform specifics while maintaining the `Completion`-based contract.

```rust
/// https://github.com/tursodatabase/turso/blob/main/core/io/mod.rs#L51-L57
pub trait IO: Send + Sync {
    fn open_file(&self, path: &str, flags: OpenFlags, direct: bool) -> Result<Arc<dyn File>>;
    fn pread(&self, pos: u64, c: Completion) -> Result<Completion>;
    fn pwrite(&self, pos: u64, buffer: Arc<Buffer>, c: Completion) -> Result<Completion>;
    // …
}

```

Concrete implementations like `UnixIO` in [`core/io/unix.rs`](https://github.com/tursodatabase/turso/blob/main/core/io/unix.rs) translate these calls to the appropriate system APIs (`pread`, `pwrite`, or `io_uring` submit operations) and construct a `Completion` that understands how to complete the future when the kernel signals readiness.

## End-to-End Flow in the VDBE

Understanding how these components interact requires tracing a complete I/O operation. When the VDBE executes an opcode requiring disk access, the following sequence occurs:

1. The opcode calls a method that returns `IOResult<T>`.
2. If the data is cached, the method returns `IOResult::Done(value)`.
3. If the data requires disk I/O, the backend creates a `Completion`, submits it to the kernel, and returns `IOResult::IO(completion)`.
4. The `return_if_io!` macro catches this variant and returns it up the call stack to the scheduler.
5. The scheduler parks the current task and registers the `Completion` waker.
6. When the kernel completes the I/O, it invokes the waker, marking the task as runnable.
7. The scheduler re-enters the VM at the same opcode, which now receives `IOResult::Done` with the buffered data.

This pattern allows Turso to achieve full async I/O without viral `async` annotations throughout the codebase, keeping the core VM code straightforward and debuggable while maintaining high concurrency.

## Summary

- **`IOResult<T>`** provides a strict state machine distinguishing synchronous results (`Done`) from pending kernel operations (`IO`).
- **`Completion` objects** encapsulate platform-specific I/O requests and implement `Future` for integration with the cooperative scheduler.
- **`return_if_io!` macro** eliminates boilerplate when propagating pending I/O up the call stack, preserving synchronous coding patterns.
- **`IO` trait** abstracts over Linux `io_uring`, Unix syscalls, Windows IOCP, and generic backends, ensuring portable async behavior.
- **Zero-cost abstraction** allows the VDBE to interleave computation with I/O without thread blocking or `async`/`await` syntax overhead.

## Frequently Asked Questions

### What is the purpose of IOResult in Turso?

`IOResult` serves as the fundamental state machine for async I/O operations in Turso's core engine. It allows the database to distinguish between operations that complete immediately in memory (`Done`) versus those that require waiting for the kernel to finish disk I/O (`IO`). This enum propagates through the entire call stack, enabling the cooperative scheduler to suspend and resume execution without blocking OS threads.

### How does Turso handle async I/O without async/await syntax?

Turso uses a poll-based model where `Completion` objects implement the `Future` trait but are driven by a custom scheduler rather than Rust's async runtime. The `return_if_io!` macro allows the VDBE to write straight-line code that automatically bubbles pending I/O up to the scheduler. When the kernel completes an operation, it wakes the corresponding `Completion`, allowing the scheduler to resume the specific opcode that was waiting.

### What platforms does Turso's async I/O support?

The `IO` trait in [`core/io/mod.rs`](https://github.com/tursodatabase/turso/blob/main/core/io/mod.rs) abstracts over multiple backends including Linux `io_uring`, traditional Unix syscalls (`pread`/`pwrite`), Windows IOCP, and generic fallbacks. Each implementation in [`core/io/unix.rs`](https://github.com/tursodatabase/turso/blob/main/core/io/unix.rs) or platform-specific modules constructs `Completion` objects appropriate for that OS's async I/O mechanisms while exposing the same `IOResult`-based interface to the VDBE.

### How does the return_if_io! macro improve performance?

The `return_if_io!` macro improves performance by eliminating branch prediction failures on the hot path. By marking error branches with `branches::mark_unlikely()`, it hints to the compiler that I/O suspension is the exceptional case, keeping the fast path optimized for cache hits. This macro also prevents the code bloat associated with manual pattern matching on `IOResult` throughout the hundreds of opcode implementations in the VDBE.