Runtime and recovery¶
Single-writer core¶
SequencedShard runs a symbol's engine as a single-writer state machine behind
the FLOX EventBus (Disruptor). Commands are published to the ingress ring;
one consumer journals each command write-ahead and then applies it. All engine
state is owned by that one consumer thread.
SequencedShard<LadderBook> shard(cfg, journalPath, book);
shard.submit(InboundCommand{order}); // published to the ring
shard.journaled(); // records written
Symbols are independent (a CLOB has no cross-symbol matching), so each one can
run on its own shard. SymbolRouter is the dispatch layer: it owns an engine
per symbol, routes a command to the right one, and exposes shardOf() for
partitioning. It dispatches inline on the calling thread; to run shards
concurrently, drive several SequencedShards, each with its own consumer.
SymbolRouter<MatchingBook> router(/*shards*/ 4);
auto& engine = router.addSymbol(cfg, sink);
router.submit(cmd); // routed by symbol
router.snapshotAccount(acct); // cross-shard view for a reconnecting client
Determinism¶
Same commands in, same events out, byte for byte. That is what makes replay and hot standby work, and the tests enforce it:
- Every state-mutating input is a command. Orders, cancels, modifies, mass
cancels, quotes, last-look decisions, and also
SetMark,ApplyFunding, andAdminCmd(auction transitions, halts, emergency cancel-all). A crash after an opening uncross must recover the same book, so the uncross has to be in the stream. - No order-sensitive decision reads an unordered container. Anywhere output depends on processing order (ADL victim choice, liquidation order, peg repricing, expiry, mass cancel) the ids are collected and sorted first, so a replica on a different STL or build makes the same choices.
event_hash.hfolds the outbound stream into a rolling hash, so two runs can be compared in one comparison.
Persistent instrument configuration (tick, lot, bands, risk limits) is
recovered from the configuration store, so it stays out of the WAL and is not
an AdminCmd.
Journal and replay¶
Journal j(path);
j.append(cmd, tsNs); // write-ahead: record, then apply
j.flush();
for (const auto& [ts, cmd] : Journal::loadTimed(path)) engine.submit(cmd, ts);
Records are [ts:8][tag:1][struct]; every command type is trivially copyable
(enforced by static_assert), so a record is a tag plus a raw blob. The
sequencer timestamp is stored, so loadTimed reproduces time-dependent
behaviour (GTD expiry, last-look windows, MMP windows, LULD pauses) at the
same points it happened live.
Replaying from empty must reconstruct an identical ledger, book, positions,
and reservations. test_venue_engine asserts the event-stream hash and the
ledger match after a round trip; test_venue_venue covers the auction case,
replaying a journal whose stream contains an AdminCmd uncross.
Clock¶
Time enters through IClock, so the same code runs on simulated and real time:
| Implementation | Use |
|---|---|
SimulatedClock |
backtests, deterministic replay |
SystemClock (flox/util/system_clock.h) |
live |
Client recovery¶
ResendBufferkeeps per-session outbound events for gap-fill, andGapDetectortells a client when it missed a sequence.snapshotAccountreturns an account's open orders (with the true remaining quantity, hidden reserve included: the owner sees its whole order, unlike the public feed), pending stops, and position, so a reconnecting client can reconcile in one shot.- Cancel-on-disconnect optionally pulls a session's resting orders when the connection drops.