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
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
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
A version key as a guard: read it before you write, and refuse a stranger. Coordination teaches the guard.
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
- Valkey — GET — the one read the fence is.
- Valkey — SET — the administrative write, and the
NXform the retired wire-4 connector claimed with. - Valkey — ACL — the read permission the fence checks as the caller.
- Valkey — Modules introduction — the module the fence guards, and the server it loads into.
- Valkey — INFO — the server facts a module reads at load.
Related in this course
- The native module — the module this dive belongs to.
- The version fence — the version fence, from the immutability module.
- The reserve —
{emq}:and the version key inside it. - Commands, not scripts — the previous dive: the command form the fence sits inside.
- redis-patterns · Coordination — guards and atomic updates, the near side of the door.