PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
id_table_read_cache.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <cstddef>
6#include <cstdint>
7#include <cstring>
8#include <limits>
9#include <memory>
10#include <new>
11#include <stdexcept>
12#include <utility>
13
14namespace pineforge::detail {
15
16// Copy-in/copy-out logical pages for the private disk id tables only.
17//
18// WRITE-THROUGH: this class never writes or delays a write. Owners invalidate
19// before the real write, including a write that might fail after a prefix.
20// Clear, move and backend/identity changes invalidate ALL lines. Growth
21// invalidates the old tail page onward in that file; in-place writes
22// invalidate every overlapping line, even when the write fails.
23// EOF: out-of-extent reads go straight to the original reader, preserving its
24// short-read/EIO/errno text and copied prefix. A fill stops at logical_end;
25// no zero padding and no bytes beyond that end are read or returned.
26// BYPASS: plain_disk must be false for EVERY slab/arena backend, including
27// unjailed regular-file arenas and inherited/sealed memfds. No allocation,
28// tag probe or copied page occurs on that path. No payload pointer escapes.
29//
30// Fixed budget: at most 16 x 4 KiB = 64 KiB of lazy page bytes per table,
31// plus 16 fixed tags. Default capacity is zero, with no allocation. Only the
32// adapter opts its four tables in: 64/64/32/32 KiB (pair data+index together).
33// Total page bytes are 192 KiB per adapter, not per engine, regardless of
34// bars, ids or trades. One adapter owns each cache; no sharing across threads.
35// No growing directory.
36// This is disposable physical state, not logical/durable broker state.
38public:
39 static constexpr std::size_t kPageBytes = 4096;
40 static constexpr std::size_t kCacheBytes = 64 * 1024;
41 static constexpr std::size_t kSlots = kCacheBytes / kPageBytes;
42 static constexpr std::size_t kPairCacheBytes = kCacheBytes / 2;
43 static constexpr std::size_t kAdapterCacheBytes = 3 * kCacheBytes;
44
45 explicit IdTableReadCache(std::size_t bytes = 0) { set_capacity(bytes); }
48 IdTableReadCache(IdTableReadCache&& other) noexcept { *this = std::move(other); }
50 if (this != &other) {
51 release();
52 bytes_ = std::move(other.bytes_);
53 slots_ = other.slots_;
54 other.invalidate();
55 // Never transfer a valid line across an ownership change.
56 }
57 return *this;
58 }
59 void set_capacity(std::size_t bytes) {
60 if (bytes > kCacheBytes || bytes % kPageBytes != 0)
61 throw std::invalid_argument("id read cache capacity must be page-aligned and at most 64 KiB");
62 release();
63 slots_ = bytes / kPageBytes;
64 }
65 void invalidate() const noexcept {
66 for (auto& tag : tags_) tag.page = kInvalid;
67 }
68 void invalidate_from(unsigned file, std::uint64_t offset) const noexcept {
69 const auto first = offset / kPageBytes;
70 for (auto& tag : tags_)
71 if (tag.file == file && tag.page >= first) tag.page = kInvalid;
72 }
73 void release() noexcept { invalidate(); bytes_.reset(); }
74
75 void before_write(unsigned file, std::uint64_t logical_end,
76 std::uint64_t offset, std::size_t length) const noexcept {
77 if (slots_ == 0 || length == 0) return;
78 if (offset > logical_end || length > logical_end - offset) {
79 // Include any overwritten prefix when a write straddles the end.
80 // Pure appends retain all complete pages before the old tail.
81 invalidate_from(file, std::min(offset, logical_end));
82 return;
83 }
84 const auto first = offset / kPageBytes;
85 const auto last = (offset + length - 1) / kPageBytes;
86 for (auto& tag : tags_)
87 if (tag.file == file && tag.page >= first && tag.page <= last) tag.page = kInvalid;
88 }
89
90 template<class File>
91 void read(const File& source, bool plain_disk, std::uint64_t logical_end,
92 unsigned file, std::uint64_t offset, void* destination, std::size_t length) const {
93 if (!plain_disk || slots_ == 0 || length == 0
94 || offset >= logical_end || length > logical_end - offset) {
95 source.read(offset, destination, length);
96 return;
97 }
98 if (!bytes_) {
99 bytes_.reset(new (std::nothrow) unsigned char[slots_ * kPageBytes]);
100 if (!bytes_) {
101 source.read(offset, destination, length);
102 return;
103 }
104 }
105 auto* output = static_cast<unsigned char*>(destination);
106 while (length != 0) {
107 const auto page = offset / kPageBytes;
108 const auto base = page * kPageBytes;
109 const auto within = static_cast<std::size_t>(offset - base);
110 const auto valid = static_cast<std::size_t>(
111 std::min<std::uint64_t>(kPageBytes, logical_end - base));
112 // Deterministic direct mapping; data/index have separate tags
113 // but share this one fixed allocation.
114 const auto slot = static_cast<std::size_t>((page + file * 7ULL) % slots_);
115 auto& tag = tags_[slot];
116 auto* line = bytes_.get() + slot * kPageBytes;
117 if (tag.page != page || tag.file != file) {
118 tag.page = kInvalid; // A failed/partial fill publishes nothing.
119 source.read(base, line, valid);
120 tag.file = file;
121 tag.page = page;
122 }
123 const auto count = std::min(length, valid - within);
124 std::memcpy(output, line + within, count);
125 output += count;
126 offset += count;
127 length -= count;
128 }
129 }
130
131private:
132 static constexpr auto kInvalid = std::numeric_limits<std::uint64_t>::max();
133 struct Tag {
134 std::uint64_t page = kInvalid;
135 unsigned file = 0;
136 };
137 mutable std::array<Tag, kSlots> tags_{};
138 mutable std::unique_ptr<unsigned char[]> bytes_;
139 std::size_t slots_ = 0;
140};
141} // namespace pineforge::detail
IdTableReadCache(const IdTableReadCache &)=delete
static constexpr std::size_t kCacheBytes
void read(const File &source, bool plain_disk, std::uint64_t logical_end, unsigned file, std::uint64_t offset, void *destination, std::size_t length) const
IdTableReadCache & operator=(IdTableReadCache &&other) noexcept
static constexpr std::size_t kAdapterCacheBytes
IdTableReadCache(IdTableReadCache &&other) noexcept
static constexpr std::size_t kPairCacheBytes
void before_write(unsigned file, std::uint64_t logical_end, std::uint64_t offset, std::size_t length) const noexcept
static constexpr std::size_t kPageBytes
void invalidate_from(unsigned file, std::uint64_t offset) const noexcept
static constexpr std::size_t kSlots
IdTableReadCache & operator=(const IdTableReadCache &)=delete