3#include "lob/book/order_pool.hpp"
19 std::array<std::uint16_t, SlotsPerPage> nextFree{};
20 std::array<bool, SlotsPerPage> occupied{};
21 std::uint16_t firstFree{0};
22 std::size_t freeCount{SlotsPerPage};
23 Page* previousFreePage{
nullptr};
24 Page* nextFreePage{
nullptr};
25 bool onFreePageList{
false};
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");
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");
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);
48 if (suffixSize != 0) {
49 munmap(
reinterpret_cast<void*
>(alignedAddress + PageSize), suffixSize);
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);
56 nextFree[SlotsPerPage - 1] = SlotsPerPage;
61 munmap(storage, PageSize);
64 Order* rawSlot(std::size_t index) {
65 return reinterpret_cast<Order*
>(
66 static_cast<std::byte*
>(storage) + index *
sizeof(
Order));
69 Order* slot(std::size_t index) {
70 return std::launder(
reinterpret_cast<Order*
>(
71 static_cast<std::byte*
>(storage) + index *
sizeof(
Order)));
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));
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);
92 const auto result = pageIndex.emplace(pageBase, pagePointer);
95 throw std::logic_error(
"Duplicate OrderPool page address");
98 if (pageIndex.find(pageBase) == pageIndex.end()) {
103 addToFreePageList(pagePointer);
107void OrderPool::addToFreePageList(Page* page) {
108 if (page->onFreePageList) {
111 page->previousFreePage =
nullptr;
112 page->nextFreePage = firstPageWithFreeSlot;
113 if (firstPageWithFreeSlot !=
nullptr) {
114 firstPageWithFreeSlot->previousFreePage = page;
116 firstPageWithFreeSlot = page;
117 page->onFreePageList =
true;
120void OrderPool::removeFromFreePageList(Page* page) {
121 if (!page->onFreePageList) {
124 if (page->previousFreePage !=
nullptr) {
125 page->previousFreePage->nextFreePage = page->nextFreePage;
127 firstPageWithFreeSlot = page->nextFreePage;
129 if (page->nextFreePage !=
nullptr) {
130 page->nextFreePage->previousFreePage = page->previousFreePage;
132 page->previousFreePage =
nullptr;
133 page->nextFreePage =
nullptr;
134 page->onFreePageList =
false;
138 Timestamp timestamp, OrderSide orderSide,
140 return allocate(orderId, price, originalQuantity, originalQuantity, timestamp,
141 orderSide, orderType, sequenceNumber);
146 OrderSide orderSide, OrderType orderType,
148 Page* page = firstPageWithFreeSlot;
149 if (page ==
nullptr) {
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,
159 page->firstFree = page->nextFree[index];
161 page->occupied[index] =
true;
162 if (page->freeCount == 0) {
163 removeFromFreePageList(page);
176 const std::size_t neededPages = (orderCount + SlotsPerPage - 1) / SlotsPerPage;
177 while (pages.size() < neededPages) {
184 if (order ==
nullptr) {
185 throw std::invalid_argument(
"Cannot release a null order");
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");
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");
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");
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);
Type-safe identifier for an order; zero is reserved as invalid.
~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.
Type-safe nonzero price value used for price ordering.
Type-safe unsigned order quantity; zero represents a filled state.
Monotonic engine sequence used for deterministic processing order.
Nanoseconds since the Unix epoch; zero is reserved as invalid.
~Page()
Returns the page mapping to the operating system.
Page()
Allocates one aligned page and initializes its external free list.