Skip to content

sugarraysam/ringolo

Repository files navigation

Ringolo - An io_uring Async Runtime

Rust Crates.io Docs.rs License

Ringolo is a completion-driven asynchronous runtime built on top of io_uring. Its task system is derived from Tokio's battle-tested task module, which implements a state machine for managing a future's lifecycle.

⚡ Quick Start

#[ringolo::main]
async fn main() {
    println!("Hello from the runtime!");

    let join_handle = ringolo::spawn(async {
        // ... do some async work ...
        "Hello from a spawned task!"
    });

    let result = join_handle.await.unwrap();
    println!("{}", result);
}

✨ Key Features

🚀 Submission Strategies

The runtime supports four distinct submission backends to optimize throughput and latency:

Strategy Description Driver Example
Single Standard 1:1 dispatch. One SQE results in one CQE. Op Sleep
Chain Strict kernel-side ordering via IOSQE_IO_LINK. Defines dependent sequences without userspace latency. OpList::new_chain TcpListener::bind
Batch Same as Chain, except ops execute concurrently and complete in any order. OpList::new_batch N/A
Multishot Single SQE establishes a persistent request that generates a stream of CQEs (e.g. timers, accept). Multishot Tick

⚙️ I/O Model: Readiness vs. Completion

A key difference from Tokio is the underlying I/O model. Tokio is readiness-based, typically using epoll. This epoll context is global and accessible by all threads. Consequently, if a task is stolen by another thread, readiness events remain valid.

Ringolo is completion-based, using io_uring. This model is fundamentally thread-local, as each worker thread manages its own ring. Many resources, such as registered direct descriptors or provided buffers, are bound to that specific ring and are invalid on any other thread.

🧵 Work-Stealing and Thread-Local Resources

This thread-local design presents a core challenge for work-stealing. When an I/O operation is submitted on a thread's ring, its corresponding completion event must be processed by the same thread.

If a task were migrated to another thread after submission but before completion, the resulting I/O event would be delivered to the original thread's ring, but the task would no longer be there to process it. This would lead to lost I/O and undefined behavior.

Ringolo's work-stealing scheduler is designed around this constraint. It performs resource and pending I/O accounting to determine when a task is "stealable". View the detailed implementation within the task module.

🌳 Structured Concurrency

Another key difference from Tokio is Ringolo's adoption of Structured Concurrency. While this can be an overloaded term, in Ringolo it provides a simple guarantee: tasks are not allowed to outlive their parent.

To enforce this, the runtime maintains a global task tree to track the task hierarchy. When a parent task exits, all of its child tasks are automatically cancelled.

This behavior is controlled by the orphan policy. The default policy is OrphanPolicy::Enforced, which is the recommended setting for most programs. This behavior can be relaxed in two ways:

  1. Per-Task: To create a single "detached" task, you can explicitly use TaskOpts::BACKGROUND_TASK. This option bypasses the current task's hierarchy by attaching the new task directly to the ROOT_NODE of the task tree.

  2. Globally: You can configure the entire runtime with OrphanPolicy::Permissive. This setting effectively disables structured concurrency guarantees for all tasks, but it is not the intended model for typical use.

🛑 Motivation: Safer Cancellation APIs

The primary motivation for this design is to provide powerful and safe cancellation APIs.

Experience with other asynchronous frameworks, including Tokio and folly/coro, shows that relying on cancellation tokens has significant drawbacks. Manually passing tokens is error-prone, introduces code bloat, and becomes exceptionally difficult to manage correctly in a large codebase.

Ringolo's design provides a user-friendly way to perform cancellation without requiring tokens to be passed throughout the call stack. The global task tree enables this robust, built-in cancellation model.

For a detailed guide, please see the cancellation APIs.

🛡️ Kernel Interface & Pointer Stability

Interfacing with io_uring requires passing raw memory addresses to the kernel. To prevent undefined behavior, the runtime guarantees strict pointer stability based on the data's role:

  • Read-only inputs: Pointers to input data (such as file paths) are guaranteed to remain stable until the request is submitted.
  • Writable outputs: Pointers to mutable buffers (such as read destinations) are guaranteed to remain stable until the operation completes.

Ringolo handles these complex lifetime requirements transparently using self-referential structs and pinning. By taking ownership of resources and pinning them in memory, the runtime ensures the kernel never encounters a dangling pointer or a use-after-free error.

For implementation details on these safe primitives, see the future::lib module.

🧹 Async cleanup via RAII

The thread-local design of io_uring also dictates the model for resource cleanup. Certain "leaky" operations, like multishot timers, and thread-local resources, like direct descriptors, must be explicitly unregistered or closed.

This cleanup must happen on the same thread's ring that created them. To solve this without blocking on drop, Ringolo uses a maintenance task on each worker thread to handle this transparently. When a future is dropped, it enqueues an async cleanup operation with its local maintenance task. This task then batches and submits these operations, ensuring all resources are freed on the correct thread.

The runtime's behavior on a failed cleanup operation is controlled by the OnCleanupError policy.

Exiting Alpha Status

The current releases will remain in alpha status until the following milestones are met:

Contributing

Contributions are welcome! Please feel free to open an issue or submit a pull request.

About

An asynchronous runtime built on top of io-uring.

Resources

License

Stars

Watchers

Forks

Languages