Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
pool_arena.cpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19
24#include <flow/error/error.hpp>
25#include <flow/util/stat/stat_set.hpp>
26#include <boost/interprocess/exceptions.hpp>
27#include <new>
28
29namespace ipc::shm::classic
30{
31
32template<typename Mode_tag>
33Pool_arena::Pool_arena(Mode_tag mode_tag, flow::log::Logger* logger_ptr,
34 const Shared_name& pool_name_arg, size_t pool_sz,
35 const util::Permissions& perms, Error_code* err_code) :
36 flow::log::Log_context(logger_ptr, Log_component::S_SHM),
37 m_pool_name(pool_name_arg),
38 m_own_process_id(util::Process_credentials::own_process_id()),
39 m_arena_metadata(nullptr)
40{
41 using boost::io::ios_all_saver;
42
43 assert(pool_sz >= sizeof(void*));
44 static_assert(std::is_same_v<Mode_tag, util::Create_only> || std::is_same_v<Mode_tag, util::Open_or_create>,
45 "Can only delegate to this ctor with Mode_tag = Create_only or Open_or_create.");
46 constexpr char const * MODE_STR = std::is_same_v<Mode_tag, util::Create_only>
47 ? "create-only" : "open-or-create";
48
49 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_INFO, get_log_component()))
50 {
51 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
52 FLOW_LOG_INFO_WITHOUT_CHECKING
53 ("SHM-classic pool [" << *this << "]: Constructing heap handle to heap/pool at name [" << m_pool_name << "] in "
54 "[" << MODE_STR << "] mode; pool size [" << flow::util::ceil_div(pool_sz, size_t(1024 * 1024)) << "Mi]; "
55 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "].");
56 }
57
58 /* m_pool is null. Try to create/create-or-open it; it may throw exception; this will do the right thing including
59 * leaving m_pool at null, as promised, on any error. Note we might throw exception because of this call. */
61 (get_logger(), err_code, error::Code::S_SHM_BIPC_MISC_LIBRARY_ERROR, "Pool_arena(): Pool()", [&]()
62 {
63 m_pool.emplace(mode_tag, m_pool_name, pool_sz, perms);
64 init_arena_metadata(false); // If the above did not throw then do this (it also can throw, re-nullifying m_pool).
65 });
66} // Pool_arena::Pool_arena()
67
68Pool_arena::Pool_arena(flow::log::Logger* logger_ptr,
69 const Shared_name& pool_name_arg, util::Create_only, size_t pool_sz,
70 const util::Permissions& perms, Error_code* err_code) :
71 Pool_arena(util::CREATE_ONLY, logger_ptr, pool_name_arg, pool_sz, perms, err_code)
72{
73 // Cool.
74}
75
76Pool_arena::Pool_arena(flow::log::Logger* logger_ptr,
77 const Shared_name& pool_name_arg, util::Open_or_create, size_t pool_sz,
78 const util::Permissions& perms, Error_code* err_code) :
79 Pool_arena(util::OPEN_OR_CREATE, logger_ptr, pool_name_arg, pool_sz, perms, err_code)
80{
81 // Cool.
82}
83
84Pool_arena::Pool_arena(flow::log::Logger* logger_ptr,
85 const Shared_name& pool_name_arg, util::Open_only, bool read_only, Error_code* err_code) :
86 flow::log::Log_context(logger_ptr, Log_component::S_SHM),
87 m_pool_name(pool_name_arg),
88 m_own_process_id(util::Process_credentials::own_process_id()),
89 m_arena_metadata(nullptr)
90{
91 using flow::error::Runtime_error;
92
93 FLOW_LOG_INFO("SHM-classic pool [" << *this << "]: Constructing heap handle to heap/pool at name "
94 "[" << m_pool_name << "] in open-only mode; paged read-only? = [" << read_only << "].");
95
97 "Pool_arena(OPEN_ONLY): Pool()", [&]()
98 {
99 m_pool.emplace(util::OPEN_ONLY, m_pool_name, read_only);
100 // If the above did not throw then do this (it also can throw, re-nullifying m_pool).
101 init_arena_metadata(read_only);
102 });
103
104 /* It threw? Fine then. It didn't, but *err_code is truthy? We're cooked; get out.
105 * Otherwise: Per init_arena_metadata() doc header, there's an additional failure mode. */
106 if (err_code && *err_code)
107 {
108 return;
109 }
110 // else:
111 if (!m_arena_metadata)
112 {
113 assert((!m_pool) && "init_arena_metadata() is supposed to nullify m_pool in this error case too.");
114
116 FLOW_LOG_WARNING("SHM-classic pool [" << *this << "]: Improper use of read-only-mode ctor detected; emitting "
117 "error [" << our_err_code << "] "
118 "[" << our_err_code.message() << "]."); // This .message() explains the whole thing.
119 if (!err_code)
120 {
121 throw Runtime_error{our_err_code, "Pool_arena::Pool_arena(OPEN_ONLY/read_only)"};
122 }
123 // else
124 *err_code = our_err_code;
125 }
126 // else { All cool. }
127} // Pool_arena::Pool_arena(OPEN_ONLY)
128
130{
131 using ::ipc::bipc::unique_instance;
132 using flow::util::stat::print;
133
134 /* It'll lock internal mutex, create Arena_metadata{} or find it, unlock, return pointer. We get our singleton.
135 * For f_o_c(): We use the throwing form (no std::nothrow), so either it throws, or it returns non-null.
136 *
137 * find_no_lock() might return .first=null, if the user ignored the directive to ensure the pool had been fully
138 * created/initialized (via a creating ctor form) before cting *this (or if they're using this with some
139 * random pool not being set-up through a proper Pool_arena). As advertised, it's on the calling ctor to deal
140 * with it. */
141 try
142 {
143 if (read_only)
144 {
145 m_arena_metadata = m_pool->core()->find_no_lock<Arena_metadata>(unique_instance).first;
146 // (I want to say find_no_lock() won't throw but -- better safe than sorry; and it's harmless to try{} anyway.)
147
148 if (!m_arena_metadata)
149 {
150 m_pool.reset(); // As promised.
151 }
152 }
153 else
154 {
155 m_arena_metadata = m_pool->core()->find_or_construct<Arena_metadata>(unique_instance)();
156 assert(m_arena_metadata && "That find_or_construct() form is supposed to either return non-null or throw.");
157 }
158 }
159 catch (...)
160 {
161 m_pool.reset(); // As promised.
162 throw;
163 }
164
165 /* (Assume here !read_only.)
166 * Note: It's tempting to perhaps use ->find_or_construct(std::nothrow) above and lose the try/catch.
167 * Check for null; if so then reset m_pool; return. Or even posit that there is no real way that'd happen,
168 * so assert(m_arena_metadata) and return -- end of.
169 *
170 * (Arena_metadata{} ctor can't itself throw. Incidentally, if it could and did, std::nothrow would not catch it.)
171 *
172 * Problem with it: The bipc docs don't in any way say this, and (as of Boost-1.87) the following is basically only
173 * detectable by bipc code inspection, but it actually can throw still: The internal mutex lock (1) can
174 * realistically fail (at least if other process crashes while holding mutex) and (2) bipc code doesn't
175 * check for that possibility the way it does the rest of the stuff.
176 *
177 * That alone is enough to make us stop trying to be precious w/r/t various error paths, which ones of those
178 * are real, and how they'd be emitted. Just let it throw on any problem, and we'll emit that. In the unlikely
179 * case there's indeed a problem, they'll at least have an Error_code and/or exception + possible log message
180 * (from our likely caller, the ctor) to help figure it out. */
181
183 {
184 FLOW_LOG_INFO("SHM-classic pool [" << *this << "]: Stats at ctor (arena[] can change concurrently if pool "
185 "just-opened; ~zeroed if pool just-created): "
186 "arena[" << print(*(arena_stats())) << "]"
187 "[free=[" << arena_stat_free_size() << '/' << arena_size() << "]].");
188 }
189
190 // local_stats() guaranteed zeroed at the moment.
191} // Pool_arena::init_arena_metadata()
192
194{
195 FLOW_LOG_INFO("SHM-classic pool [" << *this << "]: Closing handle.");
196 if (m_pool)
197 {
198 Info_dump dump; // Multi-line (default); m_fmt.m_verbose has no effect for SHM-classic.
199 /* Note: info_dump() output has no trailing newline; we cap it with a period by our little convention.
200 * Note: Avoid unneeded info_dump() by placing it inside log-macro. */
201 FLOW_LOG_INFO("SHM-classic pool [" << *this << "]: ~Final state:\n" << (info_dump(&dump), dump) << '.');
202 }
203 // else { arena_stats() would be null (invalid Pool_arena); nothing useful to dump. }
204}
205
207{
208 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool, commit, _1);
209 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
210
211 if (!m_pool)
212 {
213 err_code->clear();
214 return false;
215 }
216 // else
217
218 bool committed = false;
220 "Pool_arena::commit()", [&]()
221 {
222 committed = m_pool->commit(); // false <=> handle closed earlier. Throws on error (e.g., no-space-left).
223 });
224
225 if (*err_code)
226 {
227 FLOW_LOG_WARNING("SHM-classic pool [" << *this << "]: Commit (take RAM for) entire pool: failed (details "
228 "above); pool remains as it was (sparse) and usable.");
229 return true;
230 }
231 // else
232
233 // TRACE-log in that no need to assume we are a rare call. They can always INFO-log if desired.
234 if (committed)
235 {
236 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: Commit (take RAM for) entire pool: done.");
237 }
238 else
239 {
240 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: Commit (take RAM for) entire pool: no-op, as "
241 "close_shm_object_handle() was called earlier.");
242 }
243 return committed;
244} // Pool_arena::commit()
245
247{
248 if (!m_pool)
249 {
250 return false;
251 }
252 // else
253
254 FLOW_LOG_INFO("SHM-classic pool [" << *this << "]: Closing SHM-pool OS handle (commit() no longer possible).");
255 m_pool->close_shm_object_handle();
256 return true;
257}
258
259void* Pool_arena::allocate(size_t n)
260{
261 using Bipc_bad_alloc = ::ipc::bipc::bad_alloc;
262 using Std_bad_alloc = std::bad_alloc;
263
264 assert((n != 0) && "Please do not allocate(0).");
265
266 if (!m_pool)
267 {
268 return nullptr;
269 }
270 // else
271
272 void* ret;
273
274 try
275 {
276 const auto logger_ptr = get_logger();
277 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_DATA, get_log_component()))
278 {
279 const auto total = arena_size();
280 const auto prev_free = arena_stat_free_size();
281 ret = m_pool->core()->allocate(n); // Can throw (hence we can throw as advertised).
282 const auto now_free = arena_stat_free_size();
283 assert(total == arena_size());
284
285 FLOW_LOG_DATA_WITHOUT_CHECKING("SHM-classic pool [" << *this << "]: SHM-alloc-ed user buffer sized [" << n << "]; "
286 "bipc alloc-algo reports free space changed "
287 "[" << prev_free << "] (used [" << (total - prev_free) << "]) => "
288 "[" << now_free << "] (used [" << (total - now_free) << "]); "
289 "raw delta [" << (prev_free - now_free) << "].");
290 }
291 else
292 {
293 ret = m_pool->core()->allocate(n); // Can throw (hence we can throw as advertised).
294 }
295 }
296 catch (const Bipc_bad_alloc&) // As advertised normalize alloc failure to std::bad_alloc.
297 {
298 /* No WARNING; we're intentionally quiet as a low-level-feeling API. User can themselves log as desired.
299 * Who knows -- maybe their algorithm is intentionally OK with exceeding (configured-by-them, limited) space.
300 * We don't want to spam in such situations. */
301 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: SHM-alloc-ed user buffer sized [" << n << "]; but "
302 "bipc alloc-algo threw bipc::bad_alloc (no space left in SHM-pool); throwing as "
303 "std::bad_alloc. At this moment: free space [" << arena_stat_free_size() << "] "
304 "of [" << arena_size() << "].");
305 throw Std_bad_alloc{};
306 }
307
308 return ret;
309} // Pool_arena::allocate()
310
311bool Pool_arena::deallocate(void* buf_not_null) noexcept
312{
313 assert(buf_not_null && "Please do not deallocate(nullptr).");
314
315 if (!m_pool)
316 {
317 return false;
318 }
319 // else
320
321 const auto logger_ptr = get_logger();
322 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_DATA, get_log_component()))
323 {
324 const auto total = arena_size();
325 const auto prev_free = arena_stat_free_size();
326 m_pool->core()->deallocate(buf_not_null); // Does not throw.
327 const auto now_free = arena_stat_free_size();
328 assert(total == arena_size());
329
330 FLOW_LOG_DATA_WITHOUT_CHECKING("SHM-classic pool [" << *this << "]: SHM-dealloc-ed user buffer (size unknown) "
331 "bipc alloc-algo reports free space changed "
332 "[" << prev_free << "] (used [" << (total - prev_free) << "]) => "
333 "[" << now_free << "] (used [" << (total - now_free) << "]); "
334 "raw delta [" << (now_free - prev_free) << "].");
335 }
336 else
337 {
338 m_pool->core()->deallocate(buf_not_null);
339 }
340
341 return true;
342} // Pool_arena::deallocate()
343
344bool Pool_arena::is_addr_in_arena(const void* p) const
345{
346 // Pre-requisite to this internal helper is: m_pool is non-null.
347
348 const auto addr = reinterpret_cast<uintptr_t>(p);
349 const auto pool_base = reinterpret_cast<uintptr_t>(m_pool->address());
350 // Sidestep any (albeit very unlikely) wrap.
351 return (addr >= pool_base) && ((addr - pool_base) < arena_size());
352}
353
355{
356 /* This does include any metadata (if we use a non-null_index index, ~hundreds of bytes; memory-algorithm
357 * book-keeping). So as advertised this should equal pool_sz to originally-pool-creating ctor. */
358 return m_pool ? m_pool->size() : 0;
359}
360
362{
363 /* Our doc header semi-obliquely refers to the fact that this, as of Boost-1.87, plain-reads an integer
364 * that can concurrently be modified, via concurrent [de]allocate(). The [de]allocate()s lock a central mutex,
365 * but that's really about guarding the memory-algorithm internal data structures -- the modification of this
366 * integer is in that locked-section, but this read isn't. */
367 return m_pool ? m_pool->core()->get_free_memory() : 0;
368}
369
371{
372 return m_arena_metadata; // As advertised null if ctor failed to open #m_pool.
373}
374
376{
377 // Reminder: might want to look at doc header's thread-safety notes.
378 flow::util::stat::stats_reset(m_arena_metadata, stat::Arena_stats{});
379}
380
382{
383 return m_local_stats;
384}
385
387{
388 // Reminder: might want to look at doc header's thread-safety notes.
389 flow::util::stat::stats_reset(&m_local_stats, stat::Local_stats{});
390}
391
392void Pool_arena::info_dump(Info_dump* target_info_dump) const
393{
394 using flow::util::stat::stats_assign;
395
396 assert(target_info_dump);
397
398 target_info_dump->m_arena_sz_or_0 = arena_size(); // 0 <=> invalid Pool_arena; see Arena_info_dump doc header.
399 target_info_dump->m_arena_stat_free_sz = arena_stat_free_size();
400
401 const auto* const arena_stats_ptr = arena_stats(); // Null <=> invalid Pool_arena.
402 if (arena_stats_ptr)
403 {
404 stats_assign(&target_info_dump->m_arena_stats, *arena_stats_ptr);
405 }
406 // else { Invalid: m_arena_sz_or_0 is 0; m_arena_stats left as-is -- not meaningful, not printed. }
407
408 stats_assign(&target_info_dump->m_local_stats, local_stats());
409} // Pool_arena::info_dump()
410
411void Pool_arena::remove_persistent(flow::log::Logger* logger_ptr, // Static.
412 const Shared_name& pool_name, Error_code* err_code)
413{
414 util::remove_persistent_shm_pool(logger_ptr, pool_name, err_code);
415 // (See that guy's doc header for why we didn't just do what's necessary right in here.)
416}
417
418std::ostream& operator<<(std::ostream& os, const Pool_arena& val)
419{
420 return os << '@' << &val << " => sh_name[" << val.m_pool_name << ']';
421}
422
423} // namespace ipc::shm::classic
A SHM-classic interface around a single SHM pool with allocation-algorithm services by boost....
Definition: pool_arena.hpp:225
bool commit(Error_code *err_code=nullptr)
Commits (takes RAM for) every page of the pool not yet committed, so that subsequent use of the pool ...
Definition: pool_arena.cpp:206
const stat::Arena_stats * arena_stats() const
Cumulative stats relevant to one SHM-classic arena – in this case single SHM-pool in shared memory,...
Definition: pool_arena.cpp:370
Pool_arena(flow::log::Logger *logger_ptr, const Shared_name &pool_name, util::Create_only mode_tag, size_t pool_sz, const util::Permissions &perms={}, Error_code *err_code=nullptr)
Construct Pool_arena accessor object to non-existing named SHM pool, creating it first.
Definition: pool_arena.cpp:68
std::optional< Pool > m_pool
Attached SHM pool. If ctor fails in non-throwing fashion then this remains null. Immutable after ctor...
Definition: pool_arena.hpp:990
Arena_metadata * m_arena_metadata
Created or acquired in ctor, pointer to in-m_pool (in-SHM) metadata singleton; see Arena_metadata doc...
Definition: pool_arena.hpp:996
stat::Local_stats m_local_stats
Per-*this (not per-arena-in-SHM) stats being accumulated, from multiple threads sans sharding.
bool deallocate(void *buf_not_null) noexcept
Undoes effects of local allocate() that returned buf_not_null; or another-process's allocate() that r...
Definition: pool_arena.cpp:311
size_t arena_stat_free_size() const
How much of arena_size() is currently available for allocations; that is, not currently used by curre...
Definition: pool_arena.cpp:361
void init_arena_metadata(bool read_only)
Helper of ctors: with the assumption that m_pool is ready, sets up m_arena_metadata if possible.
Definition: pool_arena.cpp:129
const Shared_name m_pool_name
SHM pool name as set immutably at construction.
Definition: pool_arena.hpp:792
size_t arena_size() const
Arena (in this case, arena=pool) size in bytes as configured at arena/pool creation; never changes.
Definition: pool_arena.cpp:354
bool is_addr_in_arena(const void *p) const
Returns true if and only if the byte at p is within the bounds of the pool accessed by *this.
Definition: pool_arena.cpp:344
static void remove_persistent(flow::log::Logger *logger_ptr, const Shared_name &name, Error_code *err_code=nullptr)
Removes the named SHM pool object.
Definition: pool_arena.cpp:411
void arena_stats_reset()
Resets arena_stats().
Definition: pool_arena.cpp:375
const stat::Local_stats & local_stats() const
Cumulative stats relevant to *this Pool_arena object – a Pool_arena – and excluding those in arena_st...
Definition: pool_arena.cpp:381
void * allocate(size_t n)
Allocates buffer of specified size, in bytes, in the accessed pool; returns locally-dereferenceable a...
Definition: pool_arena.cpp:259
~Pool_arena()
Destroys Pool_arena accessor object.
Definition: pool_arena.cpp:193
bool close_shm_object_handle()
Closes the pool's OS handle (FD) that *this keeps open only to enable commit(), returning that resour...
Definition: pool_arena.cpp:246
void local_stats_reset()
Resets local_stats().
Definition: pool_arena.cpp:386
void info_dump(Info_dump *target_info_dump) const
Fills-out the stats/info contents of the given stat::Arena_info_dump: a printable bundling of all sta...
Definition: pool_arena.cpp:392
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
@ S_SHM_POOL_OPEN_READ_ONLY_FOUND_BUT_UNINIT
SHM-classic pool/arena, requested to be opened in native read-only mode, was found by name,...
@ S_SHM_BIPC_MISC_LIBRARY_ERROR
Low-level boost.ipc.shm: boost.interprocess emitted miscellaneous library exception sans a system cod...
ipc::shm sub-module with the SHM-classic SHM-provider. See ipc::shm doc header for introduction.
Definition: classic_fwd.hpp:26
std::ostream & operator<<(std::ostream &os, const Pool_arena &val)
Prints string representation of the given Pool_arena to the given ostream.
Definition: pool_arena.cpp:418
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
void remove_persistent_shm_pool(flow::log::Logger *logger_ptr, const Shared_name &pool_name, Error_code *err_code)
Equivalent to shm::classic::Pool_arena::remove_persistent().
Definition: util.cpp:112
bipc::open_only_t Open_only
Tag type indicating an ideally-atomic open-if-exists-else-fail operation.
Definition: util_fwd.hpp:152
const Open_or_create OPEN_OR_CREATE
Tag value indicating an open-if-exists-else-create operation.
Definition: util.cpp:36
const Open_only OPEN_ONLY
Tag value indicating an atomic open-if-exists-else-fail operation.
Definition: util.cpp:37
bipc::open_or_create_t Open_or_create
Tag type indicating an atomic open-if-exists-else-create operation.
Definition: util_fwd.hpp:149
bipc::create_only_t Create_only
Tag type indicating a create-unless-exists-else-fail operation.
Definition: util_fwd.hpp:155
void op_with_possible_bipc_exception(flow::log::Logger *logger_ptr, Error_code *err_code, const Error_code &misc_bipc_lib_error, String_view context, const Func &func)
Internal (to ipc) utility that invokes the given function that invokes a boost.interprocess operation...
Definition: util.hpp:35
const Create_only CREATE_ONLY
Tag value indicating an atomic create-unless-exists-else-fail operation.
Definition: util.cpp:38
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Printable (with knobs in contained util::stat::Info_dump_format) bundling of stats/info pertaining to...
Arena_stats m_arena_stats
The source Pool_arena's (shared, in-SHM) arena stats from: Pool_arena::arena_stats(); meaningless/ign...
size_t m_arena_sz_or_0
The source Pool_arena's Pool_arena::arena_size(); 0 if and only if that Pool_arena was invalid (its c...
Local_stats m_local_stats
The source Pool_arena object's stats from: Pool_arena::local_stats(); meaningless/ignorable if m_aren...
size_t m_arena_stat_free_sz
The source Pool_arena's Pool_arena::arena_stat_free_size(); meaningless/ignorable if m_arena_sz_or_0 ...
Cumulative stats relevant to one SHM-classic arena – in this case single SHM-pool in shared memory,...
Cumulative stats relevant to a given SHM-classic arena object – a Pool_arena – and excluding those in...