The execution core
One dedicated thread owns all mutable trading state and is its only mutator; everything reaches it through one totally-ordered queue, so determinism is by construction rather than by locking.
vike-core is the live core. One dedicated OS thread — vt-core — owns the ExecutionEngine
(account, order registry, risk gate, venue client) and is the only mutator of any of it.
Everything else in the process reaches that state through one ingest queue, totally ordered, and
reads it back through an immutable snapshot. The authority for this page is
crates/vike-core/src/runtime/mod.rs, whose own header calls this the pinned architecture.
Three lanes into one queue
Three kinds of message arrive, and each has a different contract with its producer:
- Exec events — fills, order transitions, account and funding updates, liquidations — are
lossless. A venue adapter pushes them with
blocking_send, so a slow core back-pressures the venue task and never the GUI. - Commands from the GUI or a control surface use
try_sendinto the same queue. The GUI thread never blocks. A full-queue rejection comes back to the caller asCommandRejected::Busyand is counted in the snapshot asrejected_commands, so it cannot be silent;CommandRejected::Gonemeans the core thread has exited. - Market data goes through a latest-wins conflation slot plus a marker message. The producer overwrites the slot (a drop counter is surfaced) and at most one marker is ever in flight, so a slow core sees the freshest price without unbounded queueing.
On the core side the thread sits in blocking_recv() — there is no tokio runtime on the hot
thread — then drains opportunistically with a try_recv batch bounded by CoreConfig::batch_max
before publishing, which keeps latency bounded. The producer-side types for all three lanes live in
crates/vike-exec/src/lanes.rs, not in vike-core, so that a venue bridge depends on vike-exec
and never on the core above it; the ingest lanes page is theirs.
The snapshot is a lossy observer
The core publishes a CoreSnapshot into an arc_swap cell that the GUI reads on repaint. Publish
is dirty-flagged and coalesced: at least every CoreConfig::snapshot_interval while busy (about
16 ms by default — the one number on this page that is a live constant, held to the code by
crates/vike-ops/tests/docs_constants_gate.rs), and immediately when the queue goes idle. The
build never runs per-event while busy.
On a quiet venue, publish is per-event work
The idle-path publish is ungated by the interval. On a venue whose events arrive sporadically the
core is idle between them, so it publishes after every one. Anything CoreSnapshot::build does is
therefore per-event work, not per-interval work — which is why deferring a render "to publish
time" once turned out to be a pessimization rather than a saving: it relocated the work instead of
avoiding it (p99 2039 ns deferred, against 606 ns rendered at push and a 481 ns baseline).
The fold is latency-gated
The drain loop is the thread the core-hop tail gate measures —
crates/vike-core/tests/runtime_latency.rs, run in CI on a runner whose cores are reserved for
it. Every ceiling in that file is a derived constant with its data on it, and only three of its
harnesses gate (baseline, journal, journal-snap); the rest measure, asserting no timing
beyond the snapshot build's pre-existing 1 ms sanity check, because calibrating one ceiling there
cost 98 CI attempts and an uncalibrated ceiling is a flaky gate, which teaches people to re-run
rather than read. The
latency gate page is where a number is read.
Two consequences follow for anyone touching the core. Nothing on the fold path may log
per-message — instrument per-order boundaries and fault transitions only. And everything that can
run off the fold belongs in vike-ops, which exists to consume the published snapshot, event and
journal streams without ever sitting in this loop.
What happens when a handler panics
Every dispatch runs under catch_unwind. On a panic the core does not die and does not
keep trading. It enters safe-state: trading_state becomes Halted, so the risk gate denies
every new order; a best-effort cancel is issued for every non-terminal working order; and the
fault is surfaced in the snapshot. A possibly-inconsistent engine is never traded on again — only
read for display — until the process restarts, which is what makes wrapping it in
AssertUnwindSafe sound.
The drain-loop boundary
Cold-path work does not ride the per-message dispatch. It is armed on a DeadlineTimerWheel and
runs at the boundary of a drain, and runtime/mod.rs's TimerKind is the list of what may fire
there: the stuck-order watchdog sweep, the equity sampler, the periodic strategy-state save, the
two dead-man's-switch sweeps — one per switch, each on its own window — managed good-till-date
expiry, the periodic portfolio journal record, and the fast in-flight confirm — the reject-free
early rung of the stuck-order ladder, which only re-queries and never terminalizes. Every one is
opt-in through CoreConfig, and when none is configured the wheel stays empty and the boundary
advance is skipped. The timers, watchdog and
dead-man's switches pages carry each.
Two more pieces sit around the loop. JournalConfig is the opt-in write-ahead command journal:
None — the default — opens nothing and leaves the fold path byte-identical, while Some
journals every exec-lane message before it folds, with the segment, flush and snapshot cadence the
[sinks.journal] profile section uses. And the live strategy mount is the path through which a
Strategy drives a LiveBroker — the same mount whether the venue behind it is paper or live.
Each has its own pages; this one is only the thread they all hand their work to, and the
portfolio and snapshot page is what comes out the other
side.
Architecture
A Cargo workspace whose dependency direction is down-only and machine-checked, and one startup sequence that every binary runs through the same owner.
The ExecutionClient seam
The one trait every venue bridge implements — only submit and cancel are required, every state change comes back as an event, and one method exists precisely so a venue never overrides it.