Expand description
Direct XREAD / XREAD BLOCK tail for attempt-scoped streams.
XREAD BLOCK cannot run inside a Valkey Function (blocking commands are
rejected by FUNCTION), so tailing is implemented as a direct client
command against the same ferriskey::Client the server already holds.
Semantics:
block_ms == 0→ non-blockingXREAD(returns immediately)block_ms > 0→XREAD BLOCK <ms>(returns nil on timeout → empty result)
Cluster-safe: the stream key carries the execution’s {p:N} hash tag, so
XREAD routes to the single owning slot.
§Timeout interaction with the ferriskey client
The client’s configured request_timeout (5s on the server default) does
NOT cap blocking tail calls. ferriskey::client::get_request_timeout
inspects every outgoing cmd; for XREAD/XREADGROUP with a BLOCK
argument, it returns RequestTimeoutOption::BlockingCommand(block_ms + 500ms) via BLOCKING_CMD_TIMEOUT_EXTENSION. That means a
block_ms = 30_000 call gets a 30_500ms effective timeout regardless of
the client’s default. No manual override is needed from this module.
§Terminal signal (closed-stream propagation)
After every XREAD/XREAD BLOCK we HMGET the sibling stream_meta hash
to learn whether the producer has closed the stream (closed_at /
closed_reason). This is the terminal-signal contract described in
RFC-006 — consumers poll tail_stream until is_closed() is true,
then drain any remaining frames.
§Atomicity (XREAD vs HMGET)
The XREAD and the follow-up HMGET are separate Valkey round trips. A
close can land between them, so a caller can observe a result that
was not simultaneously true at any single instant: frames reflect
the stream as of T1, closed_at/closed_reason reflect
stream_meta as of T2 > T1.
This is correctness-safe. ff_append_frame gates on closed_at
(see lua/stream.lua), so once the stream is closed no new frames
can be appended — any frames returned by XREAD are guaranteed to be
from before the close. The terminal signal arriving “slightly later”
just means a tail loop may take one extra iteration to observe the
closure. Consumers should treat closed_at.is_some() as the exit
condition and perform a final read_attempt_stream drain from
last_seen_id to + to pick up any frames that landed before close
but after the tail’s last read — see RFC-006 Impl Notes
§“Terminal-signal drain pattern”.
Functions§
- xread_
block - Tail frames from
stream_key, blocking up toblock_ms(0 = no block).