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.
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
Enqueuerepeats an id, so it returnsDuplicateand pending stays at 3. - mint order
ClaimreturnsJOB0NbAb1xcFCy— the minimum id, the oldest job, with no second index.- token-fenced
Completecarriesj.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
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.
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
- FSharp.Core API reference —
Map,Set, andMailboxProcessor. - Microsoft — F# documentation — records, unions, and the
MailboxProcessoragent. - Microsoft — .NET documentation — the runtime the assembled queue runs on.
- The F# Software Foundation — F# on the .NET runtime.
Related in this course
- Elixir — F4.12 · Lab: build a branded CHAMP store — the parity sibling; the same lab in Elixir.
- C4.09 — Branded CHAMP maps & MailboxProcessor — the actor module the lab assembles.
- C4.08 — Branded ids & persistence — the store port the Valkey wire rides.
- C4 — Algorithms & Data Structures — the chapter this lab closes.