← Documentation
ADS-IDAC Engine 5.0.0
DETCheckpoint.h
Go to the documentation of this file.
1#pragma once
2
3#include <string>
4#include <map>
5#include <vector>
6#include <memory>
7#include <fstream>
8#include <sstream>
9#include <iostream>
10#include <filesystem>
11#include <cstdlib>
12
13#include "RestartNodeInfo.h"
14#include "../Branch/BranchRecord.h"
15#include "../ParallelExec/ParallelGlobal.h"
16
17#include <boost/archive/binary_oarchive.hpp>
18#include <boost/archive/binary_iarchive.hpp>
19#include <boost/serialization/map.hpp>
20#include <boost/serialization/vector.hpp>
21#include <boost/serialization/string.hpp>
22#include <boost/serialization/shared_ptr.hpp>
23
26 int cid = 0;
27 int branch_index = 0;
28 int sequence_id = 0;
30
31 template<class Archive>
32 void serialize(Archive& ar, unsigned int) {
34 }
35};
36
48 static constexpr int VERSION = 9;
57 uint64_t config_fingerprint = 0;
61 bool compress_queue = false;
67 bool blobs_compressed = false;
68 // RELAP restart blobs are stored ONCE per source and referenced per CID.
69 // The MPI writer moves each blob out of its RestartNodeInfo; the serial
70 // writer copies it from g_sim.restart_buffers or the backup file.
71 std::map<int, std::vector<char>> source_blobs;
72 std::map<int, int> cid_source;
76 int max_branch_id = -1;
77
80 std::string branch_center_blob;
81
82 // Crew snapshots are archived once per UNIQUE RestartNodeInfo as an LZ4
83 // blob, with index maps that rebuild restart_states and
84 // task_restart_states at load. The two containers alias the same
85 // shared_ptrs (carried-task registration shares them), so save() dedups by
86 // pointer and load() restores the aliasing. These three members are
87 // populated only inside save() and load().
88 std::vector<std::string> packed_rinfos;
89 std::map<int, int> restart_state_pack_ref;
90 std::vector<int> task_state_pack_ref;
91
92 // CID-keyed maps
93 std::map<int, std::shared_ptr<RestartNodeInfo>> restart_states;
94 std::map<int, std::string> branch_record_blobs;
95
96 // Pending tasks with per-task data (parallel vectors, same index)
97 std::vector<PendingBranch> pending_tasks;
98 std::vector<std::shared_ptr<RestartNodeInfo>> task_restart_states;
99 std::vector<std::string> task_branch_blobs;
100
101 std::vector<SequenceReport> completed_reports;
102
112 template<class Archive>
113 void serialize(Archive& ar, unsigned int) {
114 ar & version;
116 ar & sequence_counter;
117 if (version >= 3) {
118 ar & compress_queue;
119 }
120 if (version >= 4) {
121 ar & source_blobs;
122 ar & cid_source;
123 ar & max_branch_id;
124 }
125 if (version >= 5) {
126 ar & blobs_compressed;
127 }
128 if (version >= 6) {
130 }
131 if (version >= 7) {
132 ar & packed_rinfos;
135 }
137 if (version < 7) {
138 ar & restart_states;
139 }
141 ar & pending_tasks;
142 if (version < 7) {
144 }
147 }
148
152 std::ostringstream oss(std::ios::binary);
153 boost::archive::binary_oarchive oa(oss);
154 oa << br;
155 branch_center_blob = oss.str();
156 }
157
162 std::istringstream iss(branch_center_blob, std::ios::binary);
163 boost::archive::binary_iarchive ia(iss);
164 ia >> br;
165 }
166
170 static bool version_supported(int v) { return v >= 9; }
171
177 bool config_matches(uint64_t current) const {
178 return version < 6 || config_fingerprint == 0 || current == 0
179 || config_fingerprint == current;
180 }
181
183 static std::string lz4_pack_string(const std::string& s) {
184 auto v = lz4_compress(s.data(), static_cast<int>(s.size()));
185 return std::string(v.data(), v.size());
186 }
189 static std::string lz4_unpack_string(const std::string& s) {
190 auto v = lz4_decompress(s.data(), static_cast<int>(s.size()));
191 return std::string(v.data(), v.size());
192 }
193
195 static std::string pack_rinfo(const RestartNodeInfo& ri) {
196 std::ostringstream oss(std::ios::binary);
197 {
198 boost::archive::binary_oarchive oa(oss);
199 oa << ri;
200 }
201 return lz4_pack_string(oss.str());
202 }
206 static std::shared_ptr<RestartNodeInfo> unpack_rinfo(const std::string& blob) {
207 auto ri = std::make_shared<RestartNodeInfo>();
208 const std::string raw = lz4_unpack_string(blob);
209 std::istringstream iss(raw, std::ios::binary);
210 boost::archive::binary_iarchive ia(iss);
211 ia >> *ri;
212 return ri;
213 }
214
227 void save(const std::string& path) {
228 // Pack each UNIQUE crew snapshot once, by pointer. The raw maps stay
229 // populated for the caller, but the archive carries only the packed
230 // blobs plus index maps (serialize() skips the raw maps).
231 {
232 packed_rinfos.clear();
234 task_state_pack_ref.clear();
235 std::map<const RestartNodeInfo*, int> idx;
236 auto pack_of = [&](const std::shared_ptr<RestartNodeInfo>& ri) -> int {
237 if (!ri) return -1;
238 auto it = idx.find(ri.get());
239 if (it != idx.end()) return it->second;
240 int i = static_cast<int>(packed_rinfos.size());
241 packed_rinfos.push_back(pack_rinfo(*ri));
242 idx.emplace(ri.get(), i);
243 return i;
244 };
245 for (auto& [cid, ri] : restart_states)
246 restart_state_pack_ref[cid] = pack_of(ri);
248 for (auto& ri : task_restart_states)
249 task_state_pack_ref.push_back(pack_of(ri));
250 }
251 // Compress blob payloads in place before archiving. Callers build the
252 // object fresh for each save and discard it afterwards, so mutating
253 // it here is safe. Queue-compressed sources are left to that layer.
254 if (!blobs_compressed) {
255 if (!compress_queue) {
256 for (auto& [key, blob] : source_blobs) {
257 if (blob.empty()) continue;
258 auto c = lz4_compress(blob.data(), static_cast<int>(blob.size()));
259 blob = std::move(c);
260 }
261 }
262 for (auto& [key, blob] : branch_record_blobs)
263 if (!blob.empty()) blob = lz4_pack_string(blob);
264 if (!branch_center_blob.empty())
266 blobs_compressed = true;
267 }
268 // Write to a temp file and rename into place, so a kill mid-save never
269 // leaves a truncated checkpoint at the canonical path.
270 const std::string tmp_path = path + ".tmp";
271 {
272 std::ofstream ofs(tmp_path, std::ios::binary);
273 if (!ofs) {
274 std::cerr << "ERROR: cannot open checkpoint file for writing: " << tmp_path << std::endl;
275 return;
276 }
277 boost::archive::binary_oarchive oa(ofs);
278 oa << *this;
279 ofs.flush();
280 if (!ofs.good()) {
281 std::cerr << "ERROR: checkpoint write failed (disk full?): " << tmp_path << std::endl;
282 ofs.close();
283 std::error_code ec;
284 std::filesystem::remove(tmp_path, ec);
285 return;
286 }
287 }
288 std::error_code ec;
289 std::filesystem::rename(tmp_path, path, ec);
290 if (ec) {
291 std::cerr << "ERROR: cannot move checkpoint into place: " << ec.message() << std::endl;
292 return;
293 }
294 std::cout << "[CHECKPOINT] Saved: " << path
295 << " (" << sequences_completed << " sequences, "
296 << pending_tasks.size() << " pending)" << std::endl;
297 }
298
312 static DETCheckpoint load(const std::string& path) {
313 DETCheckpoint ckpt;
314 std::ifstream ifs(path, std::ios::binary);
315 if (!ifs) {
316 // A default-constructed checkpoint would make a mistyped --resume
317 // path finish at once with exit 0, so exit with an error instead.
318 std::cerr << "FATAL: cannot open checkpoint file for reading: " << path << std::endl;
319 std::exit(1);
320 }
321 try {
322 boost::archive::binary_iarchive ia(ifs);
323 ia >> ckpt;
324 } catch (const std::exception& ex) {
325 std::cerr << "FATAL: checkpoint file is corrupt or truncated: " << path
326 << " (" << ex.what() << ")" << std::endl;
327 std::exit(1);
328 }
329 const int stored_version = ckpt.version;
330 if (!version_supported(stored_version)) {
331 std::cerr << "FATAL: checkpoint version " << stored_version
332 << " is older than the format this binary reads (version "
333 << VERSION << ") and cannot\n"
334 << " be read by this binary. Finish that run with the binary that\n"
335 << " wrote it, or start a fresh run." << std::endl;
336 std::exit(1);
337 }
338 // Rebuild the rinfo maps from the packed uniques. The indexes restore
339 // the shared_ptr aliasing between restart_states and
340 // task_restart_states. The guard tests the STORED version and is
341 // always true here, because version_supported() has already passed.
342 if (stored_version >= 7) {
343 std::vector<std::shared_ptr<RestartNodeInfo>> uniques;
344 uniques.reserve(ckpt.packed_rinfos.size());
345 for (auto& blob : ckpt.packed_rinfos)
346 uniques.push_back(unpack_rinfo(blob));
347 for (auto& [cid, ref] : ckpt.restart_state_pack_ref)
348 ckpt.restart_states[cid] =
349 (ref >= 0 && ref < (int)uniques.size()) ? uniques[ref] : nullptr;
350 ckpt.task_restart_states.clear();
351 ckpt.task_restart_states.reserve(ckpt.task_state_pack_ref.size());
352 for (int ref : ckpt.task_state_pack_ref)
353 ckpt.task_restart_states.push_back(
354 (ref >= 0 && ref < (int)uniques.size()) ? uniques[ref] : nullptr);
355 ckpt.packed_rinfos.clear();
356 ckpt.packed_rinfos.shrink_to_fit();
357 ckpt.restart_state_pack_ref.clear();
358 ckpt.task_state_pack_ref.clear();
359 }
360 // Strip the at-rest compression layer so every consumer sees the
361 // uncompressed payloads. Queue-layer compression stays intact: it is
362 // owned by compress_queue and stripped at restore time.
363 if (ckpt.blobs_compressed) {
364 if (!ckpt.compress_queue) {
365 for (auto& [key, blob] : ckpt.source_blobs) {
366 if (blob.empty()) continue;
367 auto d = lz4_decompress(blob.data(), static_cast<int>(blob.size()));
368 blob = std::move(d);
369 }
370 }
371 for (auto& [key, blob] : ckpt.branch_record_blobs)
372 if (!blob.empty()) blob = lz4_unpack_string(blob);
373 if (!ckpt.branch_center_blob.empty())
375 ckpt.blobs_compressed = false;
376 }
377 return ckpt;
378 }
379};
std::vector< char > lz4_compress(const char *data, int size)
LZ4-compress a buffer with a 4-byte original-size header.
std::vector< char > lz4_decompress(const char *data, int size)
LZ4-decompress a buffer that has a 4-byte original-size header.
Checkpoint/restart state snapshot — captures entire C++ simulation state at a branching point.
Complete state of a dynamic event tree run at a sequence boundary.
Definition: DETCheckpoint.h:45
static std::string pack_rinfo(const RestartNodeInfo &ri)
Serialize one snapshot (Boost binary archive) and LZ4-compress it.
std::vector< int > task_state_pack_ref
task position -> packed index (-1 = null)
Definition: DETCheckpoint.h:90
static std::string lz4_unpack_string(const std::string &s)
Inverse of lz4_pack_string(). Exits the process (lz4_decompress) if s does not decompress to its reco...
std::map< int, std::string > branch_record_blobs
cid -> serialized BranchRecord of the holding client; filled by the MPI writer only and used there to...
Definition: DETCheckpoint.h:94
std::map< int, std::vector< char > > source_blobs
source key -> restart blob
Definition: DETCheckpoint.h:71
static std::shared_ptr< RestartNodeInfo > unpack_rinfo(const std::string &blob)
Inverse of pack_rinfo(). Deserialization pre-seeds the snapshot from the process-local shared masters...
bool config_matches(uint64_t current) const
Does this checkpoint's configuration digest match current, the digest of the running process (g_sim....
std::vector< SequenceReport > completed_reports
Reports of finished sequences (filled only when the visualization server is on)
static bool version_supported(int v)
Whether load() accepts a file with stored version v.
void serialize(Archive &ar, unsigned int)
Boost serialization of the archive layout.
static std::string lz4_pack_string(const std::string &s)
LZ4-compress s with the 4-byte size header of lz4_compress().
int sequences_completed
Sequences finished when the checkpoint was taken.
Definition: DETCheckpoint.h:50
std::string branch_center_blob
Definition: DETCheckpoint.h:80
uint64_t config_fingerprint
Definition: DETCheckpoint.h:57
void save(const std::string &path)
Write the checkpoint to path.
bool blobs_compressed
Definition: DETCheckpoint.h:67
std::vector< PendingBranch > pending_tasks
Unexplored branches.
Definition: DETCheckpoint.h:97
static constexpr int VERSION
Definition: DETCheckpoint.h:48
std::vector< std::string > packed_rinfos
lz4(serialized rinfo), one per unique snapshot
Definition: DETCheckpoint.h:88
std::map< int, std::shared_ptr< RestartNodeInfo > > restart_states
cid -> snapshot; read by serial resume
Definition: DETCheckpoint.h:93
void capture_branch_center(const BranchRecord &br)
Serialize br (Boost binary archive) into branch_center_blob, replacing its previous content.
void restore_branch_center(BranchRecord &br) const
Deserialize branch_center_blob into br. The blob must be uncompressed, as load() leaves it; a Boost a...
static DETCheckpoint load(const std::string &path)
Read a checkpoint file.
std::map< int, int > cid_source
Definition: DETCheckpoint.h:72
int version
Format version stored in the archive.
Definition: DETCheckpoint.h:49
std::vector< std::shared_ptr< RestartNodeInfo > > task_restart_states
Snapshot per pending task (null = none); read by MPI resume.
Definition: DETCheckpoint.h:98
std::map< int, int > restart_state_pack_ref
cid -> packed index (-1 = null entry)
Definition: DETCheckpoint.h:89
std::vector< std::string > task_branch_blobs
Always empty; resume dispatches branch_center_blob.
Definition: DETCheckpoint.h:99
One unexplored branch recorded in a checkpoint.
Definition: DETCheckpoint.h:25
void serialize(Archive &ar, unsigned int)
Definition: DETCheckpoint.h:32
int cid
Branch cluster ID the branch belongs to.
Definition: DETCheckpoint.h:26
int sequence_id
Global sequence ID already assigned by the MPI server; 0 = assign at dispatch (serial checkpoints)
Definition: DETCheckpoint.h:28
int parent_sequence_id
Sequence that generated the branch point (-1 = unknown)
Definition: DETCheckpoint.h:29
int branch_index
Index of the alternative within the cluster (1..N-1)
Definition: DETCheckpoint.h:27