Frame has no null-array variant, so every command whose empty answer is an array replies with the wrong RESP2 type.
Frame::Null serialises to $-1 (Null Bulk), full stop. Redis answers *-1 (Null Array) wherever the missing value is an array. A statically-typed client decodes the two differently, so this is a decode error on the client, not a cosmetic difference.
This is not a MULTI bug — it was found inside MULTI but reproduces on a plain connection:
BLPOP missing 1 # Moon: $-1 Redis: *-1
Measured inside a transaction (tests/multi_exec_queue_semantics.rs::me7):
Moon: *1\r\n$-1\r\n
Redis: *1\r\n*-1\r\n
Why it was not fixed in #472
The fix is a Frame::NullArray variant threaded through the ~14 Frame::Null match arms in serialize.rs and resp3.rs. That touches every reply path in the server, so it needs its own contract and its own review rather than riding along inside a MULTI-semantics change. Getting it wrong flips the type of replies that are currently correct.
RESP3 is unaffected in the same way — _\r\n is the null for both shapes there — so the divergence is RESP2-only, which is also why it survived the RESP3 type-fidelity work.
Scope
- add
Frame::NullArray, serialising to *-1 in RESP2 and _\r\n in RESP3
- audit every
Frame::Null site and decide, per site, which null it means — the audit is the task; a blanket replace would be wrong in both directions
- blocking commands' zero-timeout path (
BLPOP, BRPOP, BLMOVE, BZPOPMIN/MAX) are the known callers, but the audit governs
- un-ignore
me7_blpop_in_multi_returns_null_array_not_null_bulk in tests/multi_exec_queue_semantics.rs — the assertion is already correct, only the capability is missing
- add a compat-harness entry so the oracle keeps this honest
Provenance
Unmet Must #7 of the multi-exec-queue-semantics ADD task, whose gate is recorded RISK-ACCEPTED against this issue.
Framehas no null-array variant, so every command whose empty answer is an array replies with the wrong RESP2 type.Frame::Nullserialises to$-1(Null Bulk), full stop. Redis answers*-1(Null Array) wherever the missing value is an array. A statically-typed client decodes the two differently, so this is a decode error on the client, not a cosmetic difference.This is not a MULTI bug — it was found inside MULTI but reproduces on a plain connection:
Measured inside a transaction (
tests/multi_exec_queue_semantics.rs::me7):Why it was not fixed in #472
The fix is a
Frame::NullArrayvariant threaded through the ~14Frame::Nullmatch arms inserialize.rsandresp3.rs. That touches every reply path in the server, so it needs its own contract and its own review rather than riding along inside a MULTI-semantics change. Getting it wrong flips the type of replies that are currently correct.RESP3 is unaffected in the same way —
_\r\nis the null for both shapes there — so the divergence is RESP2-only, which is also why it survived the RESP3 type-fidelity work.Scope
Frame::NullArray, serialising to*-1in RESP2 and_\r\nin RESP3Frame::Nullsite and decide, per site, which null it means — the audit is the task; a blanket replace would be wrong in both directionsBLPOP,BRPOP,BLMOVE,BZPOPMIN/MAX) are the known callers, but the audit governsme7_blpop_in_multi_returns_null_array_not_null_bulkintests/multi_exec_queue_semantics.rs— the assertion is already correct, only the capability is missingProvenance
Unmet Must #7 of the
multi-exec-queue-semanticsADD task, whose gate is recorded RISK-ACCEPTED against this issue.