PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
scratch_store.hpp
Go to the documentation of this file.
1#pragma once
2
3// CR150-CX L4 / CR-221 scope B. The shared substrate contract the A3/A4 lanes
4// code against; no engine/source policy, D3.7 sink fields or report schema
5// changes. docs/scratch-store.md states the same rules in prose and names the
6// fixtures that hold them. A header-only definition of the class is included at
7// the end of this file from scratch_store_impl.hpp; the declarations here are
8// the contract and the definition can be replaced without touching them.
10
11namespace pineforge::detail {
12
13// Supported geometry floor: a page count or page size below these, or a page
14// size that is not a power of two, makes status() programming_error.
15inline constexpr std::size_t scratch_min_page_count = 2;
16inline constexpr std::size_t scratch_min_page_size = 512;
17
18// Compare entire keys by bounded reads; keys need not fit a page. On ok,
19// order is negative/zero/positive. The comparator must be a deterministic
20// strict weak ordering; equality identifies ONE logical record (map/upsert
21// semantics). It must not throw, allocate with population, mutate the store,
22// retain sources, depend on addresses or consult mutable external state. A
23// non-ok status aborts the calling store operation: lower_bound returns it
24// unchanged, put and erase report programming_error.
26 std::uint64_t id = 0; // nonzero caller-owned schema/ordering version
27 void* context = nullptr;
29 int& order) noexcept = nullptr;
30};
31
32enum class ScratchOpenMode : std::uint32_t { create_empty, reopen };
33
35 std::size_t page_count = 0; // RESIDENT cache slots, not a record limit
36 std::size_t page_size = 0; // must match backing.page_size()
37 std::size_t journal_slots = 0; // fixed RESIDENT transaction staging slots
38 std::size_t index_slots = 0; // fixed RESIDENT index staging slots
41};
42
43// Fixed-size accounting for the growth gate and charged-I/O audits. Every
44// field is a plain counter; nothing here grows with records or operations.
46 std::uint64_t resident_bytes = 0; // userspace bytes of the fixed workspace
47 std::uint64_t backing_pages = 0; // backing extent now (page_count)
48 std::uint64_t backing_bytes = 0; // backing_pages * page size
49 std::uint64_t page_reads = 0; // read_page calls issued since construction
50 std::uint64_t page_writes = 0; // write_page calls issued since construction
51 std::uint64_t syncs = 0; // sync calls issued since construction
52};
53
54// One exclusive run owner, one transaction at a time. Logical key/value blobs,
55// handle lookup, ordered index, free-space metadata and overflow journal live
56// in backing pages. Only fixed cache/journal/index slots reside in userspace;
57// no directory, free list, undo vector, dirty map, traversal stack, or hidden
58// key cache may grow with records, operations, bytes, or transaction length.
59// Construction may reserve fixed, nonthrowing workspace sized ONLY by config;
60// no subsequent allocation/growth. Recursion needs caller-supplied fixed
61// buffers; the store cannot collect per-read/per-visitor state in a container.
62// Space freed by erase, replacement, commit or rollback is reusable: a
63// workload whose live bytes stay bounded runs in a bounded backing extent.
64// ScratchStore is the integration line's report-arena diagnostic enum.
65// Keep the command record store distinct from that existing vocabulary.
67public:
68 // backing/context are borrowed through destruction; no implicit backing
69 // creation or medium selection. create_empty requires an empty backing;
70 // reopen requires a previously successful commit with matching page size
71 // and ordering id. Cache/slot counts may differ on reopen. Supported
72 // geometry is exactly page_count>=2, a power-of-two page_size>=512 equal
73 // to the backing page size, journal_slots>=1 and index_slots>=1, with a
74 // nonzero order id and a comparator. A definition may bound page_size and
75 // page_count from above and must say so; the reference definition refuses
76 // a page_size above 1 MiB or a page_count above 16 Mi. Failures surface
77 // through status():
78 // bad geometry, null backing, create_empty on a used backing, an ordering
79 // id mismatch or workspace allocation failure => programming_error; reopen
80 // of an empty backing => not_found; reopen without a valid committed state
81 // => corrupt; backing I/O failure => io_error. A store whose status is not
82 // ok answers every other call with programming_error and changes nothing;
83 // its record_count is 0, its transaction_active is false, it performs no
84 // backing I/O and its destructor is harmless.
85 CommandScratchStore(ScratchStoreConfig config, ScratchBacking* backing) noexcept;
86 ~CommandScratchStore() noexcept override;
91
92 // Construction/open result; no operation is legal unless this is ok.
93 ScratchStatus status() const noexcept;
94 // The last typed backing I/O failure, retained across successful rollback
95 // I/O so a status raiser can preserve it. A new begin clears it. Diagnostic
96 // state only; it is absent from the store's logical/durable digests.
97 const ReportScratchIoError* carried_failure() const noexcept override;
98 std::size_t page_size() const noexcept override;
99 std::uint64_t record_count() const noexcept override;
100
101 // True from a successful begin until a successful commit or rollback,
102 // including while the transaction is rollback-only.
103 bool transaction_active() const noexcept;
104
105 // begin snapshots logical state including handle allocation. Nested begin
106 // is programming_error. Reads thereafter include successful pending edits.
107 // A rollback-only transaction accepts nothing but rollback: begin, put,
108 // erase and commit return programming_error with no effect. A mutation
109 // attempted from inside any read, comparison or source callback returns
110 // programming_error with no effect at all (it does not poison the
111 // transaction). So do a nested begin and a commit that names an unknown
112 // durability level: refused before any work, they change nothing.
113 ScratchStatus begin() noexcept;
114
115 // Requires begin. Upsert by comparator equality: the equal record keeps its
116 // handle and takes this call's key and value bytes, even if their size or
117 // page location changes. A new record gets the allocation counter's value
118 // (1 for the first record of a created store), then the counter advances
119 // by one; committed and erased handles are never recycled. Sources are borrowed for this
120 // call only, may be larger than cache and must not read this store. result
121 // changes ONLY on success. Filling any resident slot spills to backing,
122 // never refuses, implicitly commits, grows RAM, or throws. An unserviceable
123 // put returns io_error for backing I/O failure (including medium exhaustion);
124 // input/comparison failure, a call outside a transaction or handle domain
125 // exhaustion returns programming_error. No run failure is prescribed. A
126 // failure inside a transaction makes it rollback-only; partial new bytes
127 // cannot damage the pre-begin state. Discard outputs from aborted puts.
129 ScratchHandle& result) noexcept;
130
131 // Requires begin. An absent handle (including scratch_no_handle) => not_found
132 // with no edit and no rollback-only effect. Backing I/O failure => io_error;
133 // a call outside a transaction or any other failure => programming_error.
134 // A failure in a transaction makes it rollback-only. The state of the
135 // transaction is judged first: outside a transaction, or in a rollback-only
136 // one, even an absent handle gets programming_error.
138
139 // Only success publishes a committed state. buffered permits orderly
140 // close/reopen; data/full additionally invoke the selected durability hook
141 // in journal/data-before-publication order and once more after the new
142 // state is published, so ok means the publication itself reached the
143 // medium. Requires an active transaction that is not rollback-only (else
144 // programming_error, no effect). Failed mutation/commit leaves an active,
145 // rollback-only transaction and reports the failing backing call's status;
146 // a failed backing call is never retried by the store. No implicit commit
147 // in destructor: abandoning a transaction and reopening yields the last
148 // successful commit.
149 ScratchStatus commit(ScratchDurability durability) noexcept;
150
151 // Restore ALL pre-begin logical bytes, handles, count, allocation state and
152 // order, even after writes/evictions/journal spill and also from a
153 // rollback-only transaction. Space the aborted transaction used stays
154 // reusable: repeated begin/put/rollback cycles do not grow the backing
155 // without bound. Outputs/handles first obtained in the aborted transaction
156 // must be discarded (its allocation numbers are issued again). rollback
157 // requires an active transaction and a serviceable backing. A rollback the
158 // backing refuses reports the failing call's status and leaves the
159 // transaction active and rollback-only; calling rollback again resumes
160 // where it stopped and completes once the backing answers.
161 ScratchStatus rollback() noexcept;
162
163 // While the active transaction is rollback-only, every read, walk and
164 // digest below returns programming_error (its pending state may be partial).
165 ScratchStatus point(ScratchHandle, ScratchRecordInfo&) const noexcept override;
167 ScratchBuffer destination) const noexcept override;
169 void* context) const noexcept override;
171 void* context) const noexcept override;
172
173 // No resident cursor/snapshot. end writes scratch_no_handle; other errors
174 // leave the output unchanged. next requires a presently live handle (else
175 // not_found) and finds its immediate ordered successor. lower_bound finds
176 // first key >= probe under the caller comparator (inclusive of
177 // equivalence). A comparator failure is returned unchanged. A probe may
178 // itself be a source backed by this store, such as a key a walk delivered:
179 // the reads the comparator then makes nest like any other read.
180 ScratchStatus first(ScratchHandle& result) const noexcept;
181 ScratchStatus next(ScratchHandle current, ScratchHandle& result) const noexcept;
183 ScratchHandle& result) const noexcept;
184
185 // Noncryptographic ordered CONTENT digest, independent of handles, cache,
186 // chunk sizes, insertion history, transactions and physical placement.
187 // FNV-1a-64: basis 14695981039346656037, prime 1099511628211, mod 2^64.
188 // Fold LE64(record_count), then each ascending record's LE64(key_bytes),
189 // key bytes, LE64(value_bytes), value bytes. No terminators/padding.
190 // Same caller-owned exact-size page and reentrancy rules as for_each.
191 // Output changes only on ok; errors never yield a sentinel digest.
192 ScratchStatus ordered_digest(ScratchBuffer page, std::uint64_t& result) const noexcept;
193
194 // Logical durable-state digest for consumer state hashing; only legal
195 // outside a transaction. Same fold, prefixed with LE64(order.id),
196 // LE64(next_handle), then LE64(record_count); each row additionally folds
197 // LE64(handle) BEFORE its lengths/bytes. Hash every persisted logical
198 // field; physical cache/journal placement is not logical durable state.
199 ScratchStatus state_digest(ScratchBuffer page, std::uint64_t& result) const noexcept;
200
201 // Fixed-size accounting; always available, never fails. resident_bytes is
202 // constant for the object's lifetime, the three counters cover the backing
203 // calls this store issued, backing_pages is the backing extent right now.
204 ScratchStoreStats stats() const noexcept;
205
206private:
207 struct State;
208 State* state_ = nullptr; // one fixed workspace; out-of-line implementation
209};
210
211} // namespace pineforge::detail
212
213#include <pineforge/detail/scratch_store_impl.hpp>
std::size_t page_size() const noexcept override
ScratchStatus ordered_digest(ScratchBuffer page, std::uint64_t &result) const noexcept
ScratchStatus state_digest(ScratchBuffer page, std::uint64_t &result) const noexcept
CommandScratchStore(ScratchStoreConfig config, ScratchBacking *backing) noexcept
ScratchStoreStats stats() const noexcept
const ReportScratchIoError * carried_failure() const noexcept override
ScratchStatus read(ScratchHandle, ScratchField, std::uint64_t offset, ScratchBuffer destination) const noexcept override
ScratchStatus first(ScratchHandle &result) const noexcept
ScratchStatus for_each(ScratchBuffer page, ScratchRecordVisitor, void *context) const noexcept override
ScratchStatus point(ScratchHandle, ScratchRecordInfo &) const noexcept override
ScratchStatus commit(ScratchDurability durability) noexcept
ScratchStatus next(ScratchHandle current, ScratchHandle &result) const noexcept
CommandScratchStore & operator=(const CommandScratchStore &)=delete
ScratchStatus lower_bound(const ScratchByteSource &probe, ScratchHandle &result) const noexcept
CommandScratchStore(CommandScratchStore &&)=delete
ScratchStatus erase(ScratchHandle) noexcept
std::uint64_t record_count() const noexcept override
CommandScratchStore & operator=(CommandScratchStore &&)=delete
ScratchStatus put(const ScratchByteSource &key, const ScratchByteSource &value, ScratchHandle &result) noexcept
CommandScratchStore(const CommandScratchStore &)=delete
ScratchStatus visit(ScratchHandle, ScratchBuffer page, ScratchChunkVisitor, void *context) const noexcept override
constexpr std::size_t scratch_min_page_count
bool(*)(void *, const ScratchChunkView &) noexcept ScratchChunkVisitor
std::uint64_t ScratchHandle
constexpr std::size_t scratch_min_page_size
bool(*)(void *, const ScratchRecordView &) noexcept ScratchRecordVisitor
ScratchStatus(* compare)(void *, const ScratchByteSource &, const ScratchByteSource &, int &order) noexcept