jonnifyechomq

The native module · dive 02

The native fence

A read on every write: one GET decides, and nothing is written on any path.

Every native write verb reads {emq}:version as the caller and requires the module's wire identity, echomq:6.0.0. The fence only reads. Two refusals name what it found — an absent value, or a foreign one — and a key of the wrong type passes the server's own WRONGTYPE through. The value is installed administratively, once, before ordinary clients connect; ordinary clients only read it. EMQ.VERSION reports the facts a client compares without reading any key. Each executor refuses the other's value, so the two keep separate nodes; a connector opened with wire 6 is the module's client, which checks EMQ.VERSION and only reads the value. Set a stored value and a command on the right, then read the fence line by line.

The fence · pick what {emq}:version holds and a command

GET {emq}:version echomq:6.0.0 verdict pass written nothing the fence passes → EMQ.CLAIM continues to its own checks
EMQ.CLAIM with {emq}:version = echomq:6.0.0 — the fence passes
read: GET {emq}:version → echomq:6.0.0 · verdict: the module's wire identity, byte for byte · then: the claim runs its own checks, pause, peek, types, before its writes
written by the fence: nothing

The fence, verbatim

Three constants, one function. The key is {emq}:version, inside the braced reserve {emq}:; the value the fence accepts is the module's wire identity. Every write verb calls fence after its shape and layout checks and before any domain read.

// trafikey — the three constants the fence is built from
/// `{emq}:version`, the cross-queue fence key (`Keyspace.version_key/0`).
pub const VERSION_KEY: &[u8] = b"{emq}:version";
/// The braced reserve for cross-queue keys (`Keyspace.reserve/1`).
pub const RESERVE: &[u8] = b"{emq}:";
// the wire identity held in VERSION_KEY: the one value the fence accepts
pub const EPOCH: &[u8] = b"echomq:6.0.0";
// trafikey — the fence every write verb calls after its shape and layout checks
/// The epoch fence (G0.3 b, C1): `{emq}:version` must hold `echomq:6.0.0`. Read-only; a wrong
/// type passes the server's `WRONGTYPE` through. `{emq}:version` is outside every key-spec
/// (Δ13), so it is opened for read as the caller first (D5.5), and the [`Caller`] is returned for
/// the handler's other derived keys.
pub fn fence(ctx: &Context) -> Result<Caller, ValkeyError> {
    // a cluster-enabled server is refused here, before any key is read
    standalone()?;
    // the calling ACL user, resolved once per command
    let caller = Caller::of(ctx)?;
    // the fence key is outside the key-spec, so the caller's read right on it is checked here
    caller.open(keyspace::VERSION_KEY, Access::Read)?;
    // one GET decides; nothing is written on any path (a WRONGTYPE has already passed through the ?)
    match read(ctx, cmd::GET, &[keyspace::VERSION_KEY])? {
        // the module's wire identity: pass, and hand the checked caller back
        Reply::Bulk(b) if b == keyspace::EPOCH => Ok(caller),
        // any other string: EMQWIRE expected echomq:6.0.0 found <value>
        Reply::Bulk(b) => Err(errors::wire_found(&b)),
        // no value at all: EMQWIRE expected echomq:6.0.0 but {emq}:version is absent
        Reply::Null => Err(errors::emq(errors::WIRE_ABSENT)),
        // any other reply shape: a typed host error
        _ => Err(errors::emq(errors::HOST_REPLY)),
    }
}
// trafikey — the EMQWIRE texts, verbatim
pub const WIRE_ABSENT: &str = "EMQWIRE expected echomq:6.0.0 but {emq}:version is absent";
pub const WIRE_CLUSTER: &str = "EMQWIRE expected a standalone server but cluster mode is enabled";
// …
/// G0: `EMQWIRE expected echomq:6.0.0 found <brand>`; the brand comes from the dataset.
pub fn wire_found(brand: &[u8]) -> ValkeyError {
    // the stored value is printed through printable, so control bytes still read as ASCII
    ValkeyError::String(format!(
        "EMQWIRE expected echomq:6.0.0 found {}",
        printable(brand)
    ))
}

Read it top to bottom. The read is ACL-checked as the caller: {emq}:version is outside every key-spec, so a restricted user needs read permission on it to run any write verb at all, and a denial is NOPERM before the GET. Then one GET decides. A byte-equal echomq:6.0.0 passes and the checked caller goes back to the handler for its derived keys. Any other string is EMQWIRE expected echomq:6.0.0 found <value>, the value printed as it is stored. No value is EMQWIRE expected echomq:6.0.0 but {emq}:version is absent. A key of another type never reaches the match: the server's WRONGTYPE passes through the ? on the read. The first line is the server mode: the module serves a standalone server, and on a cluster-enabled one every write verb refuses at the fence with the third text, before any key is read.

