jonnifyechomq

Trafikist · module 02

The command grammar

One order for every EMQ command, one registry for every EMQ-class and shape refusal.

Every job-core and bus write verb in trafikey runs the same order: shape, layout, the fence, the domain checks, one write guard, then the writes. The order carries one promise: a call any check refuses writes nothing. Past the write guard nothing is undone, so every check that can refuse sits in front of it. Every EMQ-class and shape refusal on the way is a constant or a builder in one closed registry, byte-exact where it shares a text with the Lua scripts; the others are host conditions and the server's own texts. The Protocol's native module introduced the order; this module reads it in the source, check by check.

The order, stated once

The module root states the grammar in four lines of documentation, and every handler file builds on the three files it names: errors (the closed registry), keyspace (the layout checks) and host (the only route to the server). The same lines name what a handler never does: it never calls the SDK's own Context::call, never indexes an argument by position, never unwraps.

// trafikey — the module root states the grammar every job-core and bus write verb runs
//! A handler runs the grammar's order (G0.3): shape (numkeys, counts, key layout, integers),
//! then the fence, then its domain checks, then its writes. It never calls `Context::call`,
//! never uses `ValkeyError::WrongArity`, never indexes `args[n]`, never unwraps, and never holds a
//! `c"…"` literal inside a `#[command]` body (the macro rejects it; use [`host::cmd`]).

Read the order as three zones. Before the first read: shape (numkeys spelled exactly, the keys present, the ARGV count), layout (every declared key rebuilt from one queue name, or a bus verb's one key checked against its form) and argument values (lease_ms, counts, a mode and a due time; a bus verb's entry count, its split into entries, the field names and a TTL). A call refused here has touched no key. Reads: the fence ({emq}:version read as the caller), the domain checks (each a pure verdict, after a host read where it needs stored state), and a type check on every key the call is about to write. Writes: the write guard, once, then the writes, each issued through the guard's proof and replicated as its effect.

Shape, layout, values, fence, domain, types, guard, writes: every job-core and bus write verb runs in that order (EMQ.VERSION and EMQ.INSTALL run no fence), and a refusal anywhere before the guard leaves the dataset as it was.

One handler, the whole order

EMQ.CLAIM shows every zone in one function. Its domain reads — the pause flag, the peek at the head of pending, the row's type and attempts, the type of active — sit between the fence and the guard; they are elided here and read in full in One transition, two executors.

// trafikey — EMQ.CLAIM: the whole order in one handler (the domain reads elided)
pub(crate) fn claim(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
    // shape: numkeys is exactly 3 and the keys are present, then exactly one ARGV
    let s = host::shape("EMQ.CLAIM", &args, 3)?;
    s.expect_argc(1)?;
    // layout: KEYS[3] is a base emq:{q}:, and KEYS[1], KEYS[2] are its pending and active
    let q = claim_queue(&s).ok_or_else(|| s.layout_error())?;
    // a value: lease_ms is parsed before the first read
    let lease = s.lease(1)?;
    // the fence: the first read of the call, {emq}:version as the caller
    let caller = host::fence(ctx)?;
    // … the pause flag, the peek at pending, the row's type and attempts, the type of active
    // the write guard, once, after every read and every type check
    let w = host::begin_writes(ctx)?;
    match w.call(cmd::ZPOPMIN, &[pending])? {
        // the pop must return the member the peek found
        Reply::Array(popped) if popped.first() == Some(&Reply::Bulk(id.clone())) => {}
        _ => return Err(errors::emq(errors::HOST_REPLY)),
    }
    // …
    match w.call(cmd::HINCRBY, &[&row, b"attempts", b"1"])? {
        // the reply must equal the attempt predicted before the guard
        Reply::Int(n) if n == att => {}
        // anything else: ERR EMQ unsupported host reply, and the pop before it stays in place
        _ => return Err(errors::emq(errors::HOST_REPLY)),
    }
    // … the HSET of state and lease_epoch, the ZADD to active, the reply
}

