POSIX Shared Memory Data Structures 1.0
High-performance lock-free data structures for inter-process communication
Loading...
Searching...
No Matches
shm_object_pool.h
Go to the documentation of this file.
1#pragma once
2#include "posix_shm.h"
3#include "shm_table.h"
4#include <atomic>
5#include <optional>
6#include <span>
7#include <bit>
8
21template<typename T, typename TableType = shm_table>
22 requires std::is_trivially_copyable_v<T>
24private:
25 struct PoolHeader {
26 std::atomic<uint32_t> free_head{0}; // Head of free list (stack)
27 std::atomic<uint32_t> num_allocated{0}; // Statistics
28 uint32_t capacity;
29 uint32_t next_array[]; // Next pointers for free list
30 // Objects stored after next_array[capacity]
31 };
32
33 static constexpr uint32_t NULL_INDEX = std::numeric_limits<uint32_t>::max();
34
35 PoolHeader* header_{nullptr};
36 T* objects_{nullptr};
37 const typename TableType::entry* table_entry_{nullptr};
38
39 PoolHeader* get_header(void* base_addr, size_t offset) {
40 return reinterpret_cast<PoolHeader*>(
41 static_cast<char*>(base_addr) + offset
42 );
43 }
44
45public:
46 using value_type = T;
47 using size_type = size_t;
48 using handle_type = uint32_t; // Index into pool
49
50 static constexpr handle_type invalid_handle = NULL_INDEX;
51
52 template<typename ShmType>
53 shm_object_pool(ShmType& shm, std::string_view name, size_t capacity = 0) {
54 static_assert(std::is_same_v<typename ShmType::table_type, TableType>,
55 "SharedMemory table type must match pool table type");
56
57 auto* table = static_cast<TableType*>(shm.get_base_addr());
58
59 char name_buf[TableType::MAX_NAME_SIZE]{};
60 size_t copy_len = std::min(name.size(), sizeof(name_buf) - 1);
61 std::copy_n(name.begin(), copy_len, name_buf);
62
63 auto* entry = table->find(name_buf);
64
65 if (entry) {
66 // Open existing pool
67 header_ = get_header(shm.get_base_addr(), entry->offset);
68 objects_ = reinterpret_cast<T*>(
69 reinterpret_cast<char*>(header_) +
70 sizeof(PoolHeader) + sizeof(uint32_t) * header_->capacity
71 );
72 table_entry_ = entry;
73 } else if (capacity > 0) {
74 // Create new pool
75 size_t header_size = sizeof(PoolHeader) + sizeof(uint32_t) * capacity;
76 size_t objects_size = sizeof(T) * capacity;
77 size_t total_size = header_size + objects_size;
78
79 size_t table_size = sizeof(TableType);
80 size_t current_used = table->get_total_allocated_size();
81 size_t offset = table_size + current_used;
82
83 header_ = get_header(shm.get_base_addr(), offset);
84
85 // Initialize header
86 new (header_) PoolHeader();
87 header_->capacity = static_cast<uint32_t>(capacity);
88 header_->free_head = 0;
89 header_->num_allocated = 0;
90
91 // Initialize free list (all objects initially free)
92 for (uint32_t i = 0; i < capacity; ++i) {
93 header_->next_array[i] = i + 1;
94 }
95 header_->next_array[capacity - 1] = NULL_INDEX;
96
97 objects_ = reinterpret_cast<T*>(
98 reinterpret_cast<char*>(header_) + header_size
99 );
100
101 // Register in table
102 if (!table->add(name_buf, offset, total_size, sizeof(T), capacity)) {
103 throw std::runtime_error("Failed to add pool to table");
104 }
105 table_entry_ = table->find(name_buf);
106 } else {
107 throw std::runtime_error("Pool not found and capacity not specified");
108 }
109 }
110
115 [[nodiscard]] handle_type acquire() noexcept {
116 uint32_t old_head = header_->free_head.load(std::memory_order_acquire);
117
118 while (old_head != NULL_INDEX) {
119 uint32_t new_head = header_->next_array[old_head];
120
121 if (header_->free_head.compare_exchange_weak(
122 old_head, new_head,
123 std::memory_order_release,
124 std::memory_order_acquire)) {
125
126 header_->num_allocated.fetch_add(1, std::memory_order_relaxed);
127 return old_head;
128 }
129 }
130
131 return invalid_handle; // Pool exhausted
132 }
133
138 template<typename... Args>
139 [[nodiscard]] std::optional<handle_type> acquire_construct(Args&&... args) {
140 handle_type handle = acquire();
141 if (handle != invalid_handle) {
142 new (&objects_[handle]) T(std::forward<Args>(args)...);
143 return handle;
144 }
145 return std::nullopt;
146 }
147
151 void release(handle_type handle) noexcept {
152 if (handle >= header_->capacity) return; // Invalid handle
153
154 // Push onto free list stack
155 uint32_t old_head = header_->free_head.load(std::memory_order_acquire);
156 do {
157 header_->next_array[handle] = old_head;
158 } while (!header_->free_head.compare_exchange_weak(
159 old_head, handle,
160 std::memory_order_release,
161 std::memory_order_acquire));
162
163 header_->num_allocated.fetch_sub(1, std::memory_order_relaxed);
164 }
165
169 [[nodiscard]] T& operator[](handle_type handle) noexcept {
170 return objects_[handle];
171 }
172
173 [[nodiscard]] const T& operator[](handle_type handle) const noexcept {
174 return objects_[handle];
175 }
176
177 [[nodiscard]] T* get(handle_type handle) noexcept {
178 if (handle >= header_->capacity) return nullptr;
179 return &objects_[handle];
180 }
181
182 [[nodiscard]] const T* get(handle_type handle) const noexcept {
183 if (handle >= header_->capacity) return nullptr;
184 return &objects_[handle];
185 }
186
190 [[nodiscard]] bool is_valid(handle_type handle) const noexcept {
191 return handle < header_->capacity;
192 }
193
197 [[nodiscard]] size_t capacity() const noexcept {
198 return header_->capacity;
199 }
200
201 [[nodiscard]] size_t num_allocated() const noexcept {
202 return header_->num_allocated.load(std::memory_order_relaxed);
203 }
204
205 [[nodiscard]] size_t num_available() const noexcept {
206 return capacity() - num_allocated();
207 }
208
209 [[nodiscard]] bool empty() const noexcept {
210 return num_allocated() == 0;
211 }
212
213 [[nodiscard]] bool full() const noexcept {
214 return num_allocated() == capacity();
215 }
216
221 [[nodiscard]] std::span<T> unsafe_all_objects() noexcept {
222 return std::span<T>(objects_, header_->capacity);
223 }
224
225 [[nodiscard]] std::span<const T> unsafe_all_objects() const noexcept {
226 return std::span<const T>(objects_, header_->capacity);
227 }
228
229 [[nodiscard]] std::string_view name() const noexcept {
230 return table_entry_ ? std::string_view(table_entry_->name.data()) : std::string_view{};
231 }
232
239 size_t acquire_batch(size_t count, handle_type* handles) noexcept {
240 size_t acquired = 0;
241 for (size_t i = 0; i < count; ++i) {
242 handle_type h = acquire();
243 if (h == invalid_handle) break;
244 handles[acquired++] = h;
245 }
246 return acquired;
247 }
248
252 void release_batch(std::span<const handle_type> handles) noexcept {
253 for (handle_type h : handles) {
254 release(h);
255 }
256 }
257};
High-performance object pool for shared memory.
handle_type acquire() noexcept
Acquire an object from the pool.
const T * get(handle_type handle) const noexcept
T * get(handle_type handle) noexcept
bool full() const noexcept
shm_object_pool(ShmType &shm, std::string_view name, size_t capacity=0)
T & operator[](handle_type handle) noexcept
Access object by handle.
std::span< const T > unsafe_all_objects() const noexcept
const T & operator[](handle_type handle) const noexcept
std::span< T > unsafe_all_objects() noexcept
Get view of all objects (including free ones) Use with caution - includes uninitialized objects.
size_t acquire_batch(size_t count, handle_type *handles) noexcept
Batch acquire multiple objects.
size_t num_available() const noexcept
std::string_view name() const noexcept
bool empty() const noexcept
void release(handle_type handle) noexcept
Release an object back to the pool.
size_t capacity() const noexcept
Get pool statistics.
std::optional< handle_type > acquire_construct(Args &&... args)
Acquire and construct an object.
bool is_valid(handle_type handle) const noexcept
Check if handle is valid.
static constexpr handle_type invalid_handle
void release_batch(std::span< const handle_type > handles) noexcept
Batch release multiple objects.
size_t num_allocated() const noexcept
Core POSIX shared memory management with automatic reference counting.