Leka
A low-latency C++20 price-time-priority limit order book and matching engine
▶ Replay viewer
Loading...
Searching...
No Matches
order_pool.cpp
1// src/book/order_pool.cpp
2
3#include "lob/book/order_pool.hpp"
4
5#include <cerrno>
6#include <memory>
7#include <new>
8#include <stdexcept>
9#include <system_error>
10#include <sys/mman.h>
11#include <unistd.h>
12
13namespace lob {
14
15OrderPool::OrderPool() = default;
16
18 void* storage; // Pointer to the allocated memory for the page
19 std::array<std::uint16_t, SlotsPerPage> nextFree{}; // Array to keep track of the next free slot in the page
20 std::array<bool, SlotsPerPage> occupied{}; // Array to keep track of which slots are occupied
21 std::uint16_t firstFree{0}; // Index of the first free slot in the page
22 std::size_t freeCount{SlotsPerPage}; // Count of free slots in the page
23 Page* previousFreePage{nullptr}; // Pointer to the previous page in the list of pages with free slots
24 Page* nextFreePage{nullptr}; // Pointer to the next page in the list of pages with free slots
25 bool onFreePageList{false}; // Flag to indicate whether the page is on the list of pages with free slots
26
27 /** Allocates one aligned page and initializes its external free list. */
28 Page() {
29 const long systemPageSize = sysconf(_SC_PAGESIZE);
30 if (systemPageSize <= 0 || PageSize % static_cast<std::size_t>(systemPageSize) != 0) {
31 throw std::runtime_error("OrderPool page size is incompatible with the system page size");
32 }
33
34 const std::size_t allocationSize = PageSize * 2;
35 void* allocation = mmap(nullptr, allocationSize, PROT_READ | PROT_WRITE,
36 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
37 if (allocation == MAP_FAILED) {
38 throw std::system_error(errno, std::generic_category(), "mmap failed");
39 }
40
41 const auto address = reinterpret_cast<std::uintptr_t>(allocation);
42 const auto alignedAddress = (address + PageSize - 1) & ~(PageSize - 1);
43 const auto prefixSize = alignedAddress - address;
44 const auto suffixSize = allocationSize - prefixSize - PageSize;
45 if (prefixSize != 0) {
46 munmap(reinterpret_cast<void*>(address), prefixSize);
47 }
48 if (suffixSize != 0) {
49 munmap(reinterpret_cast<void*>(alignedAddress + PageSize), suffixSize);
50 }
51
52 storage = reinterpret_cast<void*>(alignedAddress);
53 for (std::size_t index = 0; index + 1 < SlotsPerPage; ++index) {
54 nextFree[index] = static_cast<std::uint16_t>(index + 1);
55 }
56 nextFree[SlotsPerPage - 1] = SlotsPerPage;
57 }
58
59 /** Returns the page mapping to the operating system. */
61 munmap(storage, PageSize);
62 }
63
64 Order* rawSlot(std::size_t index) {
65 return reinterpret_cast<Order*>(
66 static_cast<std::byte*>(storage) + index * sizeof(Order));
67 }
68
69 Order* slot(std::size_t index) {
70 return std::launder(reinterpret_cast<Order*>(
71 static_cast<std::byte*>(storage) + index * sizeof(Order)));
72 }
73};
74
76 for (const auto& page : pages) {
77 for (std::size_t index = 0; index < SlotsPerPage; ++index) {
78 if (page->occupied[index]) {
79 std::destroy_at(page->slot(index));
80 }
81 }
82 }
83}
84
85/** Creates and registers a page without moving existing pages. */
86OrderPool::Page* OrderPool::createPage() {
87 auto page = std::make_unique<Page>();
88 Page* pagePointer = page.get();
89 pages.push_back(std::move(page));
90 const auto pageBase = reinterpret_cast<std::uintptr_t>(pagePointer->storage);
91 try {
92 const auto result = pageIndex.emplace(pageBase, pagePointer);
93 if (!result.second) {
94 pages.pop_back();
95 throw std::logic_error("Duplicate OrderPool page address");
96 }
97 } catch (...) {
98 if (pageIndex.find(pageBase) == pageIndex.end()) {
99 pages.pop_back();
100 }
101 throw;
102 }
103 addToFreePageList(pagePointer);
104 return pagePointer;
105}
106
107void OrderPool::addToFreePageList(Page* page) {
108 if (page->onFreePageList) {
109 return;
110 }
111 page->previousFreePage = nullptr;
112 page->nextFreePage = firstPageWithFreeSlot;
113 if (firstPageWithFreeSlot != nullptr) {
114 firstPageWithFreeSlot->previousFreePage = page;
115 }
116 firstPageWithFreeSlot = page;
117 page->onFreePageList = true;
118}
119
120void OrderPool::removeFromFreePageList(Page* page) {
121 if (!page->onFreePageList) {
122 return;
123 }
124 if (page->previousFreePage != nullptr) {
125 page->previousFreePage->nextFreePage = page->nextFreePage;
126 } else {
127 firstPageWithFreeSlot = page->nextFreePage;
128 }
129 if (page->nextFreePage != nullptr) {
130 page->nextFreePage->previousFreePage = page->previousFreePage;
131 }
132 page->previousFreePage = nullptr;
133 page->nextFreePage = nullptr;
134 page->onFreePageList = false;
135}
136
137Order* OrderPool::allocate(OrderId orderId, Price price, Quantity originalQuantity,
138 Timestamp timestamp, OrderSide orderSide,
139 OrderType orderType, SequenceNumber sequenceNumber) {
140 return allocate(orderId, price, originalQuantity, originalQuantity, timestamp,
141 orderSide, orderType, sequenceNumber);
142}
143
144Order* OrderPool::allocate(OrderId orderId, Price price, Quantity originalQuantity,
145 Quantity remainingQuantity, Timestamp timestamp,
146 OrderSide orderSide, OrderType orderType,
147 SequenceNumber sequenceNumber) {
148 Page* page = firstPageWithFreeSlot;
149 if (page == nullptr) {
150 page = createPage();
151 }
152
153 const std::size_t index = page->firstFree;
154 Order* order = std::construct_at(page->rawSlot(index), orderId, price,
155 originalQuantity, remainingQuantity,
156 timestamp, orderSide, orderType,
157 sequenceNumber);
158
159 page->firstFree = page->nextFree[index];
160 --page->freeCount;
161 page->occupied[index] = true;
162 if (page->freeCount == 0) {
163 removeFromFreePageList(page);
164 }
165
166 ++liveOrderCount;
167 return order;
168}
169
170/**
171 * @details Only ever creates pages, never removes them: reserving a smaller
172 * count than a previous call is a no-op rather than shrinking capacity that
173 * may already hold live orders.
174 */
175void OrderPool::reserve(std::size_t orderCount) {
176 const std::size_t neededPages = (orderCount + SlotsPerPage - 1) / SlotsPerPage;
177 while (pages.size() < neededPages) {
178 createPage();
179 }
180}
181
182/** Releases a live slot after validating pool ownership and allocation state. */
184 if (order == nullptr) {
185 throw std::invalid_argument("Cannot release a null order");
186 }
187
188 const auto address = reinterpret_cast<std::uintptr_t>(order);
189 const auto pageBase = address & ~(PageSize - 1);
190 const auto pageIterator = pageIndex.find(pageBase);
191 if (pageIterator == pageIndex.end()) {
192 throw std::invalid_argument("Order does not belong to this pool");
193 }
194
195 Page* page = pageIterator->second;
196 const auto offset = address - pageBase;
197 if (offset % sizeof(Order) != 0) {
198 throw std::invalid_argument("Pointer is not an Order slot");
199 }
200 const std::size_t index = offset / sizeof(Order);
201 if (index >= SlotsPerPage || !page->occupied[index]) {
202 throw std::invalid_argument("Order slot is not allocated");
203 }
204
205 std::destroy_at(order);
206 page->occupied[index] = false;
207 page->nextFree[index] = page->firstFree;
208 page->firstFree = static_cast<std::uint16_t>(index);
209 if (page->freeCount == 0) {
210 addToFreePageList(page);
211 }
212 ++page->freeCount;
213 --liveOrderCount;
214}
215
216} // namespace lob
Type-safe identifier for an order; zero is reserved as invalid.
Definition order_id.hpp:10
~OrderPool()
Destroys all live orders and releases every allocated page.
void release(Order *order)
Destroys an order and returns its slot to the free list.
OrderPool()
Creates an empty pool without allocating a page.
void reserve(std::size_t orderCount)
Pre-allocates enough pages to hold at least orderCount live orders without allocate() creating a page...
Order * allocate(OrderId orderId, Price price, Quantity originalQuantity, Timestamp timestamp, OrderSide orderSide, OrderType orderType, SequenceNumber sequenceNumber)
Constructs an Order in a stable pool slot.
Represents a resting or incoming order and its lifecycle state.
Definition order.hpp:17
Type-safe nonzero price value used for price ordering.
Definition price.hpp:9
Type-safe unsigned order quantity; zero represents a filled state.
Definition quantity.hpp:8
Monotonic engine sequence used for deterministic processing order.
Nanoseconds since the Unix epoch; zero is reserved as invalid.
Definition timestamp.hpp:10
~Page()
Returns the page mapping to the operating system.
Page()
Allocates one aligned page and initializes its external free list.