Skip to main content

Module engine

Module engine 

Source
Expand description

Connections, framing and buffers: the seam between the loop and the commands.

yo-reactor knows how to run a batch and nothing about what a command is. dispatch knows how to run a command and nothing about where the bytes came from. This module is the piece in between, and it is the piece a server is missing until it exists: the read buffer a command’s arguments point into, the framing that says where one command ends and the next begins, the reply buffer that holds an answer until the batch is done, and the state a connection keeps between the two.

§Two halves

Wire is a pair rather than a thing. The connection half is the front, and it is in a module of its own that cannot name a Server: the buffers, the decoder pool, the framing, the sessions and the queue of framed work. The other half is the server, which is the databases and the numbers INFO reports. The line matters because it is the line a second thread runs along: a front belongs to the thread that accepted its connections and is reached by nothing else, and the server is what the threads come to share. Everything that needs both is a method on Wire and there are three of them, which are running a command, answering a client that blocked and forgetting a client that has gone.

§What a piece of work is

Cmd is three numbers: which connection, which decoder holds the arguments, and where in that connection’s buffer they point. It is Copy and twenty four bytes, so it crosses an intake lane without touching the heap, and it carries no borrow, which is what lets the reactor hold sixty four of them while the engine owns the bytes they name.

The decoders are pooled. Framing takes one out of the pool per command, run puts it back, and a connection with a half read command keeps hold of one so that a bulk arriving in ten reads is decoded once rather than ten times. In the steady state the pool is as large as the deepest batch and nothing here allocates at all.

§One write per connection

Replies accumulate in the connection’s Out and go out in Wire::flush, which is one call to the sink per connection touched by the batch and never one per reply. That is the syscall shape 04 section 2 asks for, and it is the one aki got wrong: its HGETALL profile spent 69.7 percent of its time in write syscalls.

§What is not here

Sockets. Sink is where the bytes go and the io_uring reactor implements it later, which keeps this module testable without a network and keeps the ring out of the crate that parses the protocol.

The hash the first walk computes warms the bucket and is then thrown away, because yo-kv’s commands take keys rather than hashes. The prefetch is the part that is worth a cache miss; hashing a short key twice is a few nanoseconds, and removing the second one means a hashed form of every command method, which is a change to make with a benchmark rather than on the way past.

use yo_resp::engine::{Recorder, Wire, pump};
use yo_reactor::Reactor;

let mut r = Reactor::inline(Wire::new(Recorder::new()));
let conn = r.engine_mut().accept();

r.engine_mut().feed(conn, b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n*2\r\n$3\r\nGET\r\n$1\r\nk\r\n");
let mut batch = Vec::new();
assert_eq!(pump(&mut r, &mut batch), 2);

assert_eq!(r.engine().sink().sent(conn), b"+OK\r\n$1\r\nv\r\n");

Structs§

Cmd
One framed command, waiting to run.
Recorder
A sink that keeps everything, for tests and for a driver with no socket.
Wire
The engine: connections on one side, the command layer on the other.

Traits§

Sink
Where replies go.

Functions§

pump
Run everything that is framed, in batches, and write the replies.

Type Aliases§

ConnId
Which connection. An index, reused after a connection closes.