// trafikey — the server mode, cached once at load and consulted by every fence
static CLUSTER: OnceLock<bool> = OnceLock::new();

/// Module init: cache the server mode (D6.4). A second call keeps the first value.
pub(crate) fn load_mode(ctx: &Context) {
    let _ = CLUSTER.set(ctx.get_flags().contains(ContextFlags::CLUSTER));
}

/// Pure, D6.4: standalone admits; a cluster refuses with its `EMQWIRE` text; an unknown mode (init
/// never cached it) refuses as a host failure rather than guess.
fn mode_refusal(cluster: Option<bool>) -> Result<(), ValkeyError> {
    match cluster {
        Some(false) => Ok(()),
        Some(true) => Err(errors::emq(errors::WIRE_CLUSTER)),
        None => Err(errors::emq(errors::HOST_CALL)),
    }
}

/// D6.4: refuse on a cluster-enabled server, before any key is read or written.
pub fn standalone() -> Result<(), ValkeyError> {
    mode_refusal(CLUSTER.get().copied())
}

/// D6.4: EMQ is served only on a server known to be standalone; `EMQ.VERSION` advertises
/// capabilities only then, so R14 clients refuse on a cluster.
pub fn serving() -> bool {
    CLUSTER.get() == Some(&false)
}

The order every write verb keeps is shape → layout → fence → domain checks → the write guard → the writes. The fence sits third, so a refusal there follows two checks that read nothing and precedes every check that does; wherever a check refuses a call, the dataset after it is the dataset before it. Past the write guard nothing is undone: a host call that fails after an earlier write leaves that write in place, which is why every check comes first.

The fence is one GET as the caller: the module's own value passes, anything else is named in the refusal, and nothing is written on any path.

Who writes the value

Nobody on the module's ordinary path. No native write verb writes {emq}:version; the module's one writer is EMQ.INSTALL 1 {emq}:version — fixed arity 3, the numkeys form with no ARGV, flags write deny-oom admin deny-script, ACL write admin dangerous slow. The caller cannot name a value: the handler writes the module's own constant or nothing. It is run administratively, once, before ordinary clients connect.

// trafikey — EMQ.INSTALL 1 {emq}:version: the one administrative writer of the fence value
/// H1, H2: numkeys is exactly `1` and `KEYS[1]` is exactly `{emq}:version`.
fn check_shape<S: Bytes>(args: &[S]) -> Result<(), ValkeyError> {
    let s = host::shape(VERB, args, 1)?;
    if s.key(1) == keyspace::VERSION_KEY {
        Ok(())
    } else {
        Err(s.layout_error())
    }
}

/// H3, H4 from the one `GET {emq}:version`: a non-string passes the server's `WRONGTYPE` through
/// (incompatible installed state), another string is a foreign brand. A plain `SET` follows an
/// `Install`, so this check alone protects existing state (the C1/C4 controls rely on it).
fn decide(stored: Result<Reply, ValkeyError>) -> Result<Step, ValkeyError> {
    match stored? {
        // absent: install it
        Reply::Null => Ok(Step::Install),
        // already the module's value: a no-op
        Reply::Bulk(brand) if brand == keyspace::EPOCH => Ok(Step::Equal),
        // a foreign value: refuse and leave it unchanged
        Reply::Bulk(brand) => Err(errors::wire_found(&brand)),
        _ => Err(errors::emq(errors::HOST_REPLY)),
    }
}

/// `EMQ.INSTALL 1 {emq}:version` (install.md#grammar).
pub(crate) fn install(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
    // shape: numkeys exactly 1 and the key exactly {emq}:version
    check_shape(&args)?;
    // a cluster-enabled server refuses after the shape check, before the read
    host::standalone()?;
    match decide(host::read(ctx, cmd::GET, &[keyspace::VERSION_KEY]))? {
        Step::Equal => Ok(ValkeyValue::Integer(0)),
        Step::Install => {
            // the write guard, then one plain SET, replicated as its effect
            let w = host::begin_writes(ctx)?;
            w.call(cmd::SET, &[keyspace::VERSION_KEY, keyspace::EPOCH])?;
            Ok(ValkeyValue::Integer(1))
        }
    }
}
# EMQ.INSTALL 1 {emq}:version — the four outcomes of its one GET
stored: absent          → SET {emq}:version echomq:6.0.0 → 1
stored: echomq:6.0.0    → 0, nothing written
stored: another string  → EMQWIRE expected echomq:6.0.0 found <value>, nothing written
stored: not a string    → WRONGTYPE …, nothing written

