jonnifyfsharp

Dive C4.12.3 · the lab

Ship it

The model and the actor are built. Now run them as one queue, end to end.

This is the chapter's capstone. Construct a Broker, enqueue a few jobs, claim the oldest first, complete it — the whole lifecycle the in-memory model runs, assembled from the chapter's parts. The claim returns the minimum branded id because Base62 ids sort as their integers do, so the smallest id is the oldest job; the completion carries the claim's attempt count, so a stale holder cannot retire a job a newer claim now owns. By the end you have, in miniature, what echo_fs is today — and the one part still ahead is the Valkey wire. Every line of the run below was executed in dotnet fsi.

C4.12.3 · part 1

The assembled queue

Nothing new is added here — the two prior dives are wired together. The Broker from C4.12.2 owns a QueueState from C4.12.1 and applies its three transitions through one mailbox. The whole queue is then four members: Enqueue, Claim, Complete, PendingSize. What each line below assembles, by lesson: the persistent Map / Set registry (C4.04–C4.06), the branded JOB id that orders and dates itself (C4.07–C4.08), and the MailboxProcessor single writer (C4.09).

// the capstone run — the assembled queue, end to end (verified in dotnet fsi)
let q = Broker()

q.Enqueue("JOB0NbAb1xcFCy", "send-welcome")    // Enqueued
q.Enqueue("JOB0NbCMKoAopE", "rebuild-index")   // Enqueued
q.Enqueue("JOB0NbWMtkolM0", "expire-carts")    // Enqueued
q.Enqueue("JOB0NbAb1xcFCy", "send-welcome")    // Duplicate  (idempotent admission)
q.PendingSize()                                // 3

let j = q.Claim()                              // Some "JOB0NbAb1xcFCy"  (the oldest, by mint order)
q.Complete("JOB0NbAb1xcFCy", j.Value.Attempts) // Ok ()
q.PendingSize()                                // 2
idempotent
the fourth Enqueue repeats an id, so it returns Duplicate and pending stays at 3.
mint order
Claim returns JOB0NbAb1xcFCy — the minimum id, the oldest job, with no second index.
token-fenced
Complete carries j.Value.Attempts (the claim's count); a wrong token would be refused as stale.

Run the assembled queue

Step the queue through its whole lifecycle over a fixed three-job dataset. Each press calls one member of a real Broker-shaped state machine — the same pure transitions, threaded through one owner. The diagram tracks the Pending set, the in-flight job and its token, and the completed count; the readout names each step's verdict, exactly as dotnet fsi prints it.

The lifecycle · enqueue → claim → complete

Pending : Set<string> oldest first empty In flight — token — no job claimed Completed 0 count q.Enqueue → q.Claim → q.Complete(id, token)
a fresh broker. Pending = 0 · in flight = none · completed = 0. Enqueue a job, or run the whole demo.

Admit, claim the oldest, complete — three members over one writer. That is the queue, running. Every verdict matches what echo_fs returns, because it is the same transitions.

C4.12.3 · part 2

What you have built, and what lands next

You have built, in miniature, what echo_fs is today: an in-memory Store and a single-writer Broker, with idempotent admission, mint-ordered claim, and token-fenced completion. The whole lifecycle the production bus runs is here, on a Map and a Set.

One part is genuinely still ahead: the Valkey transport. The echo_fs README names it EchoMQ.Connector and marks it coming soon. Because the store sits behind a port, the same Broker routes its transitions to the wire when the Connector lands — the message type, the loop, and the members do not change. That is the swap the lab is built to make.

Built today (in memory)

The Store and the single-writer Broker: enqueue, claim, complete — the lab, and echo_fs's core, both tested.

Lands next (the wire)

The Valkey transport EchoMQ.Connector — the README's one coming soon — turns the in-memory queue into the production bus behind the same surface.

The lab and echo_fs build a Map / Set store, not a CHAMP and not a live Valkey bus. The branded CHAMP (C4.06) and the Valkey wire are the production forms the same Store surface admits later — taught as the target, not claimed as built.

The same lab, beside Elixir

The Elixir course's F4.12 assembles the same arc: a store behind a single writer, run end to end. There it is a GenServer over a partitioned branded CHAMP; here it is a MailboxProcessor over a Map-backed store. Same operations, same single-writer guarantee; the durable backing is the part each leaves behind a swappable surface.

Elixir — F4.12: the assembled store

# Elixir — a GenServer over the store, the same lifecycle
Portal.Store.put(id, value)   # admit
Portal.Store.get(id)          # resolve by id

F# — C4.12: the assembled Broker (verified in FSI)

// F# — a MailboxProcessor over the store, the same lifecycle
q.Enqueue(id, payload)          // admit
q.Claim() |> Option.map (fun j -> j.Id)   // take the oldest

Elixir / F4.12

A GenServer over a partitioned branded CHAMP — the store assembled and run.

F# / C4.12

A MailboxProcessor over a Map-backed store — the same arc, the same single-writer guarantee.

EchoMQ in F#

Status — echo_fs is an in-memory model. The EchoMQ protocol — the Lua transitions executing on a Valkey wire — is not implemented (there is no Lua and no Valkey client in the code); that is the coming-soon transport (EchoMQ.Connector). This block shows how the model uses the structure, not a running bus.

The assembled queue is not a sketch beside the bus — it is the bus's core, in miniature, and it is tested.

runs today echo_fs/EchoMQ/Store.fs · Broker.fs · README.md

The echo_fs solution ships exactly this assembled core: EchoMQ.Store (the Rows map plus the Pending set) and the single-writer EchoMQ.Broker, both built and under test, with the same enqueue / claim / complete verdicts the run above produces. The lab is this core, trimmed to its spine. The README states the discipline directly: the in-memory Store "stands in for the branded CHAMP / Valkey rows behind the same enqueue/claim/complete/… surface, so the durable backing swaps in with no caller change."

coming soon The EchoMQ.Connector — the Valkey wire — is the one part the README marks coming soon. It is what turns the lab's in-memory queue into the production bus; the Store surface and the Broker do not change when it lands.

The chapter, assembled

That closes C4. The chapter built data structures from lists to the branded CHAMP, and this lab wired the last ones — a persistent store, a branded key, a single-writer actor — into a working queue. The next chapter, C5, leaves the algorithms behind and ships a real F# DevOps tool end to end.

References

Sources

Related in this course