Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_paged_tree_durability.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
41# ifndef TPL_PAGED_TREE_DURABILITY_H
42# define TPL_PAGED_TREE_DURABILITY_H
43
44# include <algorithm>
45# include <array>
46# include <cerrno>
47# include <cstdint>
48# include <cstdio>
49# include <cstdlib>
50# include <filesystem>
51# include <fstream>
52# include <mutex>
53# include <string>
54# include <type_traits>
55# include <vector>
56
57# include <ah-errors.H>
58
59# if defined(__unix__) || defined(__APPLE__)
60# include <fcntl.h>
61# include <signal.h>
62# include <sys/stat.h>
63# include <sys/types.h>
64# include <unistd.h>
65# endif
66
67namespace Aleph
68{
75 enum class Paged_File_Open_Mode : std::uint8_t
76 {
79 };
80
81 namespace detail
82 {
83 enum class Pid_File_Lock_Mode : std::uint8_t
84 {
85 Shared,
87 };
88
89 inline void sync_parent_directory(const std::string & path,
90 const char * kind)
91 {
92# if defined(__unix__) || defined(__APPLE__)
93 namespace fs = std::filesystem;
94 const fs::path parent = fs::path(path).has_parent_path()
95 ? fs::path(path).parent_path()
96 : fs::current_path();
97
98 const int fd = ::open(parent.c_str(), O_RDONLY | O_DIRECTORY);
100 << kind << ": cannot open parent directory " << parent.string()
101 << " for sync";
102
103 const int status = ::fsync(fd);
104 const int close_status = ::close(fd);
105 ah_runtime_error_unless(status == 0)
106 << kind << ": cannot fsync parent directory " << parent.string();
108 << kind << ": cannot close parent directory " << parent.string();
109# else
110 (void) path;
111 (void) kind;
112# endif
113 }
114
115 inline void sync_file_by_path(const std::string & path, const char * kind)
116 {
117# if defined(__unix__) || defined(__APPLE__)
118 const int fd = ::open(path.c_str(), O_RDONLY);
120 << kind << ": cannot open " << path << " for fsync";
121
122 const int status = ::fsync(fd);
123 const int close_status = ::close(fd);
124 ah_runtime_error_unless(status == 0)
125 << kind << ": cannot fsync " << path;
127 << kind << ": cannot close " << path;
128# else
129 (void) path;
130 (void) kind;
131# endif
132 }
133
134 inline void sync_file_descriptor(const int fd,
135 const std::string & path,
136 const char * kind)
137 {
138# if defined(__unix__) || defined(__APPLE__)
139 const int status = ::fsync(fd);
140 ah_runtime_error_unless(status == 0)
141 << kind << ": cannot fsync " << path;
142# else
143 (void) fd;
144 (void) path;
145 (void) kind;
146# endif
147 }
148
149 inline void remove_file_if_exists(const std::string & path,
150 const char * kind)
151 {
152 namespace fs = std::filesystem;
153 std::error_code ec;
154 const bool removed = fs::remove(path, ec);
156 << kind << ": cannot remove " << path << " (" << ec.message() << ")";
157 if (removed)
158 sync_parent_directory(path, kind);
159 }
160
161 inline void rename_file_atomic(const std::string & src,
162 const std::string & dst,
163 const char * kind)
164 {
165 namespace fs = std::filesystem;
166 std::error_code ec;
167 fs::rename(src, dst, ec);
169 << kind << ": cannot rename " << src << " to " << dst
170 << " (" << ec.message() << ")";
172 }
173
174 inline void copy_file_contents(const std::string & src,
175 const std::string & dst,
176 const char * kind)
177 {
178 std::ifstream in(src, std::ios::binary);
180 << kind << ": cannot open " << src << " for reading";
181
182 std::ofstream out(dst, std::ios::binary | std::ios::trunc);
184 << kind << ": cannot open " << dst << " for writing";
185
186 std::array<char, 8192> buffer = {};
187 while (in.good())
188 {
189 in.read(buffer.data(), buffer.size());
190 const auto count = in.gcount();
191 if (count > 0)
192 out.write(buffer.data(), count);
193 }
194
195 ah_runtime_error_unless(in.eof() and not in.bad())
196 << kind << ": cannot read " << src;
197
198 out.flush();
200 << kind << ": cannot flush " << dst;
201
202 out.close();
204 << kind << ": cannot close " << dst;
205
206 sync_file_by_path(dst, kind);
208 }
209
210 [[nodiscard]] inline std::uint32_t crc32_begin() noexcept
211 {
212 return 0xFFFFFFFFu;
213 }
214
215 [[nodiscard]] inline std::uint32_t crc32_add_bytes(std::uint32_t crc,
216 const void * data,
217 const size_t size) noexcept
218 {
219 const auto * ptr = static_cast<const unsigned char *>(data);
220 for (size_t i = 0; i < size; ++i)
221 {
222 crc ^= ptr[i];
223 for (int bit = 0; bit < 8; ++bit)
224 crc = (crc >> 1) ^ (0xEDB88320u & static_cast<std::uint32_t>(-static_cast<int>(crc & 1u)));
225 }
226 return crc;
227 }
228
229 template <typename T>
230 [[nodiscard]] inline std::uint32_t crc32_add(std::uint32_t crc,
231 const T & value) noexcept
232 {
233 static_assert(std::is_trivially_copyable_v<T>,
234 "crc32_add requires a trivially copyable value");
235 return crc32_add_bytes(crc, &value, sizeof(T));
236 }
237
238 [[nodiscard]] inline std::uint32_t crc32_finish(const std::uint32_t crc) noexcept
239 {
240 return ~crc;
241 }
242
243 [[nodiscard]] inline long long current_process_id() noexcept
244 {
245# if defined(__unix__) || defined(__APPLE__)
246 return static_cast<long long>(::getpid());
247# else
248 return 0;
249# endif
250 }
251
252 [[nodiscard]] inline bool process_is_alive(const long long pid) noexcept
253 {
254# if defined(__unix__) || defined(__APPLE__)
255 if (pid <= 0)
256 return false;
257
258 if (::kill(static_cast<pid_t>(pid), 0) == 0)
259 return true;
260
261 return errno == EPERM;
262# else
263 (void) pid;
264 return false;
265# endif
266 }
267
269 {
271 {
272 std::string path;
273 size_t shared_count = 0;
274 bool exclusive = false;
275 };
276
277 std::string path_;
278 bool acquired_ = false;
280# if defined(__unix__) || defined(__APPLE__)
281 int fd_ = -1;
282# endif
283
284 [[nodiscard]] static std::mutex & registry_mutex()
285 {
286 static std::mutex mutex;
287 return mutex;
288 }
289
290 [[nodiscard]] static std::vector<Local_Lock_State> & registry()
291 {
292 static std::vector<Local_Lock_State> states;
293 return states;
294 }
295
296 static void register_path(const std::string & path,
297 const Pid_File_Lock_Mode mode,
298 const char * kind)
299 {
300 std::lock_guard<std::mutex> guard(registry_mutex());
301 auto & states = registry();
302 const auto it = std::find_if(states.begin(), states.end(),
303 [&path] (const Local_Lock_State & state)
304 {
305 return state.path == path;
306 });
307
308 if (it == states.end())
309 {
310 Local_Lock_State state;
311 state.path = path;
312 state.shared_count = mode == Pid_File_Lock_Mode::Shared ? 1u : 0u;
314 states.push_back(std::move(state));
315 return;
316 }
317
319 {
320 ah_runtime_error_unless(not it->exclusive)
321 << kind << ": this process already holds an exclusive lock "
322 << path;
323 ++it->shared_count;
324 return;
325 }
326
328 << kind << ": this process already holds the lock " << path;
329 }
330
331 static void unregister_path(const std::string & path,
332 const Pid_File_Lock_Mode mode) noexcept
333 {
334 std::lock_guard<std::mutex> guard(registry_mutex());
335 auto & states = registry();
336 const auto it = std::find_if(states.begin(), states.end(),
337 [&path] (const Local_Lock_State & state)
338 {
339 return state.path == path;
340 });
341 if (it == states.end())
342 return;
343
345 {
346 if (it->shared_count > 0)
347 --it->shared_count;
348 }
349 else
350 it->exclusive = false;
351
352 if (it->shared_count == 0 and not it->exclusive)
353 states.erase(it);
354 }
355
356 [[nodiscard]] static bool stale_lock(const std::string & path)
357 {
358 std::ifstream in(path);
359 if (not in.good())
360 return true;
361
362 long long pid = 0;
363 in >> pid;
364 if (not in.good())
365 return true;
366
367 return not process_is_alive(pid);
368 }
369
370# if defined(__unix__) || defined(__APPLE__)
371 static void acquire_fd_lock(const int fd,
372 const Pid_File_Lock_Mode mode,
373 const std::string & path,
374 const char * kind)
375 {
376 struct flock lock = {};
377 lock.l_type = mode == Pid_File_Lock_Mode::Shared ? F_RDLCK : F_WRLCK;
378 lock.l_whence = SEEK_SET;
379 lock.l_start = 0;
380 lock.l_len = 0;
381
382 if (::fcntl(fd, F_SETLK, &lock) == 0)
383 return;
384
386 << kind << ": cannot acquire advisory lock " << path;
388 << kind << ": another process already holds the lock " << path;
389 }
390
391 static void write_pid_marker(const int fd,
392 const std::string & path,
393 const bool created,
394 const char * kind)
395 {
396 const std::string text = std::to_string(current_process_id()) + "\n";
398 << kind << ": cannot truncate pid lock " << path;
400 << kind << ": cannot seek pid lock " << path;
401
402 const ssize_t written = ::write(fd, text.data(), text.size());
403 ah_runtime_error_unless(written == static_cast<ssize_t>(text.size()))
404 << kind << ": cannot write pid lock " << path;
405
406 sync_file_descriptor(fd, path, kind);
407 if (created)
408 sync_parent_directory(path, kind);
409 }
410# endif
411
412 public:
413 Pid_File_Lock() = default;
414
415 Pid_File_Lock(const Pid_File_Lock &) = delete;
419
421 {
422 release();
423 }
424
425 void acquire(const std::string & path,
426 const char * kind,
428 {
429 release();
430 path_ = path;
431 mode_ = mode;
432 register_path(path_, mode_, kind);
433
434 namespace fs = std::filesystem;
435 if (fs::path(path_).has_parent_path())
436 {
437 std::error_code ec;
438 (void) fs::create_directories(fs::path(path_).parent_path(), ec);
439 }
440
441# if defined(__unix__) || defined(__APPLE__)
442 std::error_code exists_ec;
443 const bool existed = fs::exists(path_, exists_ec);
445 << kind << ": cannot query pid lock " << path_ << " ("
446 << exists_ec.message() << ")";
447
448 fd_ = ::open(path_.c_str(), O_RDWR | O_CREAT, 0644);
449 if (fd_ < 0)
450 {
451 const std::string lock_path = path_;
453 path_.clear();
455 << kind << ": cannot open pid lock " << lock_path;
456 }
457
458 try
459 {
462 acquired_ = true;
463 return;
464 }
465 catch (...)
466 {
467 const int close_status = ::close(fd_);
469 fd_ = -1;
471 path_.clear();
472 throw;
473 }
474# else
475 while (true)
476 {
477 std::ofstream out(path_, std::ios::out | std::ios::trunc);
478 if (out.good())
479 {
480 out << current_process_id() << "\n";
481 out.close();
482 acquired_ = true;
483 return;
484 }
485
487 << kind << ": another process already holds the lock " << path_;
488
490 }
491# endif
492 }
493
495 {
496 if (not acquired_ and path_.empty())
497 return;
498
499# if defined(__unix__) || defined(__APPLE__)
500 if (fd_ >= 0)
501 {
502 const int close_status = ::close(fd_);
504 fd_ = -1;
505 }
506# else
507 std::error_code ec;
508 std::filesystem::remove(path_, ec);
509 if (not ec)
510 {
511 try
512 {
513 sync_parent_directory(path_, "Pid_File_Lock");
514 }
515 catch (...)
516 {
517 }
518 }
519# endif
521 acquired_ = false;
523 path_.clear();
524 }
525 };
526 } // namespace detail
527} // namespace Aleph
528
529# endif // TPL_PAGED_TREE_DURABILITY_H
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
Definition ah-errors.H:250
#define ah_runtime_error()
Throws std::runtime_error unconditionally.
Definition ah-errors.H:282
Pid_File_Lock(Pid_File_Lock &&)=delete
static void register_path(const std::string &path, const Pid_File_Lock_Mode mode, const char *kind)
Pid_File_Lock(const Pid_File_Lock &)=delete
static bool stale_lock(const std::string &path)
Pid_File_Lock & operator=(const Pid_File_Lock &)=delete
static void unregister_path(const std::string &path, const Pid_File_Lock_Mode mode) noexcept
void acquire(const std::string &path, const char *kind, const Pid_File_Lock_Mode mode=Pid_File_Lock_Mode::Exclusive)
static std::vector< Local_Lock_State > & registry()
std::uint32_t crc32_add_bytes(std::uint32_t crc, const void *data, const size_t size) noexcept
void sync_parent_directory(const std::string &path, const char *kind)
long long current_process_id() noexcept
bool process_is_alive(const long long pid) noexcept
void copy_file_contents(const std::string &src, const std::string &dst, const char *kind)
void remove_file_if_exists(const std::string &path, const char *kind)
std::uint32_t crc32_begin() noexcept
std::uint32_t crc32_add(std::uint32_t crc, const T &value) noexcept
void rename_file_atomic(const std::string &src, const std::string &dst, const char *kind)
void sync_file_descriptor(const int fd, const std::string &path, const char *kind)
std::uint32_t crc32_finish(const std::uint32_t crc) noexcept
void sync_file_by_path(const std::string &path, const char *kind)
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
Paged_File_Open_Mode
Open mode for paged persistent tree files.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
auto mode(const Container &data) -> std::decay_t< decltype(*std::begin(data))>
Compute the mode (most frequent value).
Definition stat_utils.H:456
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127