jonnify·bcs /bcs/bus/state-machine/claim-the-token-mint

B3.3 · dive 1 — claim, the token mint

The oldest job, a server-clock lease, and token 1.

L1 fixes the machine's surface: claim, complete, retry, promote, reap join enqueue, browse, pending_size — five new verbs, every transition one script. L2 closes the happy path: claim hands out the oldest job with a server-clock lease and fencing token 1; complete with the right token retires the row — nothing remains. The script that opens every life is ten lines of Lua, and three design decisions live inside it.

Source: content/bcs3.3.md, quoting bcs_rung_3_3_check.out; the bundle is committed at runtimes/elixir/lib/echo_mq/jobs.ex.

The four sets, completing 3.1's map. Four sorted sets per queue, each earning its score semantics. Select a set to read what its score means and what the meaning buys.
pending score zero lex = mint order active score = lease deadline roster + expiry index schedule score = run-at the separate set dead score zero the morgue, mint order
readout · Select a set. Four sorted sets per queue under 3.1's emq:{q}:<type> grammar — two at score zero where lex order is mint order, two where the score carries a clock.
§1

The transcript

bcs_rung_3_3_check.out · verbatim · this dive reads L1 and L2
content/echo_data/runtimes/elixir/bcs_rung_3_3_check.out · verbatimfrozen
L1 surface ok -- the machine's surface: claim, complete, retry, promote, reap join enqueue, browse, pending_size -- five new verbs, every transition one script
L2 happy ok -- claim hands out the oldest job with a server-clock lease and fencing token 1; complete with the right token retires the row -- nothing remains
L3 fence ok -- a stale token is refused on the wire: EMQSTALE; the lease holder's work survives the zombie's complete
L4 schedule ok -- retry parks the job in the schedule, promote moves the due back to pending, and the next claim hands token 2 -- one job, two lives, one counter
L5 dead ok -- attempts 2 against max 2 is the morgue: state dead, last_error kept, and the dead set browses in mint order like everything else
L6 reap ok -- a 40 ms lease expires unanswered; reap returns the orphan to pending and the next claim holds token 2 -- crash recovery is one zset scan on the server's clock
PASS 6/6
§2

The keys, completing 3.1's map

content/bcs3.3.md · What

Four sorted sets per queue, each earning its score semantics. pending stays score-zero forever — lex order is mint order, the decision B3.2 · Jobs Are Entities recorded, honored here. active is scored by lease deadline, so the active set is simultaneously the in-flight roster and the expiry index, and crash recovery is one range scan. schedule is scored by run-at — exactly the separate set 3.2 pre-stated, so scores never mix into the lex law. And dead is score-zero again, which means the morgue browses newest-first like everything else in this series — the order theorem's third appearance on the bus.

The arithmetic is worth pausing on: two of the four sets carry no clock at all, because identity already carries one. Mint time is history inside the id, and where the machine needs a different clock — a lease deadline, a run-at — the score is where that clock lives. The two clocks never share a column.

§3

Claim, the token mint

content/bcs3.3.md · What · How

The claim script pops the lex-oldest pending id, increments attempts, stamps the lease, and returns the job — quoted whole, because it is the contract:

local popped = redis.call('ZPOPMIN', KEYS[1])
if #popped == 0 then return {} end
local id = popped[1]
local jk = ARGV[1] .. id
local att = redis.call('HINCRBY', jk, 'attempts', 1)
redis.call('HSET', jk, 'state', 'active')
local t = redis.call('TIME')
local now = t[1] * 1000 + math.floor(t[2] / 1000)
redis.call('ZADD', KEYS[2], now + tonumber(ARGV[2]), id)
return {id, redis.call('HGET', jk, 'payload'), att}

Three design facts live here. The clock is the server's: TIME inside the script means leases never see client skew, the engine's frozen-time rule keeps the reading stable for the script's duration, and atomic execution guarantees the pop, the increment, and the lease land together or not at all. The token is minted by HINCRBY — monotonic per job by construction, which is precisely the property the fencing argument requires. And the job key is constructed from a prefix argument — the bundle's one sanctioned exception to declared keys: the id is unknown until popped, and 3.1's co-location law is what makes the construction safe — every key the prefix can produce lives in the same slot as the declared ones, by grammar.

The claim, stepped. The script's six effects in execution order, over a pending set holding the oldest job at attempts 0. Step through; the readout names the line and its effect.
readout · Press step to walk the committed script line by line — from ZPOPMIN to the returned triple — and watch the oldest job leave pending with state active, attempts 1, and a lease scored on the server's clock.

Static walk — 1) ZPOPMIN KEYS[1] pops the lex-oldest pending id · 2) HINCRBY jk attempts 1 mints the token: attempts 0 becomes 1 · 3) HSET jk state active · 4) TIME reads the server's clock, frozen for the script's duration · 5) ZADD KEYS[2] now+lease id scores the active set by lease deadline · 6) return {id, payload, att} — the consumer leaves holding token 1.

The bundle lives inside runtimes/elixir/lib/echo_mq/jobs.ex — five Script.new constants, SHA-pinned, dispatched EVALSHA-first through the appendix's loader, surface gated at exactly the eight verbs. Size the lease above the worst legitimate work time and well below your retry tolerance — the lease is a crash detector, not a deadline for excellence.

§4

References

sources · related

Sources

Related