Leka
A low-latency C++20 price-time-priority limit order book and matching engine
▶ Replay viewer
Loading...
Searching...
No Matches
lob::SpscEventQueue< Capacity > Class Template Reference

Wait-free single-producer/single-consumer queue of OrderEvent. More...

#include <spsc_event_queue.hpp>

Public Member Functions

 SpscEventQueue (const SpscEventQueue &)=delete
SpscEventQueue & operator= (const SpscEventQueue &)=delete
 SpscEventQueue (SpscEventQueue &&)=delete
SpscEventQueue & operator= (SpscEventQueue &&)=delete
 ~SpscEventQueue ()
 Destroys any events left in the queue at the time of destruction.
bool tryPush (const OrderEvent &event)
 Producer-only.
bool tryPop (OrderEvent &out)
 Consumer-only.
std::size_t sizeApprox () const
 Approximate occupancy, for diagnostics only.

Static Public Member Functions

static constexpr std::size_t capacity ()

Detailed Description

template<std::size_t Capacity>
class lob::SpscEventQueue< Capacity >

Wait-free single-producer/single-consumer queue of OrderEvent.

This is the boundary between a feed-handler thread (decoding or generating events) and the matching thread (calling MatchingEngine::processEvent), mirroring the two-thread split a real venue-facing system uses instead of doing both jobs on one thread. See ARCH_DECISIONS.md ADR-009 for why this shape, and measured cross-thread hand-off latency.

Producer and consumer must each be called from exactly one thread apiece, always the SAME thread for the lifetime of the queue — tryPush() from more than one thread, or tryPop() from more than one thread, is undefined behavior. That restriction is what makes this SPSC rather than MPMC, and is also what makes it possible to implement with no compare-and-swap loop at all: each side has exactly one writer for its own cursor, so a plain atomic store, correctly ordered, is enough. No lock, no CAS retry loop, and no blocking: tryPush()/tryPop() either succeed immediately or report "not right now" and return, which is what "wait-free" means here.

head_ and tail_ are each on their own 64-byte cache line (std::hardware_destructive_interference_size on most platforms actually targeted, but the exact value is not guaranteed portable, so this uses the conventional 64 directly). Without that padding, the producer's tail_ store and the consumer's head_ store would share a cache line, and every single push and pop would force that line to bounce between the two cores' caches (MESI/MOESI invalidation) even though the two threads never touch the same logical field — "false sharing," and a real, measurable throughput cost specifically because there IS no lock here to hide it behind.

Storage is raw, placement-constructed bytes rather than std::array<OrderEvent, Capacity>, the same technique OrderPool (include/lob/book/order_pool.hpp) already uses for Order: OrderEvent has no default constructor by design (see order_event.hpp), and a std::array of it would require one.

Definition at line 52 of file spsc_event_queue.hpp.

Constructor & Destructor Documentation

◆ ~SpscEventQueue()

template<std::size_t Capacity>
lob::SpscEventQueue< Capacity >::~SpscEventQueue ( )
inline

Destroys any events left in the queue at the time of destruction.

Definition at line 64 of file spsc_event_queue.hpp.

Member Function Documentation

◆ tryPush()

template<std::size_t Capacity>
bool lob::SpscEventQueue< Capacity >::tryPush ( const OrderEvent & event)
inline

Producer-only.

Appends event if the queue is not full.

Returns
false if the queue was full; event is left untouched.

Definition at line 76 of file spsc_event_queue.hpp.

◆ tryPop()

template<std::size_t Capacity>
bool lob::SpscEventQueue< Capacity >::tryPop ( OrderEvent & out)
inline

Consumer-only.

Moves the next event into out if present.

Returns
false if the queue was empty; out is left untouched.

Definition at line 102 of file spsc_event_queue.hpp.

◆ sizeApprox()

template<std::size_t Capacity>
std::size_t lob::SpscEventQueue< Capacity >::sizeApprox ( ) const
inline

Approximate occupancy, for diagnostics only.

Reads both cursors without synchronizing them against each other, so the result can be stale or transiently negative-looking (wrapped) the instant either side is mid-operation. Never use this to decide whether tryPush()/tryPop() will succeed; call them and check their return value instead.

Definition at line 128 of file spsc_event_queue.hpp.

◆ capacity()

template<std::size_t Capacity>
constexpr std::size_t lob::SpscEventQueue< Capacity >::capacity ( )
inlinestaticconstexpr

Definition at line 132 of file spsc_event_queue.hpp.


The documentation for this class was generated from the following file: