Cppgres
Build Postgres extensions in C++
Loading...
Searching...
No Matches
memory.hpp
Go to the documentation of this file.
1
4#pragma once
5
6#include <concepts>
7#include <memory>
8#include <type_traits>
9
10#include "guard.hpp"
11#include "imports.h"
12
13namespace cppgres {
14
16 virtual ~abstract_memory_context() = default;
17
18 template <typename T = std::byte> T *alloc(size_t n = 1) {
19 if constexpr (alignof(T) > MAXIMUM_ALIGNOF) {
20#if PG_VERSION_NUM >= 160000
21 return static_cast<T *>(
22 ffi_guard{::MemoryContextAllocAligned}(_memory_context(), sizeof(T) * n, alignof(T), 0));
23#else
24 static_assert(alignof(T) <= MAXIMUM_ALIGNOF,
25 "types over-aligned beyond MAXIMUM_ALIGNOF require PostgreSQL 16 or later");
26#endif
27 } else {
28 return static_cast<T *>(ffi_guard{::MemoryContextAlloc}(_memory_context(), sizeof(T) * n));
29 }
30 }
31 template <typename T = void> void free(T *ptr) { ffi_guard{::pfree}(ptr); }
32
33 void reset() { ffi_guard{::MemoryContextReset}(_memory_context()); }
34
35 bool operator==(abstract_memory_context &c) noexcept {
36 return _memory_context() == c._memory_context();
37 }
38 bool operator!=(abstract_memory_context &c) noexcept {
39 return _memory_context() != c._memory_context();
40 }
41
42 operator ::MemoryContext() { return _memory_context(); }
43
44 ::MemoryContextCallback *register_reset_callback(::MemoryContextCallbackFunction func,
45 void *arg) {
46 auto cb = alloc<::MemoryContextCallback>();
47 cb->func = func;
48 cb->arg = arg;
49 ffi_guard{::MemoryContextRegisterResetCallback}(_memory_context(), cb);
50 return cb;
51 }
52
60 template <typename T, typename... Args> T *construct(Args &&...args) {
61 static_assert(std::is_nothrow_destructible_v<T>,
62 "type constructed in a memory context must be nothrow-destructible: its "
63 "destructor runs from a memory context reset callback where exceptions cannot "
64 "propagate");
65 auto *ptr = alloc<T>();
66 if constexpr (!std::is_trivially_destructible_v<T>) {
67 auto *cb = alloc<::MemoryContextCallback>();
68 std::construct_at(ptr, std::forward<Args>(args)...);
69 cb->func = [](void *arg) { std::destroy_at(static_cast<T *>(arg)); };
70 cb->arg = ptr;
71 ffi_guard{::MemoryContextRegisterResetCallback}(_memory_context(), cb);
72 } else {
73 std::construct_at(ptr, std::forward<Args>(args)...);
74 }
75 return ptr;
76 }
77
78 void delete_context() { ffi_guard{::MemoryContextDelete}(_memory_context()); }
79
83 auto operator()(auto thunk) { return memory_context_execution(thunk, *this)(); }
84
85protected:
86 virtual ::MemoryContext _memory_context() = 0;
87
88 template <typename T> requires requires(T t) { t(); }
91 : _ctx(::CurrentMemoryContext), _thunk(thunk) {
92 ::CurrentMemoryContext = ctx;
93 }
94 ~memory_context_execution() { ::CurrentMemoryContext = _ctx; }
95
96 auto operator()() { return _thunk(); }
97
98 private:
99 MemoryContext _ctx;
100 T _thunk;
101 };
102};
103
105 friend struct memory_context;
106
107protected:
108 owned_memory_context(::MemoryContext context) : context(context), moved(false) {}
110 owned_memory_context &operator=(const owned_memory_context &) = delete;
112 : context(other.context), moved(other.moved) {
113 other.moved = true;
114 }
115 owned_memory_context &operator=(owned_memory_context &&other) {
116 if (this != &other) {
117 if (!moved) {
118 delete_context();
119 }
120 context = other.context;
121 moved = other.moved;
122 other.moved = true;
123 }
124 return *this;
125 }
126
128 if (!moved) {
129 delete_context();
130 }
131 }
132
133 ::MemoryContext context;
134 bool moved;
135
136 ::MemoryContext _memory_context() override { return context; }
137};
138
140
141 friend struct owned_memory_context;
142
143 explicit memory_context() : context(::CurrentMemoryContext) {}
144 explicit memory_context(::MemoryContext context) : context(context) {}
145 explicit memory_context(abstract_memory_context &&context) : context(context) {}
146
147 explicit memory_context(owned_memory_context &&ctx) : context(ctx) { ctx.moved = true; }
148
149 static memory_context for_pointer(void *ptr) {
150 if (ptr == nullptr || ptr != (void *)MAXALIGN(ptr)) {
151 throw std::runtime_error("invalid pointer");
152 }
153 return memory_context(ffi_guard{::GetMemoryChunkContext}(ptr));
154 }
155
156 template <typename C> requires std::derived_from<C, abstract_memory_context>
157 friend struct tracking_memory_context;
158
159protected:
160 ::MemoryContext context;
161
162 ::MemoryContext _memory_context() noexcept override { return context; }
163};
164
167
168protected:
169 ::MemoryContext _memory_context() override { return ::CurrentMemoryContext; }
170};
171
173 using owned_memory_context::owned_memory_context;
175 : owned_memory_context(ffi_guard{::AllocSetContextCreateInternal}(
176 ::CurrentMemoryContext, nullptr, ALLOCSET_DEFAULT_SIZES)) {}
179 ffi_guard{::AllocSetContextCreateInternal}(ctx, nullptr, ALLOCSET_DEFAULT_SIZES)) {}
180
183 ffi_guard{::AllocSetContextCreateInternal}(ctx, nullptr, ALLOCSET_DEFAULT_SIZES)) {}
184};
185
186inline memory_context top_memory_context() { return memory_context(TopMemoryContext); };
187
188template <typename C> requires std::derived_from<C, abstract_memory_context>
190private:
191 template <typename T> requires std::integral<T>
192 struct shared_counter {
193 T value;
194 constexpr explicit shared_counter(T init = 0) noexcept : value(init) {}
195
196 shared_counter &operator=(T v) noexcept {
197 value = v;
198 return *this;
199 }
200
201 shared_counter &operator++() noexcept {
202 ++value;
203 return *this;
204 }
205
206 T operator++(int) noexcept {
207 T old = value;
208 ++value;
209 return old;
210 }
211
212 constexpr operator T() const noexcept { return value; }
213 };
214
215 struct callback_state {
216 shared_counter<uint64_t> counter;
217 ::MemoryContextCallback *callback = nullptr;
218 };
219
220 static void track_reset(void *arg) {
221 auto *state = static_cast<callback_state *>(arg);
222 state->counter++;
223 state->callback = nullptr;
224 }
225
226public:
228 : ctx(other.ctx), state(other.state) {}
229
230 explicit tracking_memory_context(C ctx) : ctx(ctx), state(std::make_shared<callback_state>()) {
231 state->callback = this->register_reset_callback(track_reset, state.get());
232 }
233
235 : ctx(std::move(other.ctx)), state(std::move(other.state)) {}
236
237 tracking_memory_context &operator=(const tracking_memory_context &other) noexcept {
238 if (this != &other) {
239 ctx = other.ctx;
240 state = other.state;
241 }
242 return *this;
243 }
244
245 tracking_memory_context &operator=(tracking_memory_context &&other) noexcept {
246 if (this != &other) {
247 ctx = std::move(other.ctx);
248 state = std::move(other.state);
249 }
250 return *this;
251 }
252
254 if (state != nullptr && state.use_count() == 1 && state->callback != nullptr) {
255 state->callback->func = [](void *) {};
256 state->callback->arg = nullptr;
257 state->callback = nullptr;
258 }
259 }
260
261 uint64_t resets() const { return state == nullptr ? 0 : state->counter; }
262 C &get_memory_context() { return ctx; }
263
264private:
265 C ctx;
266 std::shared_ptr<callback_state> state;
267
268protected:
269 ::MemoryContext _memory_context() override { return ctx._memory_context(); }
270};
271
272template <typename T>
274 std::derived_from<T, abstract_memory_context> && std::default_initializable<T>;
275
276template <a_memory_context Context> struct memory_context_scope {
277 explicit memory_context_scope(Context &ctx)
278 : previous(::CurrentMemoryContext), ctx(ctx.operator ::MemoryContext()) {
279 ::CurrentMemoryContext = ctx;
280 }
281 explicit memory_context_scope(Context &&ctx)
282 : previous(::CurrentMemoryContext), ctx(ctx.operator ::MemoryContext()) {
283 ::CurrentMemoryContext = ctx;
284 }
285
286 ~memory_context_scope() { ::CurrentMemoryContext = previous; }
287
288private:
289 ::MemoryContext previous;
290 ::MemoryContext ctx;
291};
292
293template <class T, a_memory_context Context = memory_context> struct memory_context_allocator {
294 using value_type = T;
295 memory_context_allocator() noexcept : context(Context()), explicit_deallocation(false) {}
296 memory_context_allocator(Context &&ctx, bool explicit_deallocation) noexcept
297 : context(std::move(ctx)), explicit_deallocation(explicit_deallocation) {}
298
299 constexpr memory_context_allocator(const memory_context_allocator<T> &c) noexcept
300 : context(c.context) {}
301
302 [[nodiscard]] T *allocate(std::size_t n) {
303 try {
304 return context.template alloc<T>(n);
305 } catch (pg_exception &e) {
306 throw std::bad_alloc();
307 }
308 }
309
310 void deallocate(T *p, std::size_t n) noexcept {
311 if (explicit_deallocation || context == top_memory_context()) {
312 context.free(p);
313 }
314 }
315
316 bool operator==(const memory_context_allocator &c) { return context == c.context; }
317 bool operator!=(const memory_context_allocator &c) { return context != c.context; }
318
319 Context &memory_context() { return context; }
320
321private:
322 Context context;
323 bool explicit_deallocation;
324};
325
326struct pointer_gone_exception : public std::exception {
327 const char *what() const noexcept override {
328 return "pointer belongs to a MemoryContext that has been reset or deleted";
329 }
330};
331
332} // namespace cppgres
Definition: exception.hpp:7
Definition: memory.hpp:273
Definition: memory.hpp:15
T * construct(Args &&...args)
Definition: memory.hpp:60
auto operator()(auto thunk)
Definition: memory.hpp:83
Definition: memory.hpp:172
Definition: memory.hpp:165
Definition: guard.hpp:20
Definition: memory.hpp:293
Definition: memory.hpp:276
Definition: memory.hpp:139
Definition: memory.hpp:104
Definition: memory.hpp:326
Definition: memory.hpp:189
Definition: value.hpp:8