Container System 0.1.0
High-performance C++20 type-safe container framework with SIMD-accelerated serialization
Loading...
Searching...
No Matches
epoch_manager.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#pragma once
6
7#include <atomic>
8#include <array>
9#include <vector>
10#include <functional>
11#include <mutex>
12
13namespace kcenon::container
14{
47 {
48 public:
52 static constexpr uint64_t INACTIVE = UINT64_MAX;
53
57 static constexpr size_t NUM_EPOCHS = 3;
58
65 return instance;
66 }
67
76 void enter_critical() noexcept {
77 thread_epoch() = global_epoch_.load(std::memory_order_acquire);
78 }
79
87 void exit_critical() noexcept {
89 }
90
95 [[nodiscard]] bool in_critical_section() const noexcept {
96 return thread_epoch() != INACTIVE;
97 }
98
110 void defer_delete(void* ptr, std::function<void(void*)> deleter) {
111 if (ptr == nullptr) {
112 return;
113 }
114
115 uint64_t epoch = global_epoch_.load(std::memory_order_relaxed);
116 size_t bucket = epoch % NUM_EPOCHS;
117
118 std::lock_guard<std::mutex> lock(retire_mutex_[bucket]);
119 retired_[bucket].emplace_back(ptr, std::move(deleter));
120 }
121
130 template<typename T>
131 void defer_delete(T* ptr) {
132 defer_delete(static_cast<void*>(ptr), [](void* p) {
133 delete static_cast<T*>(p);
134 });
135 }
136
145 size_t try_gc() {
146 // Advance epoch
147 uint64_t current = global_epoch_.fetch_add(1, std::memory_order_acq_rel);
148
149 // Calculate which epoch is safe to collect
150 // We need 2 epoch advances before an epoch is safe
151 if (current < 2) {
152 return 0;
153 }
154
155 uint64_t safe_epoch = current - 2;
156 size_t bucket = safe_epoch % NUM_EPOCHS;
157
158 // Check if any thread is still in the safe epoch
159 // For simplicity, we just try to collect
160 // In production, you'd check all registered threads
161
162 std::vector<std::pair<void*, std::function<void(void*)>>> to_delete;
163 {
164 std::lock_guard<std::mutex> lock(retire_mutex_[bucket]);
165 to_delete = std::move(retired_[bucket]);
166 retired_[bucket].clear();
167 }
168
169 // Actually delete the objects
170 for (auto& [ptr, deleter] : to_delete) {
171 deleter(ptr);
172 }
173
174 gc_count_.fetch_add(1, std::memory_order_relaxed);
175 reclaimed_count_.fetch_add(to_delete.size(), std::memory_order_relaxed);
176
177 return to_delete.size();
178 }
179
188 size_t force_gc() {
189 size_t total = 0;
190 for (size_t i = 0; i < NUM_EPOCHS; ++i) {
191 std::vector<std::pair<void*, std::function<void(void*)>>> to_delete;
192 {
193 std::lock_guard<std::mutex> lock(retire_mutex_[i]);
194 to_delete = std::move(retired_[i]);
195 retired_[i].clear();
196 }
197
198 for (auto& [ptr, deleter] : to_delete) {
199 deleter(ptr);
200 }
201 total += to_delete.size();
202 }
203
204 if (total > 0) {
205 gc_count_.fetch_add(1, std::memory_order_relaxed);
206 reclaimed_count_.fetch_add(total, std::memory_order_relaxed);
207 }
208
209 return total;
210 }
211
216 [[nodiscard]] uint64_t current_epoch() const noexcept {
217 return global_epoch_.load(std::memory_order_relaxed);
218 }
219
224 [[nodiscard]] size_t gc_count() const noexcept {
225 return gc_count_.load(std::memory_order_relaxed);
226 }
227
232 [[nodiscard]] size_t reclaimed_count() const noexcept {
233 return reclaimed_count_.load(std::memory_order_relaxed);
234 }
235
240 [[nodiscard]] size_t pending_count() const {
241 size_t total = 0;
242 for (size_t i = 0; i < NUM_EPOCHS; ++i) {
243 std::lock_guard<std::mutex> lock(retire_mutex_[i]);
244 total += retired_[i].size();
245 }
246 return total;
247 }
248
249 // Delete copy and move operations
250 epoch_manager(const epoch_manager&) = delete;
254
255 private:
259 epoch_manager() = default;
260
265 force_gc();
266 }
267
272 static std::atomic<uint64_t>& thread_epoch() {
273 thread_local std::atomic<uint64_t> epoch{INACTIVE};
274 return epoch;
275 }
276
277 std::atomic<uint64_t> global_epoch_{0};
278 std::atomic<size_t> gc_count_{0};
279 std::atomic<size_t> reclaimed_count_{0};
280
281 mutable std::array<std::mutex, NUM_EPOCHS> retire_mutex_;
282 std::array<std::vector<std::pair<void*, std::function<void(void*)>>>, NUM_EPOCHS> retired_;
283 };
284
299 {
300 public:
307
314
315 // Non-copyable, non-movable
316 epoch_guard(const epoch_guard&) = delete;
320 };
321
322} // namespace kcenon::container
RAII guard for epoch critical section.
~epoch_guard() noexcept
Exit critical section.
epoch_guard & operator=(epoch_guard &&)=delete
epoch_guard(epoch_guard &&)=delete
epoch_guard(const epoch_guard &)=delete
epoch_guard & operator=(const epoch_guard &)=delete
epoch_guard() noexcept
Enter critical section.
Epoch-based memory reclamation for lock-free data structures.
void defer_delete(T *ptr)
Defer deletion of a typed pointer.
std::array< std::vector< std::pair< void *, std::function< void(void *)> > >, NUM_EPOCHS > retired_
std::array< std::mutex, NUM_EPOCHS > retire_mutex_
size_t reclaimed_count() const noexcept
Get the total number of objects reclaimed.
void exit_critical() noexcept
Exit critical section.
std::atomic< uint64_t > global_epoch_
static std::atomic< uint64_t > & thread_epoch()
Get thread-local epoch reference.
size_t pending_count() const
Get the number of objects pending deletion.
static constexpr uint64_t INACTIVE
Sentinel value indicating thread is not in critical section.
size_t try_gc()
Attempt garbage collection.
std::atomic< size_t > reclaimed_count_
static constexpr size_t NUM_EPOCHS
Number of epochs in rotation (must be at least 3 for safety)
std::atomic< size_t > gc_count_
static epoch_manager & instance()
Get the singleton instance.
uint64_t current_epoch() const noexcept
Get the current global epoch.
void defer_delete(void *ptr, std::function< void(void *)> deleter)
Defer deletion of a pointer.
epoch_manager(epoch_manager &&)=delete
bool in_critical_section() const noexcept
Check if current thread is in critical section.
size_t gc_count() const noexcept
Get the number of GC cycles performed.
~epoch_manager()
Destructor - clean up any remaining deferred deletions.
epoch_manager()=default
Private constructor for singleton.
epoch_manager(const epoch_manager &)=delete
epoch_manager & operator=(epoch_manager &&)=delete
epoch_manager & operator=(const epoch_manager &)=delete
size_t force_gc()
Force garbage collection of all epochs.
void enter_critical() noexcept
Enter critical section (pin to current epoch)