S7 Sequencer pipeline and simulated clock¶
Built: - The ring: an LMAX Disruptor ring in front of the engine. Many producers, one matching thread that assigns input sequence numbers, and ordered downstream stages. - The clock: a simulated clock with a speed multiplier and a ticker thread. - The schedule: the engine opens and closes the market from clock ticks.
ADR 0006. Explainer with a simulator: ring-buffer.html.
Concepts¶
- Single writer. Only one thread ever changes the books, so matching needs no locks. Concurrency is handled once, at the entrance (the ring), instead of everywhere.
- Ring buffer vs. a queue. The ring is a fixed array of reusable slots. Producers claim the next sequence with an atomic operation, write and publish; consumers each track their own position with a counter. No allocation and no lock per message. A
LinkedBlockingQueueallocates a node per message and takes a lock. - Sequencer. The ring turns "many threads at once" into one official order. Whatever order arrives is fine; what matters is that there is exactly one, and that it's recorded.
- Input sequence assigned on the matching thread. If producers numbered commands themselves, two threads could take numbers 5 and 6 but publish them in the opposite order. Numbering at the consumer makes number and processing order identical by construction.
- Stages and gating. The journal stage runs after matching, and the publishers run after the journal. "Respond only after the journal is on disk" is just a dependency between stages.
- Back-pressure. When the ring is full,
trySubmitsays no and the gateway answers "busy". Without that, a slow disk would grow memory without limit until the process died. - Wait strategies. Blocking (sleep until woken, low CPU), yielding, or busy-spin (burn a core for the lowest latency). A latency-vs-CPU trade-off chosen by config.
- Fail safe. If a handler throws, its thread stops and trading halts. Skipping a command would silently corrupt the history.
- Simulated clock.
simTime = anchor + wallElapsed × multiplier. Changing speed re-anchors so time never jumps. The wall clock is injected, so tests control time exactly. - Ticks as commands. The clock thread's readings enter the engine as journaled
ClockTicks, so a replay sees identical times even if it runs 1,000× faster. - Fixed offset vs. time zone. IST is +05:30 all year. A fixed offset means sim time → time of day can never change because of a time-zone database update or daylight-saving rule.
- Testing concurrency for determinism. You can't predict the interleaving, so test the contract instead: no gaps, per-producer order kept, and replay equality. Run it repeatedly (5 green runs) and plant a bug to prove it can fail.
- Shell gotcha. zsh does not split
$VARinto words like bash does;./mvnw $ARGSpasses one big argument. Write the arguments out in full or use an array.
In an interview¶
"Concurrency is solved once, at the entrance: a multi-producer Disruptor ring feeds a single matching thread that numbers each command. The acceptance test hammers it from eight threads, then replays the recorded order through a fresh engine and requires identical events."
"Time is an input: a clock thread outside the engine turns wall time into sim time and publishes ticks into the ring, so replays never read a real clock."
Explain-back answers¶
- Why number commands on the matching thread, not when submitted? Producers race: thread A takes number 5 and thread B number 6, but B publishes first. The consumer-assigned number always equals processing order.
- What does back-pressure protect against? A slow downstream stage (disk, network) would otherwise make producers queue without limit until memory runs out. A bounded ring plus
trySubmitturns overload into a clear "busy" answer. - Why halt on a handler exception instead of logging and continuing? Continuing would mean a command was applied without being journaled, or skipped after being numbered. The recorded history would no longer match reality, and replay would diverge.
- Why do ticks go through the ring like orders? Their position relative to orders matters: an order before or after the 15:30 tick is accepted or rejected. Only the ring order, journaled, can reproduce that.
- How do you test something whose interleaving you can't control? Assert properties that must hold for every interleaving (no gaps, per-producer order, replay equality), repeat the test, and plant bugs to confirm it can fail.