Idempotent and conservative: a second install answers 0 and touches nothing; a foreign value is refused with the same EMQWIRE text the fence uses, and stays as it was. The write itself is one plain SET through the write guard, so what reaches the log and the replicas is SET {emq}:version echomq:6.0.0, never the EMQ.INSTALL line. The reads that follow, from every write verb on every connection, then pass.

One administrative write, then only reads: the value is installed before the first ordinary client and never touched by one.

EMQ.VERSION and the client rule

EMQ.VERSION (arity 1, readonly fast) reads no key and runs no fence; the client reads {emq}:version itself. It answers a six-entry map: wire, module, module_version, module_major (an integer), build and capabilities — the tokens of the registered write verbs, in table order (EMQ.CLAIM advertises two; EMQ.VERSION and EMQ.INSTALL none), seventeen on a standalone server, the fourteen of the job core and then one for each bus verb: enqueue claim claim.pause extend extends complete.flat schedule promote reschedule delay fail.flat reprocess requeue stalled.check append failures.incr publish. Under RESP2 the map arrives as a flat array of twelve.

// trafikey — EMQ.VERSION: six entries, no key read, no fence
const MODULE_VERSION: &str = env!("CARGO_PKG_VERSION");
const MODULE_MAJOR: i64 = decimal(env!("CARGO_PKG_VERSION_MAJOR"));
/// Reported, never compared (R14). The builder may export `TRAFIKEY_BUILD_ID`.
const BUILD: &str = match option_env!("TRAFIKEY_BUILD_ID") {
    Some(id) => id,
    None => "unset",
};
// …
// read-only, arity 1: any argument gets Valkey's arity error. A map with TRA.CAPABILITIES'
// encoding — a RESP3 map, a flat array of 12 under RESP2. It reads no key and runs no fence.
pub(crate) fn version(ctx: &Context, _: Vec<ValkeyString>) -> ValkeyResult {
    // G1 order is the EMQ rows' table order; a token is advertised only while its command is
    // registered (R14), so a capability always names a served command.
    // on a cluster-enabled server every EMQ command stays registered but refuses, so none is advertised
    let caps = if host::serving() {
        registry::capabilities(Family::Emq, |name| registry::registered(ctx, name))
            .collect::<Vec<_>>()
            .join(" ")
    } else {
        String::new()
    };
    let text = |v: &[u8]| raw::reply_with_string_buffer(ctx.ctx, v.as_ptr().cast(), v.len());
    raw::reply_with_map(ctx.ctx, 6);
    // the wire identity the fence requires
    text(b"wire");
    text(keyspace::EPOCH);
    text(b"module");
    text(b"trafikey");
    text(b"module_version");
    text(MODULE_VERSION.as_bytes());
    text(b"module_major");
    raw::reply_with_long_long(ctx.ctx, MODULE_MAJOR);
    // reported, never compared
    text(b"build");
    text(BUILD.as_bytes());
    // the registered write verbs' tokens, in table order (claim advertises two)
    text(b"capabilities");
    text(caps.as_bytes());
    Ok(ValkeyValue::NoReply)
}

The client rule. A client accepts a node when wire is equal, module_major is equal, and every capability it needs is listed; build is informative only. module_version is the crate's own version string and module_major its leading number, so a major bump refuses old clients by itself. TRA.CAPABILITIES (arity 1, readonly fast) is the module's own metadata in the same encoding — eight pairs: module, version, stage, api_version, bitmap_commands, shared_api, bundled_lua, lua_scripts — and carries no EMQ field. EchoMQ.Connector applies the rule when it opens with wire: 6, needing the fourteen job-core tokens and ignoring the bus tokens; its handshake is read below. Apply the rule to four fixed replies, for a client that needs enqueue, claim and complete.flat.

The client rule · pick an EMQ.VERSION reply

wire equal echomq:6.0.0 = echomq:6.0.0 module_major equal 0 = 0 capabilities listed 3 of 3 needed every check passed → the client accepts the node
a matching node — accepted
reply: wire echomq:6.0.0 · module trafikey · module_major 0 · build unset · capabilities enqueue claim claim.pause extend extends complete.flat schedule promote reschedule delay fail.flat reprocess requeue stalled.check append failures.incr publish
rule: wire equal · module_major equal · enqueue, claim, complete.flat listed · build not compared

