Redis over the ring

ioxide.redis is a Redis client where connect, every command, and pipelines are io_uring ops on the host reactor's ring, resumed inline. Full RESP2, a generic command API that runs anything, typed helpers across every data type, and pipelining - pooled per reactor, shared-nothing.

The pool

Open connections per reactor from OnStart and register the pool as a service; handlers fetch it and rent per operation. One connection carries one in-flight command (or pipeline) at a time, so the pool size is the reactor's Redis concurrency.

reactor.OnStart = r => RedisPool.Start(r, new RedisOptions
{
    Host = "127.0.0.1", Port = 6379, PoolSize = 8,
    // Password, User (ACL), Database (SELECT on connect) are all optional
});

Two API levels

Generic - ExecuteAsync runs any command and returns a RespValue, the full RESP2 reply taxonomy. Use it for anything the typed helpers do not cover:

var redis = reactor.GetService<RedisPool>();

RespValue pong = await redis.ExecuteAsync("PING");
RespValue n    = await redis.ExecuteAsync("INCRBY", "counter", 5);
RespValue info = await redis.ExecuteAsync("COMMAND", "COUNT");

Typed - convenience methods over the generic call, covering strings, keys, hashes, lists, sets, sorted sets, pub/sub, and scripting. Rent a connection for a sequence, or use the pool's one-shot shortcuts:

var c = await redis.RentAsync();
try
{
    await c.SetExAsync("session:42", token, seconds: 300);
    string? v   = await c.GetAsync("session:42");
    long count  = await c.IncrAsync("hits");
    var fields  = await c.HGetAllAsync("user:42");
    var range   = await c.LRangeAsync("queue", 0, -1);
    double? sc  = await c.ZScoreAsync("board", "alice");
}
finally { redis.Return(c); }

The reply type

RespValue represents any RESP2 reply - null, simple string, error, integer, bulk string, or a (possibly nested) array - and converts on demand. Errors are surfaced two ways: ExecuteAsync throws a RedisException on a top-level error reply, while array elements that are errors stay inspectable (for MULTI/EXEC).

RespValue r = await redis.ExecuteAsync("HGETALL", "user:42");
foreach (RespValue field in r.Items)        // arrays
    Console.WriteLine(field.AsString());

long size = (await redis.ExecuteAsync("DBSIZE")).AsInteger();   // integers
bool gone = (await redis.ExecuteAsync("GET", "missing")).IsNull;

Pipelining

Send several commands back to back and read all replies in one round trip. Errors are returned per-command rather than thrown, so you can inspect each outcome.

RespValue[] replies = await c.PipelineAsync(
    new RedisCommand("SET", "k", "10"),
    new RedisCommand("INCR", "k"),
    new RedisCommand("GET", "k"));

long current = replies[2].AsInteger();   // 11

Cache-aside

The everyday pattern - check the cache, fall back to the source, populate with a TTL, and invalidate on write. The pool exposes GetAsync / SetExAsync / DelAsync directly so a read is one rented round trip:

string key = $"item:{id}";
string? cached = await redis.GetAsync(key);
if (cached is null)
{
    cached = await LoadFromDatabase(id);          // ioxide.pg, also on the ring
    await redis.SetExAsync(key, cached, seconds: 1);
}
// ... on update:
await redis.DelAsync($"item:{id}");

Shared-nothing, but consistent. Each reactor owns its own Redis connections, so there is no shared managed state and no lock on the hot path. Because Redis itself is shared across reactors, a write that invalidates a key is seen by every reactor's next read - cache-aside stays correct even though connections land on different reactors. This is the reason a shared cache (Redis) fits the model better than a per-reactor in-process cache, which would serve stale reads across reactors.

Connection

RedisOptions: Host (IPv4 literal - resolve names up front, DNS would block the reactor), Port, Password with optional User for ACL AUTH, Database to SELECT on connect, and PoolSize per reactor. Total server-side connections are PoolSize × ReactorCount. Broken connections are discarded on return and replaced in the background, exactly like the Postgres pool.