Skip to main content

Crate compio_pool

Crate compio_pool 

Source
Expand description

A bb8-style connection pool for compio on Linux io_uring.

You build one Pool — the manager plus a handful of Builder knobs — then clone that handle onto each thread of your own thread-per-core runtime. On every thread you call Pool::local once to get that thread’s LocalPool, and LocalPool::get to lease a connection. The pool is the product; spawning threads, pinning them and running an accept loop are yours.

§Why it is shaped this way

A compio connection is bound to the io_uring ring of the thread that opened it: its buffers are registered with that ring and its completions are delivered there. It cannot be used from another thread. So, unlike bb8 on tokio, the pool of connections cannot be a single shared, work-stealing store. What is shared is only the blueprint:

  • Pool — Send + Sync + Clone. The manager and the configuration, behind one Arc. Cloning is an Arc bump. This is the only thing that crosses threads.
  • LocalPool — !Send, one per thread. The idle connections and the slot count, all Rc/Cell, no lock, no atom. The compiler keeps it, and the ring-bound connections it holds, on its own thread.

The parts of bb8 that make sense per thread are here — max_size, idle and lifetime timeouts, test_on_check_out, min_idle warm-up — sized per thread, not per process.

§Running it in production

Three things a long-lived server needs are built on the same split:

§Quickstart

Define a manager, build the pool, and use it from a compio task:

use std::io;

use compio_pool::{ManageConnection, Pool};

/// A pool of reusable 16 KiB scratch buffers. A real manager would open a
/// backend connection (Redis, Postgres, an upstream socket) in `connect`.
struct Buffers;

impl ManageConnection for Buffers {
    type Connection = Vec<u8>;
    type Error = io::Error;

    async fn connect(&self) -> io::Result<Vec<u8>> {
        Ok(Vec::with_capacity(16 * 1024))
    }
    async fn is_valid(&self, _buf: &mut Vec<u8>) -> io::Result<()> {
        Ok(())
    }
    fn has_broken(&self, _buf: &mut Vec<u8>) -> bool {
        false
    }
}

// Build once; `Pool` is `Send + Clone`, so hand a clone to each thread.
let pool = Pool::builder().max_size(1024).build(Buffers);

// On each compio thread you spawn (see `examples/echo.rs` for the full
// pinned, SO_REUSEPORT thread-per-core setup):
let local = pool.local(); // this thread's own pool, `!Send`
let mut buf = local.get().await?; // PooledConnection, returned on drop
buf.clear();

The crate also ships the two helpers that building your own thread-per-core loop needs: cpu for core enumeration and pinning, and listener::bind_reuseport for a shared-address SO_REUSEPORT socket.

§Platform

Linux is the only supported target: SO_REUSEPORT load balancing and io_uring are Linux semantics. The crate type-checks on other Unixes so editors work, but nothing is tested there.

Re-exports§

pub use builder::Builder;
pub use listener::bind_reuseport;
pub use manage::ManageConnection;
pub use manage::RunError;
pub use pool::LocalPool;
pub use pool::Pool;
pub use pool::PooledConnection;
pub use pool::State;
pub use pool::Statistics;

Modules§

builder
Build a Pool from a manager and a handful of knobs — the bb8 Builder, minus the settings that only make sense for a single shared pool.
cpu
Core enumeration and thread pinning — the helpers you need to stand up your own thread-per-core runtime around a Pool.
listener
A SO_REUSEPORT listener helper, for building your own thread-per-core accept loop around a Pool.
manage
The manager trait and the error get can return — the bb8 vocabulary, adapted for a completion runtime where a connection never leaves the thread it was opened on.
pool
The pool: a Send blueprint you build once and clone onto every thread, and the strictly thread-local pool it stamps out on each.

Structs§

CoreId
This represents a CPU core.