Three comparisons and no key read: wire equal, major equal, the needed tokens listed. Build is reported and never compared.

One node, one fence value

EchoMQ.Connector fences too, at connect, and its handshake is read-only. It calls EMQ.VERSION (an unknown command reply means the module is not loaded: {:emq_absent, msg}), runs the capability check, then reads {emq}:version and refuses {:version_fence, :absent | got} unless it is echomq:6.0.0. It never sends SET … NX or EMQ.INSTALL, so connecting never changes the server, and every refusal is fatal:

# echo_wire — EchoMQ.Connector: the connect refusals that stop it
@fatal_connect [:version_fence, :auth_refused, :boot_refused, :emq_absent, :emq_version, :r14]
# echo_wire — EchoMQ.Connector, the version read at connect: only the module's
# brand runs; absent or any other value is {:version_fence, …}, fatal.
defp brand(sock, buf) do
  case sync(sock, ["GET", Keyspace.version_key()], buf) do
    {:ok, @brand, _} -> :ok
    {:ok, nil, _} -> {:error, {:version_fence, :absent}}
    {:ok, {:error_reply, msg}, _} -> {:error, {:version_fence, {:server, msg}}}
    {:ok, got, _} -> {:error, {:version_fence, got}}
    {:error, _} = err -> err
  end
end

Before the module, the connector's old wire 4 ran the Lua scripts and fenced against its own echomq:4.0.0, claiming an absent value with SET … NX. That wire is retired. A node that still holds echomq:4.0.0 is refused by every native write verb with EMQWIRE expected echomq:6.0.0 found echomq:4.0.0, and by the connector at connect. One node holds one fence value: the module's node is installed administratively and then only read.

The connector is the module's client. Past the handshake it sends every queue operation as the EMQ.* verb its script declares (Script.native/2), through eval/5, and sends no Lua.

# echo_wire — EchoMQ.Connector, opened with wire: 6: the module's client
# EMQ.VERSION, then the client rule (R14), then a read of the brand. It
# sends no SET and no EMQ.INSTALL, and every refusal is fatal.
defp fence(sock, buf, 6) do
  with {:ok, g1, buf2} <- emq_version(sock, buf),
       :ok <- r14(g1) do
    brand(sock, buf2)
  end
end
# …
# the client rule: wire equal, module_major 0, and the fourteen job-core
# tokens of @wire6_job_caps listed; every failed condition is reported,
# and build and module_version are never compared.
defp r14(g1) do
  listed = String.split(g1["capabilities"])
  missing = Enum.reject(@wire6_job_caps, &(&1 in listed))

  failures =
    for {key, got, held?} <- [
          {:wire, g1["wire"], g1["wire"] == @wire6_version},
          {:module_major, g1["module_major"], g1["module_major"] == @wire6_module_major},
          {:missing, missing, missing == []}
        ],
        not held?,
        do: {key, got}

  if failures == [], do: :ok, else: {:error, {:r14, failures}}
end

# the brand read: only the module's value passes; absent is refused, never claimed
defp brand(sock, buf) do
  case sync(sock, ["GET", Keyspace.version_key()], buf) do
    {:ok, @wire6_version, _} -> :ok
    {:ok, nil, _} -> {:error, {:version_fence, :absent}}
    {:ok, {:error_reply, msg}, _} -> {:error, {:version_fence, {:server, msg}}}
    {:ok, got, _} -> {:error, {:version_fence, got}}
    {:error, _} = err -> err
  end
end
the pattern (Redis Patterns Applied)

A version key as a guard: read it before you write, and refuse a stranger. Coordination teaches the guard.

the implementation (trafikey)

fence reads {emq}:version as the caller on every write verb and refuses an absent or foreign value with EMQWIRE; EMQ.INSTALL is the module's only writer, administrative and idempotent.

Two wire identities, one key per node: the fence is why the executors never share a node, not a choice made after the fact.

Recap — the native fence

The native fence is a read on every write: {emq}:version must hold the module's wire identity, and the module never writes it on the ordinary path. EMQ.INSTALL installs it once under administrative authority. EMQ.VERSION reports the facts a client compares — wire equal, major equal, capabilities listed — and a wire 6 connector compares them before it reads the value. Each executor refuses the other's value, so each keeps its own node, and the connector's wire option picks the node it can serve. The next dive runs one transition on both.

Next in the native module: One transition, two executors — the claim as the retired @claim2 script and as EMQ.CLAIM, the six differences, the restart exemption, and how parity was kept.

References

Where the fence, its texts and the client rule are grounded, and the engine behaviour they rely on.

Sources

Related in this course