Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
pool_arena.hpp
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#pragma once
20
25#include "ipc/shm/shm_stats.hpp"
26#include "ipc/shm/shm_fwd.hpp"
29#include "ipc/util/util.hpp"
30#include "ipc/util/util_fwd.hpp"
32#include <flow/util/basic_blob.hpp>
33#include <flow/util/stat/stat_set.hpp>
34#include <flow/util/util.hpp>
35#include <boost/interprocess/indexes/flat_map_index.hpp>
36#include <cstdint>
37#include <cstring>
38#include <optional>
39#include <type_traits>
40#include <atomic>
41
42namespace ipc::shm::classic
43{
44
45// Types.
46
47/**
48 * A SHM-classic interface around a single SHM pool with allocation-algorithm services by boost.interprocess,
49 * as in `bipc::managed_shared_memory`, with symmetric read/write semantics, compatible with ipc::shm::stl
50 * STL-compliance and SHM-handle borrowing manually or via ipc::session.
51 *
52 * ### When to use ###
53 * Generally, this is a simple way to work with SHM. It is very easy to set up and has very little infrastructure
54 * on top of what is provided by a typically-used subset of bipc's SHM API -- which, itself, is essentially a thin
55 * wrapper around a classic OS-supplied SHM pool (segment) API, plus a Boost-supplied heap-like allocation algorithm.
56 * Nevertheless this wrapper, when combined with ipc::shm::stl, is eminently usable and flexible. Its main
57 * limitations may or may not be serious in production use, depending on the context. These limitations include
58 * the following.
59 * - When buffers are allocated and deallocated, bipc's default memory allocation algorithm -- #Mem_algo --
60 * is what is used. (We could also allow for the other supplied algo, `simple_seq_fit`, or a custom
61 * user-supplied one.) While surely care was taken in writing this, in production one might demand
62 * something with thread caching and just general, like, industry relevance/experience; a jemalloc or
63 * tcmalloc maybe.
64 * - Possible contingency: Not really. In many cases this does not matter too much; but if one wants general
65 * performance equal to that of the general heap in a loaded production environment, then classic::Pool_arena
66 * is probably not for you. Consider jemalloc-based SHM provided elsewhere in ipc::shm.
67 * - It works within exactly one *pool* a/k/a `mmap()`ped segment, and that pool's max size must be specified
68 * at creation. Once it is exhausted via un-deallocated allocations, it'll throw exceptions likely to wreak
69 * havoc in your application.
70 * - Possible contingency: You may set the max pool size to a giant value. This will *not* take it from
71 * the OS like Linux: only when a page is actually touched, such as by allocating in it, does that actual
72 * RAM get assigned to your application(s). See the section on RAM use below. All in all it is a viable
73 * approach but may need a measure of finesse.
74 * - The ability to allocate in a given backing pool via any process's Pool_arena handle to that
75 * backing pool -- not to mention deallocate in process B what was allocated in process A -- requires
76 * guaranteed read-write capability in all `Pool_arena`s accessing a given pool. That read-write capability
77 * in and of itself (orthogonally to allocations) provides algorithmic possibilities which are not
78 * easily available in an asymmetric setup, where only one process can write or deallocate, while others
79 * can only borrow handles and read. How is this a limitation, you ask? While providing power and
80 * simplicity, it also hugely increases the number and difficulty of dealing with unexpected conditions.
81 * That is -- any process can write and corrupt the pool's contents, thus "poisoning" other processes;
82 * any process crashing means the others cannot trust the integrity of the pool's contents; things of that
83 * nature.
84 * - Possible contingency: It would not be difficult to enable read-only access from all but one process;
85 * we provide such a constructor argument. However, one cannot use the SHM-handle borrowing method
86 * borrow_object() which severely stunts the usefulness of the class in that process and therefore across
87 * the system. That said one could imagine somewhat extending Pool_arena to enable lend-borrow and
88 * delegated deallocation (and maybe delegated allocation while at it) by adding some internal
89 * message-passing (maybe through a supplied transport::Channel or something). Doable but frankly
90 * goes against the spirit of simplicity and closeness-to-the-"source-material" cultivated by
91 * classic::Pool_arena. The jemalloc-based API available elsewhere in ipc::shm is the one that
92 * happily performs message-passing IPC internally for such reasons. It's a thought though.
93 *
94 * ### Properties ###
95 * Backing pool structure: One (1) SHM pool, explicitly named at construction. Can open handle with create-only,
96 * create-or-open (atomic), or open-only semantics. Pool size specified at construction/cannot be changed.
97 * Vaddr structure is not synchronized (so `void* p` pointing into SHM in process 1 cannot be used in process 2
98 * without first adjusting it based on the different base vaddr of the mapped pool in process 2 versus 1).
99 *
100 * Handle-to-arena structure: Pool_arena is the only type, used by any process involved, that accesses the underlying
101 * arena, and once open all capabilities are symmetrically available to all Pool_arena objects in all processes.
102 * - Any `Pool_arena` can allocate (and deallocate what it has allocated).
103 * - Any `Pool_arena` can deallocate what any other `Pool_arena` has allocated (as long as the correct
104 * locally-dereferenceable `void*` has been obtained in the deallocating process).
105 *
106 * Allocation algorithm: As of this writing the bipc default, #Mem_algo. See its description in bipc docs.
107 * Note it does not perform any thread-caching like modern `malloc()`s.
108 *
109 * Allocation/deallocation API: See section below for proper use techniques.
110 *
111 * Cleanup: The underlying SHM pool is deleted if and only if one calls remove_persistent(), supplying it the
112 * pool name. This is not invoked internally at all, so it is the user's responsibility. ipc::session-managed
113 * `Pool_arena`s will be automatically cleaned up, as ipc::session strives to clean all persistent shared
114 * resources via a general algorithm. See remove_persistent() and for_each_persistent() doc headers.
115 *
116 * Satisfies `Arena` requirements for shm::stl::Stateless_allocator: Yes. I.e., it is easy to store
117 * STL-compliant containers directly in SHM by using `Stateless_allocator<Pool_arena>` as the allocator at all
118 * levels.
119 * - SHM-stored pointer type provided: Yes, #Pointer. This is, in reality, `bipc::offset_ptr`.
120 * - Non-STL-compliant data structures with pointers, such as linked lists, can be written in terms
121 * of allocate() and deallocate(), but only if one uses #Pointer as opposed to raw `T*`
122 * pointers. We recommend against this, but if it cannot be avoided due to legacy code or what-not....
123 *
124 * Handle borrowing support: Yes. Pool_arena directly provides an API for this. Internally it uses minimalistic
125 * atomic ref-counting directly in SHM without any IPC messaging used. Due to this internal simplicity this support
126 * is symmetric and supports unlimited proxying out of the box. That is, any process of N, each with a Pool_arena
127 * open to the same pool named P, can construct a borrowable object, then lend it to any other process which can also
128 * lend it to any other of the N processes. Internally the last Pool_arena's borrowed (or originally lent)
129 * handle to reach 0 intra-process ref-count shall invoke the object's dtor and deallocate the underlying buffer.
130 * (Algorithms like this = why symmetric read/write capability is fairly central to Pool_arena as written.)
131 * - However, as of this writing, this support is deliberately basic. In particular if a borrower process dies
132 * ungracefully (crash that does not execute all destructors, and so on), then the memory will leak until
133 * cleanup via remove_persistent().
134 *
135 * Thread safety: On a given `*this` all APIs are safe to call concurrently unless stated otherwise (or qualified)
136 * in a specific API's doc header (as of this writing: arena_stats_reset(), local_stats_reset()) -- with one more
137 * exception: commit() and close_shm_object_handle() must not be called concurrently with each other.
138 *
139 * Performance: We omit a high-level assessment here; it depends on the use-case, and a `*this` provides access to
140 * essentially a memory-manager/heap which is a very general data structure. The most relevant factors to use in
141 * a high-level analysis are as follows.
142 * - The boost.interprocess memory-algorithm currently selected is #Mem_algo; it is described in Boost docs.
143 * - Every allocate() and deallocate() call shall lock one per-arena-in-SHM mutex (itself an in-SHM mutex),
144 * do most of its work, and unlock. We emphasize this is (typically) cross-process locking in every
145 * `[de]allocate()` from anywhere that executes these ops against a given SHM-pool (not merely `*this`).
146 * - Recall that allocate() can be called directly; for `sizeof(T)` in `construct<T>()`; and potentially many times
147 * on behalf of `T` from its ctor and other allocating ops, if `T` is *or involves* STL-compliant type(s)
148 * that use(s) (SHM-enabled) allocator(s) (such as the recommended stl::Stateless_allocator).
149 * - Each of those allocate() calls is likely to eventually involve a matching deallocate().
150 *
151 * ### RAM use: sparse by default; commit() if desired ###
152 * Creating a pool of size `pool_sz` does not take `pool_sz` bytes of RAM: a page of it takes RAM (is *committed*)
153 * only once first touched -- by allocation-algorithm book-keeping or by your data. So a large `pool_sz` costs
154 * little up-front. The flip side: should the system be out of RAM-for-SHM (Linux: the configurable size limit of
155 * the tmpfs underlying POSIX SHM) at the moment a new page is first touched, there is no error to report; the
156 * touching process is killed (SIGBUS).
157 *
158 * If you would rather pay up-front -- and learn of a shortage then, as a civilized error -- call commit(): it
159 * commits the entire pool, at any time after construction. Conversely, if you know you won't do so, you may call
160 * close_shm_object_handle() to release the OS handle (FD) kept around (one per `*this`) for `commit()`'s sake.
161 *
162 * ### Allocation API and how to properly use it ###
163 * The most basic and lowest-level API consists of allocate() and deallocate(). We recommend against
164 * user code using these, as it is easy to leak and double-free (same as with `new` and `delete` in regular heap,
165 * except as usual with SHM anything that was not allocated will persist in RAM until remove_persistent()).
166 * - shm::stl allocator(s) will use these APIs safely.
167 * - As noted earlier, if one writes a non-STL-compliant data structure (such as a manually written linked list),
168 * it is appropriate to use allocate(), deallocate(), and #Pointer. It is best to avoid
169 * such data structures in favor of shm::stl-aided STL-compliant structures.
170 *
171 * The next level of API is construct(). `construct<T>()` returns a regular-looking `shared_ptr`. If it is
172 * never lent to another process, then the constructed `T` will be destroyed automatically as one would expect.
173 * If one *does* properly lend such a `shared_ptr` (which we call a *handle*) to another process (and which it
174 * can proxy-lend to another process still), then the `T` will be destroyed by the last process whose
175 * handle reaches ref-count 0. No explicit locking on the user's part is required to make this work.
176 * - If `T` is a POD (plain old data-type), then that's that.
177 * - If not, but `T` is a shm::stl-allocator-aided STL-compliant-structure, then you're good.
178 * - If not (e.g., the aforementioned manually implemented linked list) but the `T` destructor
179 * performs the necessary inner deallocation via deallocate(), then you're good. Again, we recommend
180 * against this, but sometimes it cannot be helped.
181 * - If not, then one must manually do the inner deallocation first, then let the handle (`shared_ptr`) group
182 * reach ref-count 0. The key is to do it in that order (which is why doing it via `T::~T()` is easiest).
183 *
184 * `T` cannot be a native array; and there is no facility for constructing such. Use `std::array` or `boost::array`
185 * as `T` if desired.
186 *
187 * @internal
188 *
189 * Impl notes: Stats
190 * -----------------
191 * One class of stats relevant here is the kind that would require updates in allocate() and deallocate().
192 * (Cf. first-class/outer operations: construct() and related: borrow_object(), lend_object(), and the
193 * disposers attached to `Handle`s returned by `construct()` and `borrow_object()`.)
194 * allocate() and deallocate() are (potentially) very frequently called from various threads.
195 * Notes about this type of stat:
196 * - The most obviously useful such stat is provided by boost.ipc #Pool and already exposed via
197 * arena_stat_free_size(). (See also arena_size() which is related but constant.) Perf characteristics are
198 * acceptable; see arena_stat_free_size() doc header.
199 * - Other stats are certainly conceivably useful. (Example 1 (per-arena, process-agnostic, `*this`-agnostic):
200 * a low-water mark of arena_stat_free_size(). Example 2 (per-`*this`): buffer count + bytes ever allocated
201 * via `*this`; gauge of currently outstanding buffers/bytes; high-water mark thereof.) However we consciously
202 * forego tracking these, for one reason: we do not want to spend more
203 * cycles in `[de]allocate()`, period, unless a slam-dunk need is demonstrated.
204 * Counter-argument: Pool_arena already involves, via bipc, a mutex-lock around the "meat" of
205 * our `[de]allocate()`; if this is a problem, then some extra `atomic::fetch_add()`s, etc., will hardly
206 * make it appreciably worse; if this is *not* a problem, then such atomic-ops will be even less so.
207 * Counter-counter-argument: We are not willing to worry about it, until there's a demonstrated need.
208 * Counter-counter-argument wins, for now. Should that change, here's a design sketch for how to add these:
209 * - Per-arena/`*this`-agnostic: Use non-`null_index` index in #Pool; obtain pointer to a stats-`struct`
210 * `find_or_construct()`ed-as-`unique_instance` using this facility; save it in ctor.
211 * Update relevant stats via this pointer in allocate() and deallocate(). Use `atomic<>` and
212 * `flow::util::stat::fetch_add()` et al.
213 * - Corner case: if constructed with util::OPEN_ONLY tag and `read_only = true`, then `find_or_construct()`
214 * (which requires a mutex lock) is not available (forbidden by OS). We must therefore use
215 * `find_no_lock()`. Moreover we tell the user in the ctor doc header that they must ensure
216 * the pool has been created (via a creating ctor) before invoking the `read_only` ctor. Full atomicity
217 * is lost. In practice, though, it is common in a SHM system to only publicize a pool-name after
218 * fully creating/initializing the pool.
219 * - Per-`*this`: Simply store as a regular data member stats-`struct`. Otherwise the same as the previous
220 * bullet.
221 */
223 public flow::log::Log_context,
224 private boost::noncopyable
225{
226public:
227 // Types.
228
229 /**
230 * SHM-storable fancy-pointer. See class doc header for discussion. Suitable for shm::stl allocator(s).
231 *
232 * @tparam T
233 * The pointed-to type. `Pointer<T>` acts like `T*`.
234 */
235 template<typename T>
236 using Pointer = ::ipc::bipc::offset_ptr<T>;
237
238 /**
239 * Outer handle to a SHM-stored object; really a regular-looking `shared_ptr` but with custom deleter
240 * that ensures deallocation via Pool_arena as well as cross-process ref-count semantics.
241 * See class doc header and construct(). A handle can also be lent/borrowed between processes;
242 * see lend_object() and borrow_object().
243 *
244 * ### Rationale ###
245 * Why have an alias, where it's a mere `shared_ptr`? Two-fold reason:
246 * - (Mostly) While it *is* just `shared_ptr<T>`, and acts just as one would expect if borrow_object() and
247 * lend_object() are not involved, (1) it is an *outer* handle to a SHM-stored object, unlike the inner
248 * (subordinate) #Pointer values as maintained by STL-compliant logic or other data structure-internals code; and
249 * (2) it has special (if hopefully quite intuitive) capabilities of invoking ref-counting "across" processes
250 * via lend_object() and borrow_object().
251 * - (Minor) It's nice not to visibly impose a particular `shared_ptr` impl but kinda hide it behind an
252 * alias. Ahem....
253 *
254 * @tparam T
255 * The pointed-to type. Its dtor, informally, should ensure any inner deallocations subordinate
256 * to the managed `T` are performed before the `shared_ptr` reaches ref-count 0 in all processes
257 * to get the handle. See class doc header.
258 */
259 template<typename T>
260 using Handle = boost::shared_ptr<T>;
261
262 /// Convenience alias for a shm::stl::Arena_activator w/r/t Pool_arena.
264
265 /**
266 * Convenience alias for a shm::stl::Stateless_allocator> w/r/t Pool_arena; use with #Activator.
267 *
268 * @tparam T
269 * Pointed-to type for the allocator. See standard C++ `Allocator` concept.
270 */
271 template<typename T>
273
274 /**
275 * Alias for a light-weight blob. They're little; TRACE-logging of deallocs and copies is of low value;
276 * otherwise this can be switched to `flow::util::Blob`.
277 */
278 using Blob = flow::util::Blob_sans_log_context;
279
280 /// The boost.interprocess memory-algorithm used by the pools we set up.
281 using Mem_algo = ::ipc::bipc::rbtree_best_fit<::ipc::bipc::mutex_family>;
282
283 /// Alias for a stats/info bundle type.
285
286 // Constructors/destructor.
287
288 /**
289 * Construct Pool_arena accessor object to non-existing named SHM pool, creating it first.
290 * If it already exists, it is an error. If an error is emitted via `*err_code`, methods shall return
291 * sentinel/`false` values.
292 *
293 * @param logger_ptr
294 * Logger to use for subsequently logging.
295 * @param pool_name
296 * Absolute name at which the persistent SHM pool lives.
297 * @param mode_tag
298 * API-choosing tag util::CREATE_ONLY.
299 * @param perms
300 * Permissions to use for creation. Suggest the use of util::shared_resource_permissions() to translate
301 * from one of a small handful of levels of access; these apply almost always in practice.
302 * The applied permissions shall *ignore* the process umask and shall thus exactly match `perms`,
303 * unless an error occurs.
304 * @param pool_sz
305 * The value to be returned by arena_size(). See potentially non-trivial notes on that method,
306 * particularly regarding the viability of setting this to a large value + effect thereof on actual RAM
307 * use over time.
308 * @param err_code
309 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
310 * various. Most likely creation failed due to permissions, or it already existed.
311 */
312 explicit Pool_arena(flow::log::Logger* logger_ptr, const Shared_name& pool_name,
313 util::Create_only mode_tag, size_t pool_sz,
314 const util::Permissions& perms = {}, Error_code* err_code = nullptr);
315
316 /**
317 * Construct Pool_arena accessor object to non-existing named SHM pool, or else if it does not exist creates it
318 * first and opens it (atomically). If an error is emitted via `*err_code`, methods shall return
319 * sentinel/`false` values.
320 *
321 * @param logger_ptr
322 * Logger to use for subsequently logging.
323 * @param pool_name
324 * Absolute name at which the persistent SHM pool lives.
325 * @param mode_tag
326 * API-choosing tag util::OPEN_OR_CREATE.
327 * @param perms_on_create
328 * Permissions to use for creation. Suggest the use of util::shared_resource_permissions() to translate
329 * from one of a small handful of levels of access; these apply almost always in practice.
330 * The applied permissions shall *ignore* the process umask and shall thus exactly match `perms_on_create`,
331 * unless an error occurs.
332 * @param pool_sz
333 * The value to be returned by arena_size() (ignored unless creation occurs). See potentially non-trivial
334 * notes on that method, particularly regarding the viability of setting this to a large value + effect
335 * thereof on actual RAM use over time.
336 * @param err_code
337 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
338 * various. Most likely creation failed due to permissions.
339 */
340 explicit Pool_arena(flow::log::Logger* logger_ptr, const Shared_name& pool_name,
341 util::Open_or_create mode_tag, size_t pool_sz,
342 const util::Permissions& perms_on_create = util::Permissions{}, Error_code* err_code = nullptr);
343
344 /**
345 * Construct Pool_arena accessor object to existing named SHM pool. If it does not exist, it is an error.
346 * If an error is emitted via `*err_code`, methods shall return sentinel/`false` values.
347 *
348 * @warning Be careful with `read_only = true`. See the requirements and notes below. Furthermore note that
349 * it is wrong to rely on error::Code::S_SHM_POOL_OPEN_READ_ONLY_FOUND_BUT_UNINIT as merely a safe
350 * eventuality like pool-name-not-found. That is: if it is indeed emitted, you only got lucky; it is
351 * possible that, instead, the lockless internal check for initialized-state *does* pass, but the
352 * internal metadata are in some halfway state. Instead be sure to complete a creating-ctor invocation
353 * (in any process) before attempting this ctor form.
354 *
355 * @param logger_ptr
356 * Logger to use for subsequently logging.
357 * @param pool_name
358 * Absolute name at which the persistent SHM pool lives.
359 * @param mode_tag
360 * API-choosing tag util::OPEN_ONLY.
361 * @param read_only
362 * If and only if `true` the calling process will be prevented by the OS from writing into the pages
363 * mapped by `*this` subsequently. Such attempts will lead to undefined behavior (Linux: segmentation fault).
364 * Note that this includes any attempt at allocating as well as writing into allocated (or otherwise)
365 * address space. Further note that, internally, deallocation -- directly or otherwise -- involves
366 * (in this implementation) writing and is thus also disallowed. Moreover, and quite significantly,
367 * borrow_object() cannot be called. Therefore it is up to you, in that
368 * case, to (1) never call deallocate() directly or otherwise (i.e., through an allocator);
369 * and (2) to design your algorithms in such a way as to never require lending to this Pool_arena.
370 * In practice this would be quite a low-level, stunted use of `Pool_arena` across 2+ processes;
371 * but it is not necessarily useless. (There might be, say, test/debug/reporting use cases. In
372 * particular it's possible to read arena_stats() which is a clean use-case. See arena_stats() doc header.)
373 * Lastly, a caution: Do not use this ctor form (with `read_only = true`) until you know `pool_name`
374 * has been fully created/initialized via a creating ctor.
375 * @param err_code
376 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
377 * various. Most likely `pool_name` could not be found, or an access control error, or --
378 * with `read_only = true` only -- error::Code::S_SHM_POOL_OPEN_READ_ONLY_FOUND_BUT_UNINIT.
379 * (If you see the latter you are not using the read-only ctor form properly as indicated above;
380 * per the warning above undefined behavior could have occurred instead.)
381 */
382 explicit Pool_arena(flow::log::Logger* logger_ptr, const Shared_name& pool_name,
383 util::Open_only mode_tag, bool read_only = false, Error_code* err_code = nullptr);
384
385 /**
386 * Destroys Pool_arena accessor object. In and of itself this does not destroy the underlying pool named
387 * #m_pool_name; it continues to exist as long as (1) any other similar accessor objects (or other OS-created
388 * handles) do; and/or (2) its entry in the file system lives (hence until remove_persistent() is called
389 * for #m_pool_name). This is analogous to closing a descriptor to a file.
390 */
391 ~Pool_arena();
392
393 // Methods.
394
395 /**
396 * Removes the named SHM pool object. The name `name` is removed from the system immediately; and
397 * the function is non-blocking. However the underlying pool if any continues to exist until all handles
398 * (accessor objects Pool_arena or other OS-created handles) to it are closed; their presence in this or other
399 * process is *not* an error. See also dtor doc header for related notes.
400 *
401 * @note The specified pool need not have been created via a Pool_arena object; it can be any pool
402 * created by name ultimately via OS `shm_open()` or equivalent call. Therefore this is a utility
403 * that is not limited for use in the ipc::shm::classic context.
404 * @see `util::remove_each_persistent_*`() for a convenient way to remove more than one item. E.g.,
405 * `util::remove_each_persistent_with_name_prefix<Pool_arena>()` combines remove_persistent() and
406 * for_each_persistent() in a common-sense way to remove only those `name`s starting with a given prefix;
407 * or simply all of them.
408 *
409 * Trying to remove a non-existent name *is* an error.
410 *
411 * Logs INFO message.
412 *
413 * @param logger_ptr
414 * Logger to use for subsequently logging.
415 * @param name
416 * Absolute name at which the persistent SHM pool lives.
417 * @param err_code
418 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
419 * various. Most likely it'll be a not-found error or permissions error.
420 */
421 static void remove_persistent(flow::log::Logger* logger_ptr, const Shared_name& name,
422 Error_code* err_code = nullptr);
423
424 /**
425 * Lists all named SHM pool objects currently persisting, invoking the given handler synchronously on each one.
426 *
427 * Note that, in a sanely set-up OS install, all existing pools will be listed by this function;
428 * but permissions/ownership may forbid certain operations the user may typically want to invoke on
429 * a given listed name -- for example remove_persistent(). This function does *not* filter-out any
430 * potentially inaccessible items.
431 *
432 * @note The listed pools need not have been created via Pool_arena objects; they will be all pools
433 * created by name ultimately via OS `shm_open()` or equivalent call. Therefore this is a utility
434 * that is not limited for use in the ipc::shm::classic context.
435 *
436 * @tparam Handle_name_func
437 * Function object matching signature `void F(const Shared_name&)`.
438 * @param handle_name_func
439 * `handle_name_func()` shall be invoked for each item. See `Handle_name_func`.
440 */
441 template<typename Handle_name_func>
442 static void for_each_persistent(const Handle_name_func& handle_name_func);
443
444 /**
445 * Commits (takes RAM for) every page of the pool not yet committed, so that subsequent use of the pool cannot fail
446 * for lack of RAM. Returns `false` without doing anything if no pool is
447 * attached to `*this`; or after close_shm_object_handle(). Otherwise returns `true`.
448 *
449 * @see class doc header section on RAM use.
450 *
451 * It can be called on any `*this` accessing the pool, regardless of whether it created the pool (except one opened
452 * with `read_only`: that may emit an error), at any time, any number of times. A repeat call does nothing unless
453 * pages were decommitted since (holes punched, e.g., in Linux via `madvise(MADV_REMOVE)` on the mapping).
454 *
455 * Be ready for it to fail when the attempt would exceed a system limit on RAM use (Linux: the configurable size
456 * limit of the tmpfs underlying POSIX SHM; might default to half of physical RAM or, in containers, something like
457 * 64Mi bytes). In that case the pool remains as it was (sparse) and fully usable; and commit() may
458 * be retried later.
459 *
460 * Logs WARNING on error; other at most TRACE-logs.
461 *
462 * @param err_code
463 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
464 * various. Most likely it'll be the no-space-left error described above: this would likely be a
465 * `no_space_on_device` code.
466 * @return `true` if the operation was attempted (even if `*err_code` came out truthy!);
467 * `false` if no-op (see above: ctor failed to attach a pool; or already did close_shm_object_handle()).
468 */
469 bool commit(Error_code* err_code = nullptr);
470
471 /**
472 * Closes the pool's OS handle (FD) that `*this` keeps open only to enable commit(), returning that resource to the
473 * system; `*this` remains otherwise fully operational, except that commit() shall subsequently no-op. Returns
474 * `false` without doing anything if no pool is attached to `*this`. Idempotent.
475 *
476 * Once you know you won't commit() (ever or subsequently), it is reasonable to call this, if FDs are
477 * a concern as a resource.
478 *
479 * @return `false` if ctor failed to attach a pool; else `true` (also logs INFO message).
480 */
482
483 /**
484 * Allocates buffer of specified size, in bytes, in the accessed pool; returns locally-dereferenceable address
485 * to the first byte. Returns null if and only if no pool attached to `*this`. Throws on any other
486 * error (details below).
487 *
488 * Take care to only use this when and as appropriate; see class doc header notes on this.
489 *
490 * ### On running out of space ###
491 * If there is no space for `n` bytes and associated book-keeping: Throws exception `std::bad_alloc`.
492 *
493 * @note Attention: that's `std::` and not `boost::interprocess::`. This is intended to be consistent across
494 * SHM-providers like SHM-classic and SHM-jemalloc (jemalloc::Ipc_arena::allocate()).
495 *
496 * It is salient that an allocator's (e.g., our stl::Stateless_allocator) `allocate()` may forward to us and is
497 * allowed to "throw exceptions" (no particular exception type is mandated or recommended). So we are
498 * behaving consistently with letting an allocator's `allocate()` simply forward to this method (of some `*this`)
499 * and let it throw. Assuming one operates on an active `*this` (that did not fail construction), there is no
500 * need to check for null return.
501 *
502 * ### Other errors ###
503 * They throw exceptions. All are out of the mainstream. (One we know of as of this writing is
504 * a bipc `lock_exception`, probably if a process crashed while holding the in-SHM-pool lock.)
505 *
506 * @param n
507 * Desired buffer size in bytes. Must not be 0 (behavior undefined/assertion may trip).
508 * @return Non-null on success (see above); null if ctor failed to attach pool.
509 */
510 void* allocate(size_t n);
511
512 /**
513 * Undoes effects of local allocate() that returned `buf_not_null`; or another-process's
514 * allocate() that returned pointer whose locally-dereferenceable equivalent is `but_not_null`.
515 * Returns `false` if and only if no pool attached to `*this`. Does not throw exception. Behavior is
516 * undefined if `buf_not_null` is not as described above; in particular if it is null.
517 *
518 * @param buf_not_null
519 * See above.
520 * @return `true` on success; `false` if ctor failed to attach a pool.
521 */
522 bool deallocate(void* buf_not_null) noexcept;
523
524 /**
525 * Constructs an object of given type with given ctor args, having allocated space directly in attached
526 * SHM pool, and returns a ref-counted handle that (1) guarantees destruction and deallocation shall occur
527 * once no owners hold a reference; and (2) can be lent to other processes (and other processes still
528 * indefinitely), thus adding owners beyond this process, via lend_object()/borrow_object().
529 * Returns null if no pool attached to `*this`. Throws on any other error (details below).
530 *
531 * Is better to use this than allocate() whenever possible; see class doc header notes on this.
532 *
533 * Note that that there is no way to `construct()` a native array. If that is your aim please use
534 * `T = std::array<>` or similar.
535 *
536 * ### On running out of space (or otherwise failing) in `allocate(sizeof(T) + ...)` ###
537 * The first step in this method is to allocate the outer-layer buffer sized for the new `T` itself along with
538 * some internal book-keeping bytes. If this fails: the method throws as allocate().
539 *
540 * @see allocate() doc header which describes the relevant throwing semantics.
541 *
542 * ### If `T` constructor throws ###
543 * The thrown exception propagates to our caller upon undoing the `allocate(sizeof(T) + ...)` preceding the
544 * ctor call.
545 *
546 * Corollary + context: A particular sub-case of this is that, while executing `T` ctor, a subordinate --
547 * typically/recommendedly via allocator-furnished container-containing type `T` -- `allocate()` threw.
548 * We guarantee that our `allocate(sizeof(T) + ...)` is undone. In addition: A properly coded (at all nesting-layers)
549 * container-containing type `T`, using (at all nesting-layers) a proper SHM-supporting allocator shall undo all
550 * `allocate()`s that had succeeded up to the one that failed. This method itself can only take care of its
551 * own `allocate()`, which as just noted it does.
552 *
553 * ### Integration with shm::stl::Stateless_allocator ###
554 * This method, bracketing the invocation of the `T` ctor, sets the thread-local
555 * `shm::stl::Arena_activator<Pool_arena>` context to `this`. Therefore the caller need not do so.
556 * If `T` does not store an STL-compliant structure that uses `Stateless_allocator`, then this is harmless
557 * albeit a small perf hit (also skipped for all trivially-destructible `T`). If `T` does do so, then it is a
558 * convenience.
559 *
560 * Arguably more importantly: The returned `shared_ptr` is such that when garbage-collection of the created
561 * data structure does occur -- which may occur in this process, but via lend_object() and borrow_object()
562 * may well occur in another process -- the `T::~T()` *dtor* call shall also be bracketed by the aforementioned
563 * context. Again: If `T` does not rely on `Stateless_allocator`, then it's harmless; but if it *does*, then
564 * doing this is quite essential. That is because the user cannot, typically (or at least sufficiently easily),
565 * control the per-thread allocator context at the time of dtor call -- simply because who knows who or what
566 * will be running when the cross-process ref-count reaches 0.
567 *
568 * @tparam T
569 * Object type. See class doc header for discussion on appropriate properties of `T`.
570 * Short version: PODs work; STL nested container+POD combos work, as long as
571 * a shm::stl allocator is used at all levels; manually-implemented non-STL-compliant data
572 * structures work if care is taken to use allocate() and #Pointer.
573 * @tparam Ctor_args
574 * `T` ctor arg types.
575 * @param ctor_args
576 * 0 or more args to `T` constructor.
577 * @return Non-null on success; `nullptr` if ctor failed to attach a pool.
578 */
579 template<typename T, typename... Ctor_args>
580 Handle<T> construct(Ctor_args&&... ctor_args);
581
582 /**
583 * Adds an owner process to the owner count of the given construct()-created handle, and returns
584 * an opaque blob, such that if one passes it to borrow_object() in the receiving process, that borrow_object()
585 * shall return an equivalent #Handle in that process. The returned `Blob` is guaranteed to have non-zero
586 * size that is small enough to be considered very cheap to copy; in particular internally as of this writing
587 * it is a `ptrdiff_t`. Returns `.empty()` object if no pool attached to `*this`.
588 *
589 * It is the user's responsibility to transmit the returned blob, such as via a transport::Channel or any other
590 * copying IPC mechanism, to the borrowing process and there pass it to `x->borrow_object()` (where `x`
591 * is a Pool_arena accessing the same-named SHM pool as `*this`). Failing to do so will leak the object until
592 * remove_persistent(). That borrowing process dying without running #Handle dtor(s) on #Handle returned
593 * by `x` borrow_object() will similarly leak it until remove_persistent().
594 *
595 * @tparam T
596 * See construct().
597 * @param handle
598 * Value returned by construct() (lending from original allocating process) or borrow_object() (proxying); or
599 * copied/moved therefrom. Note this is a mere `shared_ptr<T>` albeit with unspecified custom deleter
600 * logic attached. See #Handle doc header.
601 * @return See above. `.empty()` if and only if ctor failed to attach a pool.
602 */
603 template<typename T>
604 Blob lend_object(const Handle<T>& handle);
605
606 /**
607 * Completes the cross-process operation begun by lend_object() that returned `serialization`; to be invoked in the
608 * intended new owner process. Returns null if no pool attached to `*this`, or if we detect `serialization`
609 * to be invalid (best-effort check).
610 *
611 * Consider the only 2 ways a user may obtain a new #Handle to a `T` from `*this`:
612 * - construct(): This is allocation by the original/first owner of the `T`.
613 * - borrow_object(), after lend_object() was called on a previous #Handle in another process, acquired *there*
614 * however it was acquired:
615 * - Acquired via construct(): I.e., the original/first owner lent to us. I.e., it's the original loan.
616 * - Acquired via another borrow_object(): I.e., it was itself first borrowed from another. I.e., it's a loan
617 * by a lender a/k/a *proxying*.
618 *
619 * ### Integration with shm::stl::Stateless_allocator ###
620 * Crucially, the 2nd paragraph of similarly named section of construct() doc header -- where it speaks of
621 * applying `Stateless_allocator` context around dtor call possibly invoked by returned handle's deleter --
622 * applies exactly equally here. Please read it.
623 *
624 * @tparam T
625 * See lend_object().
626 * @param serialization
627 * Value, not `.empty()`, returned by lend_object() and transmitted bit-for-bit to this process.
628 * @return Non-null on success; `nullptr` if ctor failed to attach a pool, or if `serialization` is invalid
629 * (best-effort check).
630 */
631 template<typename T>
632 Handle<T> borrow_object(const Blob& serialization);
633
634 /**
635 * Returns `true` if and only if `handle` came from either `this->construct<T>()` or `this->borrow_object<T>()`.
636 * Another way of saying that is: if and only if `handle` may be passed to `this->lend_object<T>()`.
637 * (The words "came from" mean "was returned by or is a copy/move of one that was," or
638 * equivalently "belongs to the ref-counting pointer group of one that was returned by.")
639 *
640 * @param handle
641 * An object, or copy/move of an object, returned by `construct<T>()` or `borrow_object<T>()`
642 * of *a* Pool_arena (not necessarily `*this`).
643 * @return See above. (Corner case: If ctor failed to attach a pool, we return `false`.)
644 */
645 template<typename T>
646 bool is_handle_in_arena(const Handle<T>& handle) const;
647
648 // Stats-et-al methods.
649
650 /**
651 * Arena (in this case, arena=pool) size in bytes as configured at arena/pool creation; never changes.
652 * This shall equal `pool_sz` given to the `Create_only` or `Open_or_create` ctor overload;
653 * but recall that that may or may not have been `*this` particular `Pool_arena` ctor -- but the one that
654 * actually was the first to in fact create the SHM-pool.
655 *
656 * @note OS, namely Linux, shall not in fact take (necessarily) this full amount from general
657 * availability but rather a small amount. Chunks of RAM (pages) shall be later reserved as they begin to
658 * be used, namely via the allocation API. It may be viable to set this to a quite large value to
659 * avoid running out of pool space.
660 * @note It is not named `stat_arena_size()` to indicate it's more of a config value rather than a dynamically-moving
661 * stat. See also arena_stat_free_size().
662 *
663 * @return See above. (Corner case: If ctor failed to attach a pool, we return zero.)
664 */
665 size_t arena_size() const;
666
667 /**
668 * How much of arena_size() is currently available for allocations; that is, not currently used by currently
669 * allocated items/internally required small metadata.
670 *
671 * Perf: Internally, it forwards to a boost.ipc accessor which, as of this writing, is doing an unsynchronized
672 * load. Basically: a normal memory read of small (safe against corruption from concurrency) datum -- of a datum
673 * that can change concurrently (due to allocation/deallocations).
674 *
675 * Corollary: Use only as a guide for logging/reporting/monitoring. It may not be perfectly coherent among threads,
676 * and so on.
677 *
678 * @note As with arena_size(), do not treat this as a reflection of general RAM availability. The free-size
679 * reported here may include pages currently available for general use (not yet touched by preceding,
680 * now-freed allocations et al) and ones now reserved for this SHM-pool (touched previously but now
681 * not-allocated), or an unknown mix thereof.
682 *
683 * @return See above. (Corner case: If ctor failed to attach a pool, we return zero.)
684 */
685 size_t arena_stat_free_size() const;
686
687 /**
688 * Cumulative stats relevant to one SHM-classic arena -- in this case single SHM-pool in shared memory, accessed
689 * via 1+ Pool_arena objects (like this one) over time. (Returns null in a pathological corner case; see below.)
690 * A few interrelated, essential points about the returned value and its pointee:
691 * - The returned value is a pointer into something stored in-SHM, and since `*this` guarantees the mapping
692 * to that SHM area (pool), the pointee shall remain valid through `*this` lifetime; after that accessing
693 * it is undefined behavior.
694 * - The individual data members (a/k/a stats, stat-members) are all `atomic`s or essentially atomic in nature
695 * (the latter = any `Histogram_counter`s) and may change at any time. Please see `flow::util::stat` doc
696 * header for background on atomic, concurrently-changing stats for a full picture; also about
697 * `flow::util::stat::stat[_set]_*()` utilities that help consume them. The highlights:
698 * - Again, they can change at any time, so there can be a slight mutual incoherence between any two values
699 * one grabs (by dereferencing returned pointer) even in immediate succession.
700 * - For best performance -- though admittedly typically a stats-accessor like this is assumed not to be
701 * called frequently enough to matter -- the `atomic` fields should be accessed via
702 * `util::stat::load()`.
703 * - If `S` is the returned pointer, it may be useful to give `*S` as the source arg to
704 * `flow::util::stat::stats_assign()` and/or `ostream << flow::util::stat::print()` (et al).
705 * The latter and/or `stat::stats_to_ostream()` pretty-print the stats.
706 * - Tip: If also grabbing/printing local_stats(): Suggest identifying arena_stats() and local_stats()
707 * with a disambiguating qualifier like `"arena[]"` and `"local[]"` respectively. Some fields may
708 * look ~duplicate otherwise and thus confuse people.
709 *
710 * A use-case for the `OPEN_ONLY`/`read_only = true` ctor form: One can monitor arena_stats() through such
711 * a Pool_arena object all while ensuring at the OS-level that it is impossible to modify the pool's contents
712 * through it. So it becomes potentially a pure/safe stats-observer; pretty clean.
713 *
714 * @see local_stats() which tracks stats relevant to `*this` particular Pool_arena *object*, as opposed to
715 * arena_stats() that tracks events in the in-SHM arena regardless of Pool_arena object.
716 * @note Does not include arena_size() or arena_stat_free_size().
717 *
718 * @return See above. (Corner case: If ctor failed to attach a pool, we return null.)
719 */
720 const stat::Arena_stats* arena_stats() const;
721
722 /**
723 * Resets arena_stats(). The formal meaning of a reset is discussed in `flow::util::stat` doc header.
724 *
725 * @note Does not affect arena_size() or arena_stat_free_size().
726 *
727 * ### Thread safety ###
728 * Generally safe to call not just versus any API on `*this` but also on any other Pool_arena's referring to
729 * the same SHM-pool. (arena_stats() called concurrently will return potentially some values pre-reset and some
730 * values post-; but we consider that known and safe behavior under concurrent stat updates.)
731 *
732 * There is potentially some question whether it is safe to call concurrently with arena_stats_reset() also
733 * being called (whether on `*this` or other same-pool-pointing Pool_arena). It is safe as of this writing,
734 * because `flow::util::stat::stats_reset()` is formally documented as safe if called properly (which we,
735 * internally, do).
736 *
737 * Informally, though, we recommend against calling this method during itself on the same SHM-pool/arena;
738 * and somewhat less strongly but also recommend against calling it concurrently with arena_stats().
739 * Doing so suggests a potentially unpleasantly behaving application w/r/t stat consumption and/or may make
740 * reasoning about and maintenance of the application more difficult.
741 */
742 void arena_stats_reset();
743
744 /**
745 * Cumulative stats relevant to `*this` Pool_arena *object* -- a Pool_arena -- and excluding those
746 * in arena_stats() which track the underlying in-SHM arena (pool) itself.
747 *
748 * Notes in arena_stats() doc header apply here, except that:
749 * - we return a ref, not a pointer (but still to `const`); and
750 * - the pointee lives simply in `*this`, not in SHM (shared memory).
751 *
752 * @return See above. (Corner case: If ctor failed to attach a pool, the returned pointee shall simply
753 * hold only initial -- zero -- values forever.)
754 */
755 const stat::Local_stats& local_stats() const;
756
757 /**
758 * Resets local_stats(). The formal meaning of a reset is discussed in `flow::util::stat` doc header.
759 *
760 * ### Thread safety ###
761 * All thread-safety notes in arena_stats_reset() doc header apply here, except that `this->local_stats_reset()`
762 * under-concurrency behavior is only controversial against other `this->` API calls; other `Pool_arena`s
763 * maintain their own local_stats().
764 */
765 void local_stats_reset();
766
767 /**
768 * Fills-out the stats/info contents of the given stat::Arena_info_dump: a printable bundling of all stats/info
769 * pertaining to `*this` Pool_arena at this point in time. To summarize the resulting `*target_info_dump`:
770 * - Stats/info stored by-value; can be queried in peace. They won't concurrently change nor become invalid
771 * when `*this` Pool_arena is destroyed.
772 * - Can be printed to an `ostream` via `ostream<<`. `->m_fmt` (`Info_dump_format`) has output-format knobs,
773 * adjusting which ahead of the `<<` will affect the output.
774 * - As usual: can `FLOW_LOG_...(*target_info_dump)`, `boost::lexical_cast<string>(*target_info_dump)`,
775 * `flow::util::ostream_op_string(*target_info_dump)`.
776 *
777 * ### Rationale ###
778 * This is a "get me everything this-arena-related, all in one nicely-printable thing" operation. For
779 * finer-grained access to the same information, use the individual accessors -- arena_stats(), local_stats(),
780 * arena_size(), arena_stat_free_size() -- each of which the corresponding stat::Arena_info_dump member documents
781 * as its source. (The stat-sets can also be `<< print(...)`ed individually and post-processed via
782 * `flow::util::stat` utilities; the reset ops arena_stats_reset() and local_stats_reset() are likewise available.)
783 *
784 * @param target_info_dump
785 * The non-`->m_fmt` parts shall be assigned.
786 */
787 void info_dump(Info_dump* target_info_dump) const;
788
789 // Data.
790
791 /// SHM pool name as set immutably at construction.
793
794private:
795 // Types.
796
797 /**
798 * The SHM pool type one instance of which is managed by `*this`.
799 * It would be possible to parameterize this somewhat further, such as specifying different allocation algorithms
800 * or speed up perf in single-thread situations. See class doc header for discussion. It is not a formal
801 * to-do yet.
802 *
803 * We use, in a very limited way and only for internal purposes, the
804 * atomic-construct/find-object-in-SHM-pool feature of the `bipc::managed_shared_memory`-like `Pool::core()`.
805 * Insertion occurs only at init, so `flat_map_index` is fine. If we stop needing the feature, replace with
806 * `null_index` to save some RAM.
807 *
808 * Notice that #Mem_algo shall use an (in-SHM) mutex around the meat of allocate() and deallocate().
809 *
810 * It is a bipc_ext::Sparse_managed_shm, as opposed to `bipc::managed_shared_memory`, so that the pool is
811 * sparse (takes RAM page by page as touched) rather than fully committed at creation; see its doc header.
812 */
814
815 /**
816 * The data structure stored in SHM corresponding to an original construct()-returned #Handle;
817 * exactly one of which exists per construct() call invoked from any Pool_arena connected to the underlying pool.
818 * It is created in construct() and placed in the SHM pool. It is destroyed once its #m_atomic_owner_ct
819 * reaches 0, which occurs once the last process for which a #Handle (`shared_ptr`) group ref-count reaches 0
820 * detects this fact in its custom deleter and internally invokes deallocate() for the buffer,
821 * wherein the Handle_in_shm resides.
822 *
823 * @tparam T
824 * The `T` from the associated #Handle.
825 */
826 template<typename T>
828 {
829 // Types.
830
831 /**
832 * Atomically accessed count of each time the following events occurs for a given Handle_in_shm in
833 * the backing pool:
834 * - initial construction via construct(), which returns the original #Handle and creates
835 * the Handle_in_shm;
836 * - each call to lend_object() on that #Handle (internally, on the associated Handle_in_shm);
837 * - *minus* any corresponding invocations of the #Handle custom deleter.
838 */
839 using Atomic_owner_ct = std::atomic<uint32_t>;
840
841 // Data.
842
843 /**
844 * The constructed object; `Handle::get()` returns `&m_obj`. This must be the first member in
845 * Handle_in_shm, because the custom deleter `reinterpret_cast`s `Handle::get()` to mean `Handle_in_shm*`.
846 * If this arrangement is modified, one would need to use `offsetof` (or something) as well.
847 */
849
850 /// See #Atomic_owner_ct doc header. This value is 1+; once it reaches 0 `*this` is destroyed in SHM.
852
853 /**
854 * Process ID of the process that performed the Pool_arena::construct() that resulted in `*this` Handle_in_shm;
855 * namely from Pool_arena::m_own_process_id.
856 *
857 * Used at least for stat-keeping, such as to determine whether to increment
858 * Owner_obj_stats::m_non_owner_destroy_count when deleting `*this` object (when #m_atomic_owner_ct reaches 0).
859 */
861 }; // struct Handle_in_shm
862
863 /**
864 * Singleton-in-SHM-pool structure wherein we store a small amount of information, accessible by any Pool_arena
865 * pointing to the same-named SHM-pool, for our internal purposes. Since it's a singleton (per SHM-pool),
866 * we use bipc's `find_or_construct()(unique_instance)` feature.
867 *
868 * The precipitating use case was the storage of per-arena (as opposed to per-`*this`) stats. E.g.,
869 * something like Live_obj_stats::m_live_objects -- arena-wide extant `construct()`ed object count -- has
870 * to live in SHM while not being placed there for the user's direct access.
871 *
872 * So far it's the only thing we need to store like that, and it's a simple `struct`; so we just use an
873 * alias to it (Arena_stats).
874 *
875 * ### Performance as used for Arena_stats specifically ###
876 * The basic approach is the same as that used for #m_local_stats; namely non-TL-sharding
877 * concurrent-`atomic`-updates approach described in `flow::util::stat` doc header. It is intensified here,
878 * though, as #Arena_metadata sits in SHM with protential updates from multiple processes and more than the
879 * threads that deal with a particular `*this` alone.
880 *
881 * We consider it acceptable for the same reasons stated in #m_local_stats doc header. Admittedly, again,
882 * it is more intense for Arena_stats, as the contention is cross-`Pool_arena`/cross-process here.
883 * However the same reasoning applies; if this is a problem, then the allocate() and deallocate() central
884 * mutex (inside bipc code) is a much worse problem; and if the latter is fine, then the `atomic` adds/subs
885 * are more fine still.
886 */
888
889 // Constructors.
890
891 /**
892 * Helper ctor delegated by the 2 `public` ctors that take `Open_or_create` or `Create_only` mode.
893 *
894 * @tparam Mode_tag
895 * Either util::Open_or_create or util::Create_only.
896 * @param logger_ptr
897 * See `public` ctors.
898 * @param pool_name
899 * See `public` ctors.
900 * @param mode_tag
901 * See `public` ctors.
902 * @param pool_sz
903 * See `public` ctors.
904 * @param perms
905 * See `public` ctors.
906 * @param err_code
907 * See `public` ctors.
908 */
909 template<typename Mode_tag>
910 explicit Pool_arena(Mode_tag mode_tag, flow::log::Logger* logger_ptr, const Shared_name& pool_name, size_t pool_sz,
911 const util::Permissions& perms, Error_code* err_code);
912
913 // Methods.
914
915 /**
916 * Helper of ctors: with the assumption that #m_pool is ready, sets up #m_arena_metadata if possible. Throws bipc
917 * exception on failure, re-nullifying #m_pool first. (As of Boost-1.87 the only conceivable errors are, we feel,
918 * failure to lock mutex -- other process crashed while holding it -- or the pool being so tiny as to not
919 * fit #Arena_metadata + internal bipc index stuff. These are not possible if `read_only = true`.)
920 *
921 * If `read_only == true` an extra failure mode is possible: If the metadata has not been added in time to
922 * the underlying SHM-pool, then:
923 * - does not throw;
924 * - leaves `m_arena_metadata` null;
925 * - re-nullifies `m_pool`;
926 * - leaves any error emission/throwing/whatever to the caller ctor.
927 *
928 * @note That result (no throw, yet `m_pool` and `m_arena_metadata` become/remain null respectively) indicates
929 * the user did not heed the instruction to create `*this` only after successful creation of pool via
930 * other ctor elsewhere.
931 *
932 * @param read_only
933 * True if and only if the invoked ctor was `OPEN_ONLY` with `read_only = true`.
934 */
935 void init_arena_metadata(bool read_only);
936
937 /**
938 * Identical deleter for #Handle returned by both construct() and borrow_object(); invoked when a given process's
939 * (borrow_object() caller's) `shared_ptr` group reaches ref-count 0. It decrements Handle_in_shm::m_atomic_owner_ct;
940 * and if and only if that made it reach zero performs the opposite of what construct() did including:
941 * - runs `T::~T()` dtor;
942 * - deallocate().
943 *
944 * Otherwise (if `m_atomic_owner_ct` is not yet 0) does nothing further; other owners still remain.
945 *
946 * @param handle_state
947 * `Handle<T>::~Handle()` shall run, and if that made shared-ref-count reach 0, call
948 * `handle_deleter_impl(...that_ptr...)`. Since the #Handle returned by construct() and borrow_object()
949 * is really an alias-cted `shared_ptr<T>` to `shared_ptr<Handle_in_shm<T>>`, and Handle_in_shm::m_obj
950 * (of type `T`) is the first member in its type, those addresses are numerically equal.
951 * @param constructing_else_borrowing
952 * For stats: Whether this deleter (a/k/a disposer, particularly in stat doc headers) is being attached
953 * to a `construct()`-returned (`true`) or `borrow_object()`-returned (`false`) #Handle.
954 */
955 template<typename T>
956 void handle_deleter_impl(Handle_in_shm<T>* handle_state, bool constructing_else_borrowing);
957
958 /**
959 * Returns `true` if and only if the byte at `p` is within the bounds of the pool accessed by `*this`.
960 *
961 * Assumes #m_pool is non-null; else undefined behavior.
962 *
963 * @param p
964 * An address.
965 * @return See above.
966 */
967 bool is_addr_in_arena(const void* p) const;
968
969 /**
970 * Returns `true` if and only if all `sizeof(T)` bytes at address `obj` are within the bounds of the pool
971 * accessed by `*this`.
972 *
973 * Assumes #m_pool is non-null; else undefined behavior.
974 *
975 * @tparam T
976 * Object type; `sizeof(T)` is significant in the calculation, so this is not mere syntactic sugar.
977 * @param obj
978 * An address.
979 * @return See above.
980 */
981 template<typename T>
982 bool is_obj_in_arena(const T* obj) const;
983
984 // Data.
985
986 /// Process ID captured in ctor, at least for perf. Used for and in relation to Handle_in_shm::m_cting_process_id.
988
989 /// Attached SHM pool. If ctor fails in non-throwing fashion then this remains null. Immutable after ctor.
990 std::optional<Pool> m_pool;
991
992 /**
993 * Created or acquired in ctor, pointer to in-#m_pool (in-SHM) metadata singleton; see Arena_metadata doc
994 * header for description.
995 */
997
998 /**
999 * Per-`*this` (not per-arena-in-SHM) stats being accumulated, from multiple threads sans sharding.
1000 * Subject matter is discussed in the `struct`'s and its members' doc headers.
1001 * `flow::util::stat` namespace doc header discusses concurrent stat-keeping approaches includes the
1002 * aforementioned non-sharding-`atomic` technique.
1003 *
1004 * @see Owner_obj_stats doc header: Worth reading as background for per-`*this` and general SHM stat-keeping.
1005 *
1006 * ### Performance ###
1007 * Each first-class op (construct() + its disposer, lend_object(), borrow_object() + its disposer) updates a
1008 * few unsharded `atomic` fields here. Those calls can be executed concurrently. We consider this acceptable:
1009 * - Each op already does heavier work -- an internal allocation sequence in construct();
1010 * serialization in lend_object(); a cross-process atomic-dec on the in-SHM owner-count in the
1011 * disposer; and deallocation. The allocate() and deallocate() in there lock a pool-wide (not merely
1012 * `*this`-wide) internal mutex around the meat of their respective work. A handful of `atomic::fetch_add()`s
1013 * per call is dwarfed by that; and in the case where contention hypothetically causes these `atomic`-ops
1014 * to explode in cycle use, the aforementioned central lock would be far worse.
1015 * - #m_local_stats lives in `*this` only; the only competitors are threads in this process that
1016 * share `*this`; there is no cross-`Pool_arena` or cross-process contention. TL-sharding
1017 * would not pay back its complexity/cache cost here.
1018 */
1020}; // class Pool_arena
1021
1022// Free functions: in *_fwd.hpp.
1023
1024// Template implementations.
1025
1026template<typename T>
1028{
1029 /* Subtlety: This (public) method is not intended as a legitness check of `handle` but rather to indicate whether
1030 * the assumed-legit `handle` coming from *a* Pool_arena came from the pool (if any) to which *this refers. */
1031 return m_pool // Else couldn't open any pool in ctor.
1032 && is_addr_in_arena(static_cast<const void*>(handle.get()));
1033}
1034
1035template<typename T>
1036bool Pool_arena::is_obj_in_arena(const T* obj) const
1037{
1038 // Pre-requisite to this internal helper is: m_pool is non-null.
1039
1040 if (!is_addr_in_arena(static_cast<const void*>(obj)))
1041 {
1042 return false;
1043 }
1044 // else: First byte of obj is in range; hence ensure its last byte isn't past range's last byte.
1045
1046 // As in is_addr_in_arena(); avoid wraps via + while using known-non-negative-result subtractions.
1047 return sizeof(T)
1048 <= (arena_size() - (reinterpret_cast<uintptr_t>(obj)
1049 - reinterpret_cast<uintptr_t>(m_pool->address())));
1050}
1051
1052template<typename T, typename... Ctor_args>
1054{
1055 using Value = T;
1056 using Shm_handle = Handle_in_shm<Value>;
1057 using Atomic_owner_ct = typename Shm_handle::Atomic_owner_ct;
1058 using flow::util::stat::fetch_add;
1059 using flow::util::stat::update_hi_wmark;
1060 // using flow::util::construct_at; // C++20 => can conflict with incidentally included std:: counterpart.
1061 using boost::shared_ptr;
1062
1063 static_assert(alignof(Shm_handle) <= flow::util::max_align_sz(),
1064 "allocate() guarantees only std::max_align_t alignment; over-aligned T is not supported.");
1065
1066 if (!m_pool)
1067 {
1068 return Handle<Value>{};
1069 }
1070 // else
1071
1072 // No space => throws std::bad_alloc (or other problem=>exception) as promised. Nothing to clean; let it rip.
1073 const auto handle_state = static_cast<Shm_handle*>(allocate(sizeof(Shm_handle)));
1074
1075 // Buffer acquired but uninitialized. Construct the owner count to 1 (just us: no lend_object() yet).
1076 flow::util::construct_at(&handle_state->m_atomic_owner_ct, 1);
1077 handle_state->m_cting_process_id = m_own_process_id; // Just a regular (immutable after this) integer.
1078 // Construct the T itself. As advertised try to help out by setting selves as the current arena.
1079
1080 /* As in handle_deleter_impl() we too try to get a perf boost by not unnecessarily using an arena-activator.
1081 * After all it involves a thread-local variable assignment at the start and then another
1082 * at the end plus 1-2 more to remember the previous value;
1083 * it's quick but not nothing. It's trickier than the dtor situation though.
1084 * Ideally we'd determine something like "Value would not use an allocator to allocate something on its behalf."
1085 * There are some ideas, like maybe checking for the presence of tell-tale STL stuff... but it's tricky and
1086 * might be imperfect and thus arguably not worth it (@todo perhaps revisit). However: using
1087 * is_trivially_destructible_v<Value> here too is safe, even though it likely won't catch all the cases -- but
1088 * no false negatives, so it's safe. Basically if it's trivially destructible, it can never allocate things
1089 * on its behalf in any sane way; so that fits the bill. */
1090 try
1091 {
1092 if constexpr(std::is_trivially_destructible_v<Value>)
1093 {
1094 flow::util::construct_at(&handle_state->m_obj, std::forward<Ctor_args>(ctor_args)...);
1095 }
1096 else
1097 {
1098 Activator ctx{this};
1099 flow::util::construct_at(&handle_state->m_obj, std::forward<Ctor_args>(ctor_args)...);
1100 }
1101 }
1102 catch (...)
1103 {
1104 /* T ctor threw: before letting it propagate might as well undo what preceded it above (same steps as in
1105 * handle_deleter_impl(), minus ~T(), as ctor never completed) -- so as not to leak the buffer in SHM. */
1106 (handle_state->m_atomic_owner_ct).~Atomic_owner_ct();
1107 deallocate(static_cast<void*>(handle_state));
1108 throw;
1109 }
1110
1111 { // Stats.
1112 auto& arena_stats = *m_arena_metadata; // Just for expressiveness.
1113
1114 auto& live_obj_stats = arena_stats.m_live_obj;
1115 update_hi_wmark(&live_obj_stats.m_live_objects_hi_wmark,
1116 fetch_add(&live_obj_stats.m_live_objects, 1) + 1);
1117
1121
1122 fetch_add(&arena_stats.m_obj.m_construct_count, 1);
1123 } // Stats.
1124
1125 // Return alias shared_ptr whose .get() gives &m_obj but in reality aliases to the shared_ptr<Shm_handle>.
1126 return Handle<Value>{shared_ptr<Shm_handle>{handle_state,
1127 [this](Shm_handle* handle_state)
1128 { handle_deleter_impl<Value>(handle_state, true); }}, // Custom deleter.
1129 &handle_state->m_obj};
1130} // Pool_arena::construct()
1131
1132template<typename T>
1134{
1135 using Value = T;
1136 using Shm_handle = Handle_in_shm<Value>;
1137 using util::Blob_const;
1138 using flow::util::buffers_dump_string;
1139 using flow::util::stat::fetch_add;
1140
1141 if (!m_pool)
1142 {
1143 return {};
1144 }
1145 // else
1146
1147 const auto handle_state = reinterpret_cast<Shm_handle*>(handle.get());
1148 const auto new_owner_ct = handle_state->m_atomic_owner_ct.fetch_add(1, std::memory_order_relaxed) + 1;
1149 /* Quick refresher on atomic semantics; the above --^-- could be written, and basically means:
1150 * new_owner_ct = ++handle_state->m_atomic_owner_ct;
1151 * which is like what we wrote but with strictest-memory-ordering. Instead we use the least restrictive
1152 * memory-ordering: relaxed. It is fastest. Now, together with the other place where we touch this counter
1153 * post-init is handle_deleter_impl() (Handle disposer); we use something slightly different there (it's explained
1154 * there too), but that's not the point. Suppose for this explanation we used relaxed-memory-ordering there too.
1155 * Then, because we specifically only do classic ref-counter atomic math -- `++x` and `if (x-- == 1)` --
1156 * we get one pivotal property: There is a clean ordering of changes to x by all threads "seeing" x, and
1157 * ultimately exactly one disposer will "see" the `if` resolving to `true` (hence the destroy code will only run
1158 * there and nowhere else... and not simply nowhere). Loosely speaking: the counter will behave properly
1159 * with memory-order-relaxed everywhere -- which is also fastest. Great! And, here, that's all we care about:
1160 * it doesn't matter if some instructions w/r/t a competing thread get reordered around the .fetch_add(); we
1161 * just inc the ref-count, and that's that.
1162 *
1163 * In the disposer, around its .fetch_sub(), we do care about something else. That is handled in that code.
1164 *
1165 * Shorter version: This is a classic std::atomic ref-count pattern; e.g., boost::asio uses an identical
1166 * ref_count_up() + ref_count_down() implementation. At least the ++ part is totally uncontroversial. */
1167
1168 const ptrdiff_t offset_from_pool_base = reinterpret_cast<uintptr_t>(handle_state)
1169 - reinterpret_cast<uintptr_t>(m_pool->address());
1170
1171 Blob serialization{sizeof(offset_from_pool_base)};
1172 *(reinterpret_cast<ptrdiff_t*>(serialization.data())) = offset_from_pool_base;
1173
1174 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: Serializing SHM outer handle [" << handle << "] before "
1175 "IPC-transmission: Owning process-count incremented to [" << new_owner_ct << "] "
1176 "(may change concurrently). "
1177 "Handle points to SHM-offset [" << offset_from_pool_base << "] (serialized). Serialized contents are "
1178 "[\n" << buffers_dump_string(serialization.const_buffer(), " ") << "].");
1179
1180 { // Stats.
1181 auto& arena_stats = *m_arena_metadata; // Just for expressiveness.
1182
1184 fetch_add(&arena_stats.m_obj.m_lend_count, 1);
1185 }
1186
1187 return serialization;
1188} // Pool_arena::lend_object()
1189
1190template<typename T>
1192{
1193 using Value = T;
1194 using Shm_handle = Handle_in_shm<Value>;
1195 using flow::util::buffers_dump_string;
1196 using flow::util::stat::fetch_add;
1197 using flow::util::stat::update_hi_wmark;
1198 using boost::shared_ptr;
1199 using std::memcpy;
1200
1201 if (!m_pool)
1202 {
1203 return Handle<Value>{};
1204 }
1205 // else
1206
1207 ptrdiff_t offset_from_pool_base;
1208 if (serialization.size() != sizeof(offset_from_pool_base))
1209 {
1210 // For safety let's do real runtime check rather than a mere (often skipped in release builds) assert().
1211 FLOW_LOG_WARNING("SHM-classic pool [" << *this << "]: In attempt to deserialize SHM outer handle "
1212 "(type [" << typeid(Value).name() << "]) "
1213 "after IPC-receipt detected incorrect size [" << serialization.size() << "] of the serialized "
1214 "SHM-handle blob (expected: [" << sizeof(offset_from_pool_base) << "]). Borrow op fails. "
1215 "Was there a bug in transmitting the blob returned by opposing lend_object()?");
1216 return Handle<Value>{};
1217 }
1218 // else
1219
1220 /* memcpy() it out of there: the source address may not be aligned. (In many APIs such things are assumed as a
1221 * matter of course, but as `serialization` may be IPCed-over to us via any technique, we're being
1222 * extra defensive.) */
1223 memcpy(&offset_from_pool_base, serialization.const_data(), sizeof(offset_from_pool_base));
1224
1225 const auto handle_state
1226 = reinterpret_cast<Shm_handle*>
1227 (reinterpret_cast<uintptr_t>(m_pool->address()) + offset_from_pool_base);
1228
1229 /* Reminder: Shm_handle=Handle_in_shm<Value> -- our *handle_state in particular -- includes both the `Value` and
1230 * the metadata (m_atomic_owner_ct, m_cting_process_id as of this writing). Hence this is a good safety check: */
1231 if (!is_obj_in_arena(handle_state))
1232 {
1233 // Again: For safety let's log and abort rather than a mere (often skipped in release builds) assert().
1234 FLOW_LOG_WARNING("SHM-classic pool [" << *this << "]: In attempt to deserialize SHM outer handle "
1235 "[" << static_cast<const void*>(handle_state) << "] "
1236 "(value+metadata size [" << sizeof(Shm_handle) << "], type [" << typeid(Value).name() << "]) "
1237 "after IPC-receipt detected that the value+metadata buffer is not wholly contained in "
1238 "our pool. Borrow op fails. "
1239 "Was there a bug in transmitting the blob returned by opposing lend_object()?");
1240 return Handle<Value>{};
1241 }
1242 // else
1243
1244 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: Deserialized SHM outer handle "
1245 "[" << static_cast<const void*>(handle_state) << "] "
1246 "(type [" << typeid(Value).name() << "]) after IPC-receipt: "
1247 "Owner-count is at [" << handle_state->m_atomic_owner_ct << "] "
1248 "(may change concurrently; but includes us at least hence must be 1+); "
1249 "constructing PID = [" << handle_state->m_cting_process_id << "]; "
1250 "our PID = [" << m_own_process_id << "]. "
1251 "Handle points to SHM-offset [" << offset_from_pool_base << "] (deserialized). Serialized "
1252 "contents are [\n" << buffers_dump_string(serialization.const_buffer(), " ") << "].");
1253
1254 { // Stats.
1255 auto& arena_stats = *m_arena_metadata; // Just for expressiveness.
1256
1260
1261 fetch_add(&arena_stats.m_obj.m_borrow_count, 1);
1262 }
1263
1264 // Now simply do just as in construct():
1265 return Handle<Value>{shared_ptr<Shm_handle>{handle_state,
1266 [this](Shm_handle* handle_state)
1267 { handle_deleter_impl<Value>(handle_state, false); }},
1268 &handle_state->m_obj};
1269} // Pool_arena::borrow_object()
1270
1271template<typename T>
1272void Pool_arena::handle_deleter_impl(Handle_in_shm<T>* handle_state, bool constructing_else_borrowing)
1273{
1274 using Value = T;
1275 using Atomic_owner_ct = typename Handle_in_shm<Value>::Atomic_owner_ct;
1276 using flow::util::stat::fetch_add;
1277 using flow::util::stat::fetch_sub;
1278
1279 /* Discussion on the atomic-fu below: You will note ("Attn" x 2) below that it's basically:
1280 * if (owner_ct.fetch_sub(1, release) == 1) { fence(acquire); ...ref-count=0 reached so destroy... }
1281 * That could have been replaced with simply:
1282 * if (owner_ct-- == 1) { ...ref-count=0 reached so destroy... }
1283 * So why the fanciness? Answer:
1284 *
1285 * Firstly please read "quick refresher" comment in lend_object(), where it does the ++ version of the above
1286 * (which is simpler). Then come back here. ...Welcome back. As it says there, here we also care about
1287 * .fetch_sub() returning 1 in exactly 1 disposer, not none and not in 2+ disposers. As it says there, even
1288 * `relaxed` would have guaranteed that. However, as it also says there, here in .fetch_sub()-land we do
1289 * care about something else. It is this:
1290 *
1291 * If indeed .fetch_sub() returns 1 -- hence it has made owner_ct zero -- we'll execute ~T() dtor and deallocate().
1292 * Without additional synchronization (that is, if just did `relaxed` instead of `release`; and no
1293 * fence(acquire) thing), things in a competing thread -- effectively it would have to be another .fetch_sub()
1294 * in another disposer like us -- that logically happen-before competing .fetch_sub() of the same atomic
1295 * owner_ct are not guaranteed to happen-before the code we have following our .fetch_sub(). So if the other
1296 * thread, say, made some change to handle_state->m_obj and then dropped its handle (hence the competing disposer),
1297 * it could get reordered to *not* happen-before we execute (handle_state->m_obj).~T() (the m_obj dtor).
1298 * Formally that's undefined behavior/disaster.
1299 *
1300 * So the .fetch_sub() must synchronize with other threads touching owner_ct. The most normal way is
1301 * .fetch_sub(1, acq_rel); that would be fine. A somewhat quicker, for some architectures at least, and
1302 * still formally correct way is what we do: prime it with .fetch_sub(1, release); then *only if it matters*
1303 * fence(acquire). It only matters if indeed the ref-count reached 0.
1304 *
1305 * Shorter version: This is a classic std::atomic ref-count pattern; perhaps a little less classic due to
1306 * the if/fence optimization, but that one is also well known. E.g., boost::asio uses an identical
1307 * ref_count_up() + ref_count_down() implementation. The ref_count_down() part uses the same technique we use.
1308 * (As of this writing, the std:atomic-based branch of boost.smart_ptr shared_ptr ref-count appears to just
1309 * use the less-fancy acq_rel 1-step approach. That's interesting. This is still correct and theoretically
1310 * a bit better sometimes.) */
1311
1312 const auto prev_owner_ct = handle_state->m_atomic_owner_ct.fetch_sub(1, std::memory_order_release); // Attn.
1313 assert((prev_owner_ct != 0) && "How was owner_ct=0, yet handle was still alive? Bug?");
1314
1315 FLOW_LOG_TRACE("SHM-classic pool [" << *this << "]: Return SHM outer handle [" << handle_state << "] "
1316 "(type [" << typeid(Value).name() << "]) "
1317 "because, for a given owner, a Handle is being destroyed due to shared_ptr ref-count reaching 0: "
1318 "Owner-count decremented to [" << (prev_owner_ct - 1) << "] (may change concurrently "
1319 "unless 0). If it is 0 now, shall invoke dtor and SHM-dealloc now. "
1320 "Constructing PID = [" << handle_state->m_cting_process_id << "]; "
1321 "our PID = [" << m_own_process_id << "].");
1322
1323 enum { S_NONE, S_OWN_PID, S_OTHER_PID } destroy_type;
1324 if (prev_owner_ct == 1)
1325 {
1326 std::atomic_thread_fence(std::memory_order_acquire); // Attn (see comment earlier).
1327
1328 // Now it is zero. Time to destroy the whole thing; yay! Execute the reverse (order) of construct<>() logic.
1329 if constexpr(!std::is_trivially_destructible_v<Value>) // Skip it if possible (for perf).
1330 {
1331 /* As promised, and rather crucically, help out by setting this context (same as we had around ctor --
1332 * but this time it's more essential, since they can pretty easily do it themselves when constructing; but
1333 * doing it at the time of reaching shared_ptr ref-count=0... that's a tall order). */
1334 Activator ctx{this};
1335
1336 // But regardless:
1337 (handle_state->m_obj).~Value();
1338 }
1339 (handle_state->m_atomic_owner_ct).~Atomic_owner_ct();
1340
1341 destroy_type = ((handle_state->m_cting_process_id == m_own_process_id) ? S_OWN_PID : S_OTHER_PID);
1342 deallocate(static_cast<void*>(handle_state));
1343 }
1344 else // if (prev_owner_ct > 1): It is now 1+; stays alive. Done for now (other than stat-updating).
1345 {
1346 destroy_type = S_NONE;
1347 }
1348
1349 { // Stats.
1350 auto& arena_stats = *m_arena_metadata; // Just for expressiveness.
1351 auto& shared_arena_obj_stats = arena_stats.m_obj;
1352
1353 // Little generic helper to avoid copy/paste; obj_stats must be either &m_owner_obj or &m_borrower_obj.
1354 const auto update_stats = [&](auto* obj_stats)
1355 {
1356 fetch_add(&obj_stats->m_disposer_count, 1);
1357 fetch_sub(&obj_stats->m_live_handle_groups, 1);
1358 };
1359
1360 if (constructing_else_borrowing)
1361 {
1362 update_stats(&m_local_stats.m_owner_obj);
1363 fetch_add(&shared_arena_obj_stats.m_construct_disposer_count, 1);
1364 }
1365 else
1366 {
1367 update_stats(&m_local_stats.m_borrower_obj);
1368 fetch_add(&shared_arena_obj_stats.m_borrow_disposer_count, 1);
1369 }
1370
1371 if (destroy_type != S_NONE)
1372 {
1373 fetch_sub(&arena_stats.m_live_obj.m_live_objects, 1);
1374
1375 /* These destroy-counts count all object-destructions regardless of whether this (last) disposer/deleter
1376 * ran on a construct()-returned or borrow_object()-returned handle.
1377 * @todo It might (might!) be nice to actually break it down that way; so then the following two lines
1378 * would move to update_stats() helper above and affect *obj_stats. Before rushing to do it, though,
1379 * ensure other SHM-providers (as of this writing at least the vastly-different SHM-jemalloc) can reasonably
1380 * easily and performantly implement it, as currently they reuse some of the same `struct`s. Alternatively
1381 * bifurcate the `struct`s; but that adds even more complexity. */
1383 fetch_add(&shared_arena_obj_stats.m_destroy_count, 1);
1384
1385 if (destroy_type == S_OTHER_PID)
1386 {
1388 fetch_add(&shared_arena_obj_stats.m_non_owner_destroy_count, 1);
1389 }
1390 }
1391 } // Stats.
1392} // Pool_arena::handle_deleter_impl()
1393
1394template<typename Handle_name_func>
1395void Pool_arena::for_each_persistent(const Handle_name_func& handle_name_func) // Static.
1396{
1397 util::for_each_persistent_shm_pool(handle_name_func);
1398 // (See that guy's doc header for why we didn't just do what's necessary right in here.)
1399}
1400
1401} // namespace ipc::shm::classic
Similar to bipc's basic_managed_shared_memory, except that if a ctor created the SHM-pool in the file...
A SHM-classic interface around a single SHM pool with allocation-algorithm services by boost....
Definition: pool_arena.hpp:225
::ipc::bipc::offset_ptr< T > Pointer
SHM-storable fancy-pointer.
Definition: pool_arena.hpp:236
::ipc::bipc::rbtree_best_fit<::ipc::bipc::mutex_family > Mem_algo
The boost.interprocess memory-algorithm used by the pools we set up.
Definition: pool_arena.hpp:281
void handle_deleter_impl(Handle_in_shm< T > *handle_state, bool constructing_else_borrowing)
Identical deleter for Handle returned by both construct() and borrow_object(); invoked when a given p...
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
bool is_obj_in_arena(const T *obj) const
Returns true if and only if all sizeof(T) bytes at address obj are within the bounds of the pool acce...
const util::process_id_t m_own_process_id
Process ID captured in ctor, at least for perf. Used for and in relation to Handle_in_shm::m_cting_pr...
Definition: pool_arena.hpp:987
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
boost::shared_ptr< T > Handle
Outer handle to a SHM-stored object; really a regular-looking shared_ptr but with custom deleter that...
Definition: pool_arena.hpp:260
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
flow::util::Blob_sans_log_context Blob
Alias for a light-weight blob.
Definition: pool_arena.hpp:278
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
static void for_each_persistent(const Handle_name_func &handle_name_func)
Lists all named SHM pool objects currently persisting, invoking the given handler synchronously on ea...
stat::Arena_info_dump Info_dump
Alias for a stats/info bundle type.
Definition: pool_arena.hpp:284
Handle< T > construct(Ctor_args &&... ctor_args)
Constructs an object of given type with given ctor args, having allocated space directly in attached ...
~Pool_arena()
Destroys Pool_arena accessor object.
Definition: pool_arena.cpp:193
Blob lend_object(const Handle< T > &handle)
Adds an owner process to the owner count of the given construct()-created handle, and returns an opaq...
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
bool is_handle_in_arena(const Handle< T > &handle) const
Returns true if and only if handle came from either this->construct<T>() or this->borrow_object<T>().
Handle< T > borrow_object(const Blob &serialization)
Completes the cross-process operation begun by lend_object() that returned serialization; to be invok...
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
RAII-style class operating a stack-like notion of the given thread's currently active SHM-aware Arena...
Stateless allocator usable with STL-compliant containers to store (or merely read) them directly in S...
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
ipc::shm sub-module with the SHM-classic SHM-provider. See ipc::shm doc header for introduction.
Definition: classic_fwd.hpp:26
::pid_t process_id_t
Syntactic-sugary type for POSIX process ID (integer).
Definition: util_fwd.hpp:140
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
bipc::open_only_t Open_only
Tag type indicating an ideally-atomic open-if-exists-else-fail operation.
Definition: util_fwd.hpp:152
void for_each_persistent_shm_pool(const Handle_name_func &handle_name_func)
Equivalent to shm::classic::Pool_arena::for_each_persistent().
Definition: util.hpp:136
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
boost::asio::const_buffer Blob_const
Short-hand for an immutable blob somewhere in memory, stored as exactly a void const * and a size_t.
Definition: util_fwd.hpp:131
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
The data structure stored in SHM corresponding to an original construct()-returned Handle; exactly on...
Definition: pool_arena.hpp:828
util::process_id_t m_cting_process_id
Process ID of the process that performed the Pool_arena::construct() that resulted in *this Handle_in...
Definition: pool_arena.hpp:860
std::atomic< uint32_t > Atomic_owner_ct
Atomically accessed count of each time the following events occurs for a given Handle_in_shm in the b...
Definition: pool_arena.hpp:839
Atomic_owner_ct m_atomic_owner_ct
See Atomic_owner_ct doc header. This value is 1+; once it reaches 0 *this is destroyed in SHM.
Definition: pool_arena.hpp:851
T m_obj
The constructed object; Handle::get() returns &m_obj.
Definition: pool_arena.hpp:848
Printable (with knobs in contained util::stat::Info_dump_format) bundling of stats/info pertaining to...
Cumulative stats relevant to one SHM-classic arena – in this case single SHM-pool in shared memory,...
shm::stat::Live_obj_stats m_live_obj
Sub-struct with individual stats; see type's doc header.
shm::stat::Shared_arena_obj_stats m_obj
Sub-struct with individual stats; see type's doc header.
Cumulative stats relevant to a given SHM-classic arena object – a Pool_arena – and excluding those in...
shm::stat::Borrower_obj_stats m_borrower_obj
Sub-struct with individual stats; see type's doc header.
shm::stat::Owner_obj_stats m_owner_obj
Sub-struct with individual stats; see type's doc header.
shm::stat::Lender_obj_stats m_lender_obj
Sub-struct with individual stats; see type's doc header.
std::atomic< int64_t > m_live_handle_groups_hi_wmark
Max of m_live_handle_groups observed so far.
Definition: shm_stats.hpp:368
std::atomic< uint64_t > m_borrow_count
Count of the arena-or-session's .borrow_object<T>() calls by user.
Definition: shm_stats.hpp:345
std::atomic< int64_t > m_live_handle_groups
Gauge: How many groups counted in m_borrow_count have not yet had their disposer execute (as counted ...
Definition: shm_stats.hpp:365
std::atomic< uint64_t > m_lend_count
Count of .lend_object(obj) calls, where shared_ptr<T> obj was obtained as arena.construct<T>(....
Definition: shm_stats.hpp:279
std::atomic< uint64_t > m_live_objects
Gauge: Count of first-class – arena.construct()ed – objects that have not yet been destroyed (via GC)...
Definition: shm_stats.hpp:397
std::atomic< uint64_t > m_construct_count
Count of the arena object's .construct<T>() calls by user.
Definition: shm_stats.hpp:154
std::atomic< uint64_t > m_non_owner_destroy_count
Number of times the arena object has destroyed a first-class (construct()ed) object stored in the und...
Definition: shm_stats.hpp:242
std::atomic< uint64_t > m_destroy_count
Number of times the arena object has destroyed a first-class (construct()ed) object stored in the und...
Definition: shm_stats.hpp:207
std::atomic< int64_t > m_live_handle_groups_hi_wmark
Max of m_live_handle_groups observed so far.
Definition: shm_stats.hpp:181
std::atomic< int64_t > m_live_handle_groups
Gauge: How many groups counted in m_construct_count have not yet had their disposer execute (as count...
Definition: shm_stats.hpp:178
std::atomic< uint64_t > m_construct_count
Similar to Owner_obj_stats::m_construct_count but applies to all arena.construct()s executed w/r/t th...
Definition: shm_stats.hpp:442
std::atomic< uint64_t > m_lend_count
Similar to Lender_obj_stats::m_lend_count but applies to all arena.lend_object()s executed w/r/t the ...
Definition: shm_stats.hpp:478
std::atomic< uint64_t > m_borrow_count
Similar to Borrower_obj_stats::m_borrow_count but applies to all arena.borrow_object()s executed w/r/...
Definition: shm_stats.hpp:486