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 void reset() noexcept { file_.clear(); }
59 void open(const std::string& directory) {
60 if (opened()) return;
61 file_.set_directory(directory);
62 auxiliary("id table create", [&] { file_.open("pineforge-id-pairs"); });
63 }
64 void resize(std::uint64_t bytes) {
65 check_range(0, bytes);
66 if (!opened()) throw IdPairStoreError(EBADF, "id table resize");
67 // The production sizing caller owns a fresh replacement index.
68 // Monotone zero-filled growth also works without a sizing syscall.
69 auxiliary("id table resize", [&] { file_.ensure_size(bytes); });
70 }
71 void read(std::uint64_t offset, void* destination, std::size_t length) const {
72 check_range(offset, length);
73 auto* bytes = static_cast<unsigned char*>(destination);
74 std::size_t received = 0;
75 while (received < length) {
76 if (fixture_io_) ++fixture_io_->reads;
77 const ssize_t count = auxiliary("id table read", [&] {
78 return file_.read_some(offset + received, bytes + received, length - received);
79 });
80 if (count < 0 && errno == EINTR) continue;
81 if (count <= 0)
82 throw IdPairStoreError(count < 0 ? errno : EIO, "id table read");
83 if (fixture_io_) fixture_io_->read_bytes += static_cast<std::uint64_t>(count);
84 received += static_cast<std::size_t>(count);
85 }
86 }
87 void write(std::uint64_t offset, const void* source, std::size_t length) {
88 check_range(offset, length);
89 const auto* bytes = static_cast<const unsigned char*>(source);
90 std::size_t written = 0;
91 while (written < length) {
92 if (fixture_io_ && ++fixture_io_->writes == fixture_io_->fail_write) {
93 const auto prefix = std::min(fixture_io_->partial_write, length - written);
94 if (prefix != 0) {
95 const auto count = auxiliary("id table fixture short write", [&] {
96 return file_.write_some(offset + written, bytes + written, prefix);
97 });
98 if (count > 0) fixture_io_->write_bytes += static_cast<std::uint64_t>(count);
99 }
100 throw IdPairStoreError(EIO, "id table fixture short write");
101 }
102 const ssize_t count = auxiliary("id table write", [&] {
103 return file_.write_some(offset + written, bytes + written, length - written);
104 });
105 if (count < 0 && errno == EINTR) continue;
106 if (count <= 0)
107 throw IdPairStoreError(count < 0 ? errno : EIO, "id table write");
108 if (fixture_io_) fixture_io_->write_bytes += static_cast<std::uint64_t>(count);
109 written += static_cast<std::size_t>(count);
110 }
111 }
112
113private:
114 template<class Operation>
115 static auto auxiliary(const char* operation, Operation transfer) -> decltype(transfer()) {
116 try { return transfer(); }
117 catch (const AuxiliaryScratchIoError& error) {
118 throw IdPairStoreError(error.code().value(), scratch_with_detail(operation, error.what(), error.code().value()).c_str());
119 }
120 // Arena backing/capacity failures retain their report-scratch type
121 // and payload. Only auxiliary I/O becomes the historical ID error.
122 }
123 static void check_range(std::uint64_t offset, std::uint64_t length) {
124 constexpr auto maximum = static_cast<std::uint64_t>(std::numeric_limits<off_t>::max());
125 if (offset > maximum || length > maximum - offset)
126 throw IdPairStoreError(EFBIG, "id table offset exhausted");
127 }
128 ScratchFile file_{ScratchFile::IoRaiser::auxiliary};
129 FixtureIO* fixture_io_ = nullptr;
130};
131
132} // 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)