vike
Concepts

Ingest lanes

The producer-side channels every venue adapter holds — lossless where a missed message is a hole, latest-wins where only the freshest value matters — and why they live one crate below the core that drains them.

Everything that reaches the execution core arrives on one ingest channel, and crates/vike-exec/src/lanes.rs owns the producer half of it: the sender handles a venue adapter holds and the payload types they carry. The consumer — the single-writer core thread — lives in vike-core. The header states why the split is where it is: venue bridges depend on vike-exec but never on vike-core, so a bridge can push into the core without linking the runtime that drains it.

One message type, several disciplines

Ingest is the one enum on the channel. Its variants sort into lanes by what losing a message would mean:

  • Event — a canonical venue event, the hot-path variant. It rides unboxed on purpose: the comment beside the enum records that boxing it would trade a 240-byte move for a per-event heap allocation on exactly the path the p99 < 10 µs gate protects. Commands, which are rare, are boxed.
  • Command — everything an outside thread may ask of the core, below.
  • Market — a marker with no payload; the tick itself rides a conflation slot.
  • BarSeed / BarClose — the bar lanes, lossless: a missed close is a hole in the series. Forming-bar updates conflate with the ticks instead.
  • Quote / Trade / Book — the tick lanes, lossless and dispatched per update to a mounted strategy's tick handlers; they touch no parity-gated order-management fold path.
  • Watchdog — the opt-in stuck-order sweep tick of the watchdog.
  • StreamStatus / Flow — occasional control events: a feed-health transition delivered to Strategy::on_feed_status, and a per-side flow-toxicity update delivered to Strategy::on_flow. Both fire on a transition or at their own cadence, never per market message; neither is journaled.

Every lossless sender returns CoreGone when the core thread has exited, so a producer learns the channel is closed rather than blocking on it. The wait-free market lane reports nothing — MarketSender::publish returns no result at all, and a marker that cannot be queued is simply retried on the next publish.

Lossless senders back-pressure the feed

EventSender is the lane every adapter pushes canonical events into — send from an async task, blocking_send from a plain thread. Both back-pressure the producer and never drop. The handle also carries an optional route_key, set by EventSender::routed at the mount: it stamps the key onto the three venue-tagged payloads that carry no client-order-id — AccountState, Funding and PositionLiquidated — so the core's router can fold them into the right engine when a venue runs more than one account. Fill is deliberately left alone: it names its order, which the router resolves exactly, and stamping it would put a second key on the one wire shape the frozen parity fixtures pin. A key equal to the payload's own venue stamps nothing, which keeps every single-account box's journal bytes identical. The stamp takes the event by &mut because blocking_send sits inside the measured core hop — the latency gate feeds Ingest::Event(Event::Fill) through exactly this call.

BarSender (seed, close) and TickSender (quote, trade, book, stream_status, flow) are blocking-lossless for the same reason: each quote, trade or book update is a distinct event a strategy may act on. The book rides behind an Arc, not by value — the BookUpdate doc records the audit finding that carrying it by value deep-cloned two whole BTreeMaps (up to a 1000-level seed) on the pump thread and paid the matching drop on the fold thread; with an Arc the send is a refcount bump, a pump mutates its own standing copy through Arc::make_mut, and the wire bytes are pinned identical to the pre-Arc shape.

The market lane is latest-wins and wait-free

MarketSender is the exception, and the discipline inverts. A price tick for a (venue, symbol) is written into a per-symbol slot — the newest overwrites, and an overwrite of a still-pending tick is counted into conflated_market_drops — then at most one Ingest::Market marker is armed with try_send. If the ingest queue is momentarily full the marker is not lost: marker_in_flight stays false and the next publish retries it, and because ticks refresh continuously the slot can never go stale while the feed is alive. The publish is wait-free and safe from tokio tasks and plain threads alike. Three slot maps live under one lock — real venue marks, candle-close ticks and forming bars — and marks and bar-closes are kept apart so a one-second mark stream can never conflate away a kline feed's close for the same symbol; they fill different slots of the price board downstream.

Commands, and the one write contract

Command is the session-control vocabulary: Order, UpdateParams, SetTradingState, SetMargin, ApplySnapshot, ReconcileReports, ConfirmRecon, MountStrategy, UnmountStrategy, Shutdown. These are control verbs rather than market messages, and UpdateParams, SetMargin, ReconcileReports and the two mount verbs each say so in their own doc — occasional, off the p99 event fold. The two mount verbs are deliberately not journaled, because a mount-less replay core could never re-resolve a strategy. The control boundary is the surface these arrive through.

Command::Order wraps OrderIntent — the one write contract. Every order-scoped instruction, whether from the external command path or from a mounted strategy's broker drain, lowers into the core's single apply_intent site, so minting a client-order-id and crossing the risk gate are structural on every order path. The verbs: Submit, SubmitBatch, Cancel, CancelBatch, Modify, Confirm, MassCancel, Flatten, MarketExit, Bracket, ArmConditional, DisarmConditional and Combo (declared on the wire; its own doc marks the lowering as pending). Arming an emulated conditional bypasses the gate — only its fire crosses it — and so does disarming one.

The panic button works from a halted core, and it is not atomic

MarketExit is a compound verb: the runtime expands it at apply time into one MassCancel first, then one Flatten per non-flat BOTH-side position — a hedge-mode long or short leg would resolve a size of zero, so it is skipped — each lowered through the same apply_intent site. It has no precondition — a position-covered reduce is admitted under halt, which is exactly the shape a flatten mints, unless the position sits off the lot grid, where the rounded-up size no longer covers it and that leg is denied under Halted. Its own doc records that this is the reverse of the rule the verb carried for its whole earlier life, when an operator had to un-halt in order to escape. It is also best-effort: against a real venue the mass-cancel is fire-and-forget over the adapter's actor thread, its acknowledgements may land after the flatten orders are already on the wire, and a resting order can still fill after a flatten leg. Re-issue the exit if the board is not flat afterwards.

Two test-support constructors ship with the lanes: event_channel hands back a raw EventSender plus the receiver with no core behind it, so an adapter test can observe exactly what a client pushes, and market_data_channel is its bar/market twin.

On this page