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// Scope-B shared substrate contract. 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 => programming_error; workspace allocation failure with valid
80 // geometry => io_error with a carried out_of_memory failure; reopen
81 // of an empty backing => not_found; reopen without a valid committed state
82 // => corrupt; backing I/O failure => io_error. A store whose status is not
83 // ok answers every other call with programming_error and changes nothing;
84 // its record_count is 0, its transaction_active is false, it performs no
85 // backing I/O and its destructor is harmless.
86 CommandScratchStore(ScratchStoreConfig config, ScratchBacking* backing) noexcept;
87 ~CommandScratchStore() noexcept override;
92
93 // Construction/open result; no operation is legal unless this is ok.
94 ScratchStatus status() const noexcept;
95 // The typed setup allocation failure, or last typed backing I/O failure,
96 // retained across successful rollback I/O so a status raiser can preserve
97 // it. A new begin clears the backing failure. Diagnostic state only; it is
98 // absent from the store's logical/durable digests.
99 const ReportScratchIoError* carried_failure() const noexcept override;
100 std::size_t page_size() const noexcept override;
101 std::uint64_t record_count() const noexcept override;
102
103 // True from a successful begin until a successful commit or rollback,
104 // including while the transaction is rollback-only.
105 bool transaction_active() const noexcept;
106 // True when the borrowed backing has a passive logical-I/O observer.
107 bool has_io_observer() const noexcept;
108
109 // begin snapshots logical state including handle allocation. Nested begin
110 // is programming_error. Reads thereafter include successful pending edits.
111 // A rollback-only transaction accepts nothing but rollback: begin, put,
112 // erase and commit return programming_error with no effect. A mutation
113 // attempted from inside any read, comparison or source callback returns
114 // programming_error with no effect at all (it does not poison the
115 // transaction). So do a nested begin and a commit that names an unknown
116 // durability level: refused before any work, they change nothing.
117 ScratchStatus begin() noexcept;
118
119 // Requires begin. Upsert by comparator equality: the equal record keeps its
120 // handle and takes this call's key and value bytes, even if their size or
121 // page location changes. A new record gets the allocation counter's value
122 // (1 for the first record of a created store), then the counter advances
123 // by one; committed and erased handles are never recycled. Sources are borrowed for this
124 // call only, may be larger than cache and must not read this store. result
125 // changes ONLY on success. Filling any resident slot spills to backing,
126 // never refuses, implicitly commits, grows RAM, or throws. An unserviceable
127 // put returns io_error for backing I/O failure (including medium exhaustion);
128 // input/comparison failure, a call outside a transaction or handle domain
129 // exhaustion returns programming_error. No run failure is prescribed. A
130 // failure inside a transaction makes it rollback-only; partial new bytes
131 // cannot damage the pre-begin state. Discard outputs from aborted puts.
133 ScratchHandle& result) noexcept;
134
135 // Requires begin. An absent handle (including scratch_no_handle) => not_found
136 // with no edit and no rollback-only effect. Backing I/O failure => io_error;
137 // a call outside a transaction or any other failure => programming_error.
138 // A failure in a transaction makes it rollback-only. The state of the
139 // transaction is judged first: outside a transaction, or in a rollback-only
140 // one, even an absent handle gets programming_error.
142
143 // Only success publishes a committed state. buffered permits orderly
144 // close/reopen; data/full additionally invoke the selected durability hook
145 // in journal/data-before-publication order and once more after the new
146 // state is published, so ok means the publication itself reached the
147 // medium. Requires an active transaction that is not rollback-only (else
148 // programming_error, no effect). Failed mutation/commit leaves an active,
149 // rollback-only transaction and reports the failing backing call's status;
150 // a failed backing call is never retried by the store. No implicit commit
151 // in destructor: abandoning a transaction and reopening yields the last
152 // successful commit.
153 ScratchStatus commit(ScratchDurability durability) noexcept;
154
155 // Restore ALL pre-begin logical bytes, handles, count, allocation state and
156 // order, even after writes/evictions/journal spill and also from a
157 // rollback-only transaction. Space the aborted transaction used stays
158 // reusable: repeated begin/put/rollback cycles do not grow the backing
159 // without bound. Outputs/handles first obtained in the aborted transaction
160 // must be discarded (its allocation numbers are issued again). rollback
161 // requires an active transaction and a serviceable backing. A rollback the
162 // backing refuses reports the failing call's status and leaves the
163 // transaction active and rollback-only; calling rollback again resumes
164 // where it stopped and completes once the backing answers.
165 ScratchStatus rollback() noexcept;
166
167 // While the active transaction is rollback-only, every read, walk and
168 // digest below returns programming_error (its pending state may be partial).
169 ScratchStatus point(ScratchHandle, ScratchRecordInfo&) const noexcept override;
171 ScratchBuffer destination) const noexcept override;
173 void* context) const noexcept override;
175 void* context) const noexcept override;
176
177 // No resident cursor/snapshot. end writes scratch_no_handle; other errors
178 // leave the output unchanged. next requires a presently live handle (else
179 // not_found) and finds its immediate ordered successor. lower_bound finds
180 // first key >= probe under the caller comparator (inclusive of
181 // equivalence). A comparator failure is returned unchanged. A probe may
182 // itself be a source backed by this store, such as a key a walk delivered:
183 // the reads the comparator then makes nest like any other read.
184 ScratchStatus first(ScratchHandle& result) const noexcept;
185 ScratchStatus next(ScratchHandle current, ScratchHandle& result) const noexcept;
187 ScratchHandle& result) const noexcept;
188
189 // Noncryptographic ordered CONTENT digest, independent of handles, cache,
190 // chunk sizes, insertion history, transactions and physical placement.
191 // FNV-1a-64: basis 14695981039346656037, prime 1099511628211, mod 2^64.
192 // Fold LE64(record_count), then each ascending record's LE64(key_bytes),
193 // key bytes, LE64(value_bytes), value bytes. No terminators/padding.
194 // Same caller-owned exact-size page and reentrancy rules as for_each.
195 // Output changes only on ok; errors never yield a sentinel digest.
196 ScratchStatus ordered_digest(ScratchBuffer page, std::uint64_t& result) const noexcept;
197
198 // Logical durable-state digest for consumer state hashing; only legal
199 // outside a transaction. Same fold, prefixed with LE64(order.id),
200 // LE64(next_handle), then LE64(record_count); each row additionally folds
201 // LE64(handle) BEFORE its lengths/bytes. Hash every persisted logical
202 // field; physical cache/journal placement is not logical durable state.
203 ScratchStatus state_digest(ScratchBuffer page, std::uint64_t& result) const noexcept;
204
205 // Fixed-size accounting; always available, never fails. resident_bytes is
206 // constant for the object's lifetime, the three counters cover the backing
207 // calls this store issued, backing_pages is the backing extent right now.
208 ScratchStoreStats stats() const noexcept;
209
210 // Consume count fresh handle numbers without creating records: the same
211 // logical allocation state as count successful new-key puts followed by
212 // erasing those records. Live handles/bytes, count and ordered_digest stay
213 // unchanged; state_digest includes the advanced allocation counter.
214 // Joins an active transaction, whose rollback undoes the advance. When
215 // idle, a nonzero call begins and commits its own buffered transaction,
216 // making success reopenable. Zero is a no-op, but still requires a usable,
217 // non-rollback-only store and must not be called from a callback.
218 // Handle exhaustion returns programming_error at the same point as puts:
219 // the available prefix is consumed and the transaction is rollback-only.
220 // Failed implicit mutation/commit likewise leaves a transaction requiring
221 // rollback, under the existing put/commit status and I/O failure rules.
222 // Fixed workspace and work independent of count; no record/backing walk.
223 ScratchStatus advance_handles(std::uint64_t count) noexcept;
224
225private:
226 struct State;
227 State* state_ = nullptr; // one fixed workspace; out-of-line implementation
228};
229
230} // namespace pineforge::detail
231
232#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 advance_handles(std::uint64_t count) noexcept
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