PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
scratch_backing.hpp
Go to the documentation of this file.
1#pragma once
2
3// Scope-B contracts, not an OS backing factory.
4#include <cstddef>
5#include <cstdint>
6#include <cstring>
7#include <limits>
8#include <optional>
9
10namespace pineforge::detail {
11
13
14// No capacity/refusal status and no RunFailureCode conversion in this API.
15// end: an ordered seek found no record. cancelled: a visitor returned false.
16// not_found: an absent handle, a page past the extent, or nothing to reopen.
17// programming_error: caller contract violations. io_error: backing I/O faults,
18// including medium exhaustion or a backing that cannot extend.
19// corrupt: stored bytes failed validation.
29
30using ScratchPageId = std::uint64_t;
32 std::numeric_limits<ScratchPageId>::max();
33
35 const std::byte* data = nullptr;
36 std::size_t size = 0;
37};
38
40 std::byte* data = nullptr;
41 std::size_t size = 0;
42};
43
44// The creator must select and disclose the physical medium. There is no
45// default TMPDIR, memfd, tmpfs, mmap, or fallback to another medium here.
46enum class ScratchBackingKind : std::uint32_t {
51};
52
53enum class ScratchDurability : std::uint32_t {
54 buffered, // complete positional writes; no power-loss guarantee
55 data, // data and the metadata needed to retrieve it (fdatasync-like)
56 full, // data and all backing metadata (fsync-like)
57};
58
59enum class ScratchIoOperation : std::uint32_t { read_page, write_page, sync };
60
61// One charge per attempted public operation, including errors, journal I/O,
62// rollback I/O and memory-backed I/O. bytes is REQUESTED bytes, not a claim
63// about physical device traffic. sync uses scratch_no_page and zero bytes.
71
72// A passive accounting sink. Its callback must not call back into the engine,
73// a store or a consumer.
75 void* context = nullptr;
76 void (*charge)(void*, const ScratchIoCharge&) noexcept = nullptr;
77};
78
79// Exclusive, borrowed, positional backing for one store at a time. It owns
80// neither the store nor observer context. Pages are exactly page_size bytes;
81// ids are [0,scratch_no_page). Reads at/after page_count return not_found.
82// Successful writes extend page_count to max(old,id+1), zero-filling holes.
83// A failed write MAY have partially modified its target: transactions must
84// protect previously committed pages. No operation throws or allocates an
85// O(page_count) userspace directory. Production backing must extend/spill;
86// backing I/O faults, including exhaustion, are io_error. An arena backing
87// can additionally carry its original typed exception for an owner to rethrow;
88// the status itself is never a capacity code. programming_error is reserved
89// for caller contract violations.
91public:
92 virtual ~ScratchBacking() noexcept = default;
94 ScratchBacking& operator=(const ScratchBacking&) = delete;
96 ScratchBacking& operator=(ScratchBacking&&) = delete;
97
98 ScratchBackingKind kind() const noexcept { return kind_; }
99 std::size_t page_size() const noexcept { return page_size_; }
100 // True when this backing carries a registered passive accounting sink.
101 bool has_io_observer() const noexcept { return observer_.charge != nullptr; }
102 virtual std::uint64_t page_count() const noexcept = 0;
103 // Construction-time arena region bound, including a zero-byte share.
104 // No value for disk/test backings; this policy is not logical store state.
105 virtual std::optional<std::uint64_t> arena_capacity_bytes() const noexcept {
106 return std::nullopt;
107 }
108 // Optional original exception for the last backing operation. Borrowed
109 // until the next operation/destruction; inspect before cleanup I/O.
110 // Disk/test backings retain their existing status-only behavior.
111 virtual const ReportScratchIoError* carried_failure() const noexcept { return nullptr; }
112
113 ScratchStatus read_page(ScratchPageId id, ScratchBuffer destination) const noexcept {
114 const auto status = valid_page(id, destination.data, destination.size)
116 charge(ScratchIoOperation::read_page, id, destination.size, status);
117 return status;
118 }
119
121 const auto status = valid_page(id, source.data, source.size)
124 return status;
125 }
126
127 ScratchStatus sync(ScratchDurability durability) noexcept {
128 const bool valid = durability == ScratchDurability::buffered
129 || durability == ScratchDurability::data || durability == ScratchDurability::full;
130 const auto status = valid ? do_sync(durability) : ScratchStatus::programming_error;
132 return status;
133 }
134
135protected:
137 ScratchIoObserver observer = {}) noexcept
138 : kind_(kind), page_size_(page_size), observer_(observer) {}
139
143
144private:
145 bool valid_page(ScratchPageId id, const void* bytes, std::size_t size) const noexcept {
146 return id != scratch_no_page && page_size_ != 0 && bytes && size == page_size_;
147 }
148 void charge(ScratchIoOperation operation, ScratchPageId id, std::size_t bytes,
149 ScratchStatus status) const noexcept {
150 if (observer_.charge)
151 observer_.charge(observer_.context,
152 {kind_, operation, id, static_cast<std::uint64_t>(bytes), status});
153 }
154
155 const ScratchBackingKind kind_;
156 const std::size_t page_size_;
157 const ScratchIoObserver observer_;
158};
159
160// Test fixture only: fixed caller-owned bytes, no allocator, file, mapping or
161// capacity growth. A test must provision enough BACKING pages for its entire
162// workload, separately from the store's tiny resident cache. Exhausting this
163// fixture is a programming error in the fixture, not a store refusal policy.
164// Reopen a store against the SAME live backing object (extent is retained).
165// data/full sync are unsupported: memory is not falsely advertised durable.
167public:
169 ScratchIoObserver observer = {}) noexcept
171 storage_(storage) {}
172
173 ScratchStatus status() const noexcept {
174 return page_size() && storage_.data && storage_.size
175 && storage_.size % page_size() == 0
177 }
178 std::uint64_t capacity_pages() const noexcept {
179 return page_size() ? storage_.size / page_size() : 0;
180 }
181 std::uint64_t page_count() const noexcept override { return extent_; }
182
183protected:
184 ScratchStatus do_read_page(ScratchPageId id, ScratchBuffer destination) const noexcept override {
186 if (id >= extent_) return ScratchStatus::not_found;
187 const auto offset = static_cast<std::size_t>(id) * page_size();
188 std::memmove(destination.data, storage_.data + offset, page_size());
189 return ScratchStatus::ok;
190 }
192 if (status() != ScratchStatus::ok || id >= capacity_pages())
194 const auto offset = static_cast<std::size_t>(id) * page_size();
195 if (id > extent_) {
196 const auto old_end = static_cast<std::size_t>(extent_) * page_size();
197 std::memset(storage_.data + old_end, 0, offset - old_end);
198 }
199 std::memmove(storage_.data + offset, source.data, page_size());
200 if (id >= extent_) extent_ = id + 1;
201 return ScratchStatus::ok;
202 }
207
208private:
209 const ScratchBuffer storage_;
210 std::uint64_t extent_ = 0;
211};
212
213} // namespace pineforge::detail
ScratchStatus do_sync(ScratchDurability durability) noexcept override
ScratchStatus do_write_page(ScratchPageId id, ScratchBytes source) noexcept override
std::uint64_t capacity_pages() const noexcept
std::uint64_t page_count() const noexcept override
ScratchStatus do_read_page(ScratchPageId id, ScratchBuffer destination) const noexcept override
FixedMemoryScratchBacking(ScratchBuffer storage, std::size_t page_size, ScratchIoObserver observer={}) noexcept
virtual std::uint64_t page_count() const noexcept=0
ScratchBackingKind kind() const noexcept
std::size_t page_size() const noexcept
virtual ScratchStatus do_read_page(ScratchPageId, ScratchBuffer) const noexcept=0
ScratchBacking(const ScratchBacking &)=delete
virtual const ReportScratchIoError * carried_failure() const noexcept
virtual ScratchStatus do_write_page(ScratchPageId, ScratchBytes) noexcept=0
ScratchStatus read_page(ScratchPageId id, ScratchBuffer destination) const noexcept
virtual ScratchStatus do_sync(ScratchDurability) noexcept=0
ScratchBacking(ScratchBackingKind kind, std::size_t page_size, ScratchIoObserver observer={}) noexcept
virtual std::optional< std::uint64_t > arena_capacity_bytes() const noexcept
virtual ~ScratchBacking() noexcept=default
ScratchStatus sync(ScratchDurability durability) noexcept
ScratchStatus write_page(ScratchPageId id, ScratchBytes source) noexcept
constexpr ScratchPageId scratch_no_page
std::uint64_t ScratchPageId
void(* charge)(void *, const ScratchIoCharge &) noexcept