Two lines of the claim run after the guard and still check. ZPOPMIN must pop the member the peek found, and HINCRBY must return the attempt the handler computed before the guard from the stored value and Valkey's own integer rules. Inside one command no other client runs, so on a healthy host both hold. When a host reply differs, there is no rollback: the handler answers ERR EMQ unsupported host reply and the pop stays in place. Single-threaded execution is not rollback; the order is what keeps a refusal from writing.

A refusal writes nothing; past the guard nothing is undone. The guarantee is the order, and it lives in the handler, not in a transaction.

Where a call stops

Run one claim against fixed datasets, each broken at a different place, and read where the handler stops, which host reads it made on the way, and what it wrote. The handler is ported check for check over the fixed data; nothing here talks to a server.

EMQ.CLAIM through the order · pick a dataset

shape layout values fence domain types guard writes reads nothing reads only once effects every check passed → four writes → the leased job
a good call — [JOB0KHTOWnGLuC, order-7, 1]
call: EMQ.CLAIM 3 emq:{orders}:pending emq:{orders}:active emq:{orders}: 30000
stops at: nowhere; the reply follows the writes
host reads: GET {emq}:version · HGET emq:{orders}:meta paused · ZRANGE emq:{orders}:pending 0 0 · TYPE emq:{orders}:job:JOB0KHTOWnGLuC · HGET emq:{orders}:job:JOB0KHTOWnGLuC attempts · TYPE emq:{orders}:active · HGET emq:{orders}:job:JOB0KHTOWnGLuC payload
written: ZPOPMIN emq:{orders}:pending · HINCRBY emq:{orders}:job:JOB0KHTOWnGLuC attempts 1 · HSET emq:{orders}:job:JOB0KHTOWnGLuC state active lease_epoch <run_id> · ZADD emq:{orders}:active <now + 30000> JOB0KHTOWnGLuC

Every refusal left of the guard writes nothing. Its text is a registry entry or the server's own (WRONGTYPE …, or the HINCRBY text the claim predicts); the one failure right of the guard is a host fault, and it keeps what was already written.

One registry for every refusal

Every EMQ-class and shape text an EMQ handler sends is a constant or a builder in errors.rs, so those bytes are reviewable in one file. The first word of a text is its class. Wire 4 holds eleven texts, byte-exact with the redis.error_reply('EMQ…') literals of the Elixir scripts, in five classes: EMQKIND, EMQLOCK, EMQRATE, EMQSTALE and EMQSTATE. A unit test re-reads echo/apps/echo_mq/lib and fails when a text is added, removed or changed there. Wire 6 adds twenty-two fixed texts — the fence's EMQWIRE refusals, the native verbs' EMQSTATE and EMQSTALE refusals for lanes, flows and states, and the bus verbs' EMQKIND brand refusals and their stream-order EMQSTATE text — and builders for the per-verb shape texts and for EMQWIRE … found. Host refusals are three ERR EMQ texts and Valkey's own NOPERM No permissions to access a key; they are not registry entries. The only other texts are the server's own: WRONGTYPE …, the two HINCRBY texts in jobs.rs, and INCR's ERR value is not an integer or out of range in bus.rs, which shares HINCRBY's overflow text.

The module's other family runs a different grammar. A TRA.* command has no numkeys and no fence, lets the host enforce its arity and sends its own three ERR TRA texts, and the family's one write, TRA.SETBIT, replicates its command line as it came; the third dive sets the two side by side.

The class is the first word, the text is fixed, and a test holds the wire-4 texts to the Elixir source byte for byte.

The dives

Three dives read the grammar in the trafikey source: the checks that run before any read, the guard and the effects, and the registry the refusals come from.

built page ready to read

Pattern & implementation

the pattern (Redis Patterns Applied)

An atomic update checks before it writes and runs as one server-side step, so no other client reads it half done. Atomic updates and Lua scripts for atomic logic teach it.

the implementation (trafikey)

Every job-core and bus write verb: shape, layout and values before any read; the fence and the domain reads; every written key typed; one write guard; writes replicated as their effects; every EMQ-class and shape refusal from one closed registry.

Take the dives in order: the checks before any read, then the guard, then the registry. The module after this one is The job core, natively.

References

Sources

Related in this course