PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
id_pair_scratch_file.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <algorithm>
6#include <cerrno>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10#include <string>
11#include <system_error>
12#include <utility>
13
14namespace pineforge::detail {
15
16// Keep L19a's system_error surface, carrying the run's resource failure too.
17class IdPairStoreError final : public std::system_error, public RunFailureInfo {
18public:
19 IdPairStoreError(int error, const char* operation)
20 : std::system_error(error, std::generic_category(), operation),
22};
23
25public:
26 // Explicit per-instance fixture seam. Counters observe actual transfers
27 // (syscalls in file mode, checked copies in arena mode);
28 // a selected write writes a real prefix, then reports an IO failure.
29 struct FixtureIO {
30 std::uint64_t reads = 0, writes = 0;
31 std::uint64_t read_bytes = 0, write_bytes = 0;
32 std::uint64_t fail_write = 0;
33 std::size_t partial_write = 0;
34 };
35 void fixture_count_io(FixtureIO* io) noexcept { fixture_io_ = io; }
36 FixtureIO* fixture_io() const noexcept { return fixture_io_; }
37 void set_store(ScratchStore store) noexcept { file_.set_store(store); }
38
40 file_.set_store(store);
41 }
45 : file_(std::move(other.file_)),
46 fixture_io_(std::exchange(other.fixture_io_, nullptr)) {}
48 if (this != &other) {
49 reset();
50 file_ = std::move(other.file_);
51 fixture_io_ = std::exchange(other.fixture_io_, nullptr);
52 }
53 return *this;
54 }
55 ~IdPairScratchFile() = default;
56
57 bool opened() const noexcept { return file_.opened(); }
58 bool disk_backed() const noexcept { return file_.disk_backed(); }
59 void reset() noexcept { file_.clear(); }
60 void open(const std::string& directory) {
61 if (opened()) return;
62 file_.set_directory(directory);
63 auxiliary("id table create", [&] { file_.open("pineforge-id-pairs"); });
64 }
65 void resize(std::uint64_t bytes) {
66 check_range(0, bytes);
67 if (!opened()) throw IdPairStoreError(EBADF, "id table resize");
68 // The production sizing caller owns a fresh replacement index.
69 // Monotone zero-filled growth also works without a sizing syscall.
70 auxiliary("id table resize", [&] { file_.ensure_size(bytes); });
71 }
72 void read(std::uint64_t offset, void* destination, std::size_t length) const {
73 check_range(offset, length);
74 auto* bytes = static_cast<unsigned char*>(destination);
75 std::size_t received = 0;
76 while (received < length) {
77 if (fixture_io_) ++fixture_io_->reads;
78 const ssize_t count = auxiliary("id table read", [&] {
79 return file_.read_some(offset + received, bytes + received, length - received);
80 });
81 if (count < 0 && errno == EINTR) continue;
82 if (count <= 0)
83 throw IdPairStoreError(count < 0 ? errno : EIO, "id table read");
84 if (fixture_io_) fixture_io_->read_bytes += static_cast<std::uint64_t>(count);
85 received += static_cast<std::size_t>(count);
86 }
87 }
88 void write(std::uint64_t offset, const void* source, std::size_t length) {
89 check_range(offset, length);
90 const auto* bytes = static_cast<const unsigned char*>(source);
91 std::size_t written = 0;
92 while (written < length) {
93 if (fixture_io_ && ++fixture_io_->writes == fixture_io_->fail_write) {
94 const auto prefix = std::min(fixture_io_->partial_write, length - written);
95 if (prefix != 0) {
96 const auto count = auxiliary("id table fixture short write", [&] {
97 return file_.write_some(offset + written, bytes + written, prefix);
98 });
99 if (count > 0) fixture_io_->write_bytes += static_cast<std::uint64_t>(count);
100 }
101 throw IdPairStoreError(EIO, "id table fixture short write");
102 }
103 const ssize_t count = auxiliary("id table write", [&] {
104 return file_.write_some(offset + written, bytes + written, length - written);
105 });
106 if (count < 0 && errno == EINTR) continue;
107 if (count <= 0)
108 throw IdPairStoreError(count < 0 ? errno : EIO, "id table write");
109 if (fixture_io_) fixture_io_->write_bytes += static_cast<std::uint64_t>(count);
110 written += static_cast<std::size_t>(count);
111 }
112 }
113
114private:
115 template<class Operation>
116 static auto auxiliary(const char* operation, Operation transfer) -> decltype(transfer()) {
117 try { return transfer(); }
118 catch (const AuxiliaryScratchIoError& error) {
119 throw IdPairStoreError(error.code().value(), scratch_with_detail(operation, error.what(), error.code().value()).c_str());
120 }
121 // Arena backing/capacity failures retain their report-scratch type
122 // and payload. Only auxiliary I/O becomes the historical ID error.
123 }
124 static void check_range(std::uint64_t offset, std::uint64_t length) {
125 constexpr auto maximum = static_cast<std::uint64_t>(std::numeric_limits<off_t>::max());
126 if (offset > maximum || length > maximum - offset)
127 throw IdPairStoreError(EFBIG, "id table offset exhausted");
128 }
129 ScratchFile file_{ScratchFile::IoRaiser::auxiliary};
130 FixtureIO* fixture_io_ = nullptr;
131};
132
133} // namespace pineforge::detail
RunFailureInfo(RunFailureCode code, const RunFailureArgs &args) noexcept
void fixture_count_io(FixtureIO *io) noexcept
FixtureIO * fixture_io() const noexcept
IdPairScratchFile & operator=(IdPairScratchFile &&other) noexcept
IdPairScratchFile & operator=(const IdPairScratchFile &)=delete
void write(std::uint64_t offset, const void *source, std::size_t length)
IdPairScratchFile(ScratchStore store=ScratchStore::unknown)
IdPairScratchFile(const IdPairScratchFile &)=delete
void set_store(ScratchStore store) noexcept
IdPairScratchFile(IdPairScratchFile &&other) noexcept
void open(const std::string &directory)
void read(std::uint64_t offset, void *destination, std::size_t length) const
IdPairStoreError(int error, const char *operation)
std::string scratch_with_detail(const char *prefix, const char *what, int errno_value)