Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
ipc_arena.hpp
Go to the documentation of this file.
1/* Flow-IPC: SHM-jemalloc
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26#pragma once
27
28#include "ipc/shm/arena_lend/owner_shm_pool_collection.hpp"
32#include "ipc/shm/arena_lend/shm_pool_offset_ptr.hpp"
40#include "ipc/util/util.hpp"
41#include "ipc/util/util_fwd.hpp"
43#include <flow/log/log.hpp>
44#include <flow/util/util.hpp>
45#include <boost/interprocess/exceptions.hpp>
46#include <unordered_map>
47#include <map>
48#include <set>
49#include <type_traits>
50#include <memory>
51#include <vector>
52#include <new>
53#include <typeinfo>
54
56{
57
58// Types.
59
60/**
61 * Represents/manages a segregated collection of in-SHM (shared memory) objects and buffers allocated using
62 * the memory manager *jemalloc*, acting according to the SHM-arena-lend design. It is arguably the central
63 * class of SHM-jemalloc, a type SHM-arena-lending SHM-provider in Flow-IPC. SHM-jemalloc's key objects
64 * (Ipc_arena and ipc::session::shm::arena_lend::jemalloc::Shm_session, principally) can be plugged into a
65 * general Flow-IPC session, as established via ipc::session machinery; or they can be used on a standalone basis.
66 * It is generally significantly easier to do the former: a successfully established `Session` will get you
67 * an Ipc_arena (2, even, one of session-scope, another of app-scope) and `Shm_session` for free (~no additional
68 * coding effort is required). You may access `jemalloc::Session::session_shm()` (`Ipc_arena*`),
69 * `jemalloc::Server_session::app_shm()` (ditto), and `jemalloc::Session::shm_session()` (`Shm_session*`).
70 *
71 * @note These notes (in Ipc_arena doc header) are written from the standpoint of presenting it (and the rest of
72 * SHM-jemalloc) as a standalone system. In most situations a general Flow-IPC user can think of
73 * SHM-providers, whether SHM-jemalloc or another one (such as SHM-classic in shm::classic), interchangeably.
74 * That said each SHM-provider has certain properties specific to it. We mention them below under
75 * "Properties of SHM-jemalloc."
76 *
77 * @see ipc::session::shm::arena_lend::jemalloc::Shm_session, an important class required for sharing objects
78 * constructed via Ipc_arena with other process(es).
79 *
80 * The key relevant ops are as follows. Let `*arena` be an Ipc_arena. Let `*session` be a
81 * session::shm::arena_lend::jemalloc::Shm_session connected to an entity (process, usually not the process holding
82 * `*arena`) P. Both are `shared_ptr`s but never mind for now. Let `*session2` be the peer object of `*session`,
83 * in process P.
84 * - `shared_ptr<T> p = arena->construct<T>(...)`: Constructs a `T{...}` in SHM as *owned* by `*this` arena; jemalloc
85 * is used for allocation of actual buffers.
86 * - `session->lend_arena(arena)`: Makes it possible to share -- *lend* -- `p` (and others) to P.
87 * - `auto blob = session->lend_object(p)`: Registers that `*p` is planned to be *borrowed* by P.
88 * - IPC-transmit the short `blob` to P (call the received copy `blob2`).
89 * - (In opposing process P) `shared_ptr<T> p2 = session2->borrow_object(blob2)`.
90 * - Now: `*p` is garbage-collected *across processes* (ours and P in this scenario; but it is possible to
91 * lend to other processes via other `Shm_session`s, 1 of the latter for each). Only once the `p` and `p2`
92 * ref-counted-pointer groups each reaches ref-count zero does `*p` get destroyed (`~T()` runs, then
93 * `sizeof(T)` memory is jemalloc-deallocated).
94 *
95 * ### Interrelated lifetimes of Ipc_arena, its `construct()`ed objects, and lent-through `Shm_session`s ###
96 * `Ipc_arena`s are publicly accessed exclusively via `shared_ptr` handles (use create() factory method instead of
97 * any direct ctor). This semantic is used to ensure it will outlive each of the following.
98 * - 1, each (garbage-collectable) object `arena->construct<T>()`ed (in SHM), even while no process has borrowed
99 * it. Therefore you may picture that after `shared_ptr<T> p = arena->construct<T>(...)`, the `shared_ptr` group
100 * of `p` maintains an invisible `shared_ptr` copy of `arena`, live while the group is live.
101 * - 2, each (also `shared_ptr`-accessed) `Shm_session` object for which one has called `session->lend_arena(arena)`.
102 * Thus picture that the latter call saves a copy of `shared_ptr<Ipc_arena> arena` inside `*session`, live
103 * while `*session` is live. (The user should let go of `session` if and only if the connection to the opposing
104 * process is lost, and/or we want to imminently end that connection.)
105 *
106 * Hence, assuming no crashes/etc.:
107 * - A `*this` is required for constructing future objects to be shared via each given `Shm_session`. Therefore
108 * it outlives all `Shm_session`s through which it has been `lend_arena()`d.
109 * - A `*this` is required for destroying objects it has constructed, and which may or may not have been
110 * lent via 1+ `Shm_session`s. Therefore it outlives (...see prev bullet...); and it outlives all
111 * `shared_ptr` group(s) returned by `this->construct()`.
112 *
113 * @todo ipc::shm::arena_lend::jemalloc::Ipc_arena has public methods for end users but also an API, including
114 * (e.g.) add_shm_pool_listener(), used by Flow-IPC internals to make things work properly; these should
115 * be made non-`public` (accessible by said internals via a facade/attorney/etc. pattern of choice). A careful
116 * effort is required to clean up the API and then classify what's left as either public-facing or otherwise.
117 * Suggest also converting currently-used `protected`-for-unit-tests pattern to the facade/attorney/etc.
118 * pattern of choice (if necessary a separate facade/whatever could be used for internal-code consumers versus
119 * test-code consumers). E.g., start() is such a test-accessed `protected`-only-for-that-reason
120 * item. / Lastly: the public super-class Owner_shm_pool_collection, and its super-class(es), likely expose
121 * some public things that too should be gated-off from end-users.
122 *
123 * ### Cleanup (of in-SHM resources) ###
124 * As explained above, a borrowing/lending scheme + GC semantics ensure proper cleanup of `Ipc_arena`-constructed
125 * objects. What about the SHM-pools (a/k/a SHM objects, SHM segments, in-SHM extents) that are obtained from the
126 * OS? (Even if all buffers held in a given SHM-pool have been deallocated, at a lower level RAM may still be
127 * earmarked for SHM use on `*this` behalf. Plus there are vaddr areas that are mapped that may need to be
128 * unnmapped.) Answer:
129 *
130 * Assuming no crashes occur throughout, here or in borrowing process(es) if any: A `*this` shall only be destroyed
131 * when safe (via aforementioned `shared_ptr` techniques). When this occurs, the Ipc_arena destructor, synchronously
132 * or otherwise, shall return/free all resources, including SHM-pool handles (in Linux et al, FDs really) and/or mapped
133 * (in Linux et al, `mmap()`ed) vaddr areas, and SHM-pool names in the file-system. Similarly a borrowing
134 * `Shm_session` (also destroyed when safe via `shared_ptr` goodness) shall handle its end of such things
135 * (read-only SHM-pool handles, mapped areas). Once all (well, a subset) of this occurs, the relevant SHM-used
136 * RAM is returned to the system for general use.
137 *
138 * If a crash occurs, the dtor may not run (or its asynchronous aspects may not finish). The crash itself will
139 * return any SHM-pool handles and mapped extents; but a given SHM-pool's name would remain in the file-system;
140 * thus the RAM is not returned. Handling this eventuality is the responsibility of the Ipc_arena (et al) user.
141 * In particular by arranging a certain pool-naming scheme -- see `pool_name_base` arg to Ipc_arena::create() --
142 * one can opportunistically remove (unlink) dead process-created SHM-pools form the file-system.
143 *
144 * Happily ipc::session (available in Flow-IPC but optional to use with SHM-jemalloc) has implemented such
145 * post-crash opportunistic cleanup (and, relatedly, determines the pool-naming scheme). It calls
146 * Ipc_arena::create() in particular (among other things) for you. If you don't use ipc::session to establish
147 * your `Ipc_arena`(s), though, you would need to handle it yourself (or risk memory leaks).
148 *
149 * @warning Due to certain implementation details, the `create()`-returned `shared_ptr` group (that points to
150 * `*this`) reaching ref-count-zero concurrently to a *relevant thread* exiting can lead to undefined behavior.
151 * A *relevant thread* is defined as one in this process that has touched a `this->construct<T>()`ed
152 * handle group through any of the following operations: Ipc_arena::construct() itself,
153 * `Shm_session::lend_object()`, and the *disposer*. The *disposer* executes when the handle
154 * (#Handle a/k/a `shared_ptr<T>`) group reaches ref-count-zero. Loosely speaking this means either
155 * join the threads that work with SHM-objects `construct()`ed by `*this` before ending `*this` arena;
156 * or end this arena before joining such threads.
157 *
158 * ### Properties of SHM-jemalloc (and likely other SHM-arena-lending SHM-providers, if/when they exist) ###
159 * Please see the doc header for namespace ipc::shm. It contrasts the `Ipc_arena`-centered SHM-jemalloc, which
160 * is an *arena-lending* provider, to the `Pool_arena`-centered SHM-classic (an *arena-sharing* provider).
161 *
162 * Spoiler alert: e.g., in our case borrower-side access of a SHM-stored object is read-only.
163 *
164 * @internal
165 *
166 * The warning just above, in mentioning "certain implementation details," is actually referring to the limitations
167 * on when detail::Thread_lcl_obj_db_admin::forgetting_shm_arena() (ditto `Thread_lcl_obj_db_client`) can be safely
168 * called. This is documented in the doc headers for those two `static` methods. The danger zone begins when
169 * a relevant thread begins shutdown -- `Thread_local_state_registry` removes an `_admin` and/or `_client`
170 * (that has worked with `*this` arena's objects) from its extant-TL-states list -- and ends basically when
171 * the `_admin` and/or `_client`'s destructor has finished. This is a brief period called The Gap in some internal
172 * documentation in thread_lcl_obj_db.hpp-land.
173 */
176 public std::enable_shared_from_this<Ipc_arena>
177{
178public:
179 // Types.
180
181 /**
182 * Fancy pointer type used by #Allocator.
183 *
184 * @tparam Pointed_type
185 * The type contained within the pointer.
186 */
187 template<typename Pointed_type>
189
190 /**
191 * First-class handle -- a `shared_ptr` of some kind by definition -- to objects `construct<>()`ed by this arena
192 * class. While as of this writing no separately documented concept for SHM arena class (implemented, e.g., by
193 * this Ipc_arena; or its SHM-classic counterpart shm::classic::Pool_arena) exists, certain important
194 * APIs do take `Shm_arena`s like us and formally require it to contain `Handle`, which must be *a* `shared_ptr`
195 * type (`std::`, `boost::`, or in theory some other impl), and be the type returned by `Shm_arena::construct<>()`
196 * as well as `Shm_session::borrow_object()`. A key such API, if working with capnp in-SHM,
197 * is transport::struc::shm::Builder.
198 *
199 * ### Rationale ###
200 * In addition to the generic-programming aspect touched-upon above, a
201 * squishier alleged benefit is the stylistic attention it calls to construct() or session `borrow_object()`
202 * returning a cross-process SHM handle to an outer/first-class object, as opposed to a normal heap-deleting
203 * ref-counting pointer.
204 */
205 template<typename Pointed_type>
207
208 /**
209 * Convenience alias for a shm::stl::Stateless_allocator> w/r/t Ipc_arena;
210 * use with #Activator.
211 *
212 * @tparam T
213 * Pointed-to type for the allocator. See standard C++ `Allocator` concept.
214 */
215 template<typename T>
217
218 /// Alias for an Arena_activator using Ipc_arena.
220
221 /// Alias for a stats/info bundle type.
223
224 /// Alias for a stats type.
226 /// Alias for a stats type.
228 /// Alias for a stats type.
230 /// Alias for a stats type.
232 /// Alias for a stats type.
234 /// Alias for an info type.
236
237 // Methods.
238
239 /**
240 * Creates an instance of this class, factory-style. See class doc header for notes regarding lifetimes
241 * of related objects (`Shm_session`, `construct()`ed objects) and how the `shared_ptr`-ownership semantics
242 * help this happen.
243 *
244 * @warning `*logger` -- unless `logger` is null -- must exist at least past (1) all
245 * `construct()`-returned pointers' groups, (2) all `Shm_session`s through which you'd
246 * `Shm_session::lend_arena()` the returned `Ipc_arena`, and (3) of course the returned pointer group.
247 *
248 * ### Errors ###
249 * If jemalloc fails to create a jemalloc-arena a `flow::error::Runtime_error` exception shall be thrown.
250 * In particular jemalloc's `opt.retain` must be enabled (it is by default in 64-bit Linux); if not this throws
251 * as noted (with `.code() == boost::system::errc::not_supported`) before creating a jemalloc-arena.
252 * One particular condition which can be a result of user error is if `pool_name_base` is malformed in some way
253 * (contains illegal characters most likely), and therefore internally SHM-pool creation by name (based off
254 * `pool_name_base`) failed. For certain internal reasons this will manifest as having `.code()`,
255 * probably, a-la `resource_unavailable_try_again` (Linux: from `EAGAIN`). With a non-null `logger` you
256 * should see logged WARNING(s) to that effect.
257 *
258 * @note Consider using Shared_name::sanitize() w/r/t `pool_name_base`.
259 *
260 * Other errors are of a truly exceptional nature to the best of our knowledge.
261 *
262 * @todo Ipc_arena::create() should follow normalized Flow error emission semantics (take `Error_code*`, set
263 * to success or specific failure, if null then throw on failure, encapsulating the truthy `Error_code`).
264 *
265 * @todo Ipc_arena::create() would be nice to emit a specific fitting error, as opposed to the somewhat
266 * ambiguous `resource_unavailable_try_again`-or-similar, when `pool_name_base` is malformed. The message
267 * could even suggest Shared_name::sanitize() and/or Shared_name::sanitized().
268 *
269 * @param logger
270 * Used for logging purposes.
271 * @param memory_manager
272 * The jemalloc memory allocator.
273 * @param pool_name_base
274 * Pool-name prefix; each pool's SHM object name is derived from this plus its unique ID.
275 * See "Cleanup" in class doc header for notes on how this can implement on-crash cleanup.
276 * @param permissions
277 * The shared memory object file permissions when one is created (on-demand during construct() et al).
278 *
279 * @return A shared pointer to an instance of this class; not null. (See also above about error possibilities
280 * involving throwing.)
281 */
282 static std::shared_ptr<Ipc_arena> create(flow::log::Logger* logger,
283 const std::shared_ptr<Memory_manager>& memory_manager,
284 Shared_name&& pool_name_base,
285 const util::Permissions& permissions);
286
287 /**
288 * `allocate()`s `sizeof(T)` bytes (in SHM), invokes `T{...}` ctor with the given args, and returns a
289 * ref-counted handle, so that the resulting object shall be garbage-collected w/r/t this process as well
290 * as any subsequent *borrower* process(es). That is to say, `~T()` dtor shall be called, followed by deallocate(),
291 * in this process -- but only after (1) the returned handle's `shared_ptr` group reaches ref-count zero,
292 * *and* (2) the same happens to any similar `Handle<T>` returned by `Shm_session::borrow_object()`.
293 *
294 * Does not return null. Throws on any error (details below).
295 *
296 * @see Class doc header for an overview of related ops including lend/borrow. That'll explain how the
297 * aforementioned `borrow_object()` call -- probably in another process! -- is connected to the
298 * handle returned here.
299 * (Spoiler alert: `p = a->construct()` => `blob = session->lend_object(p)`
300 * => `p2 = their_session->borrow_object(copy_of_blob_ipced_over_to_us)`.)
301 *
302 * [De]allocation propagation via STL-allocator
303 * --------------------------------------------
304 * What if `T` is not a plain old data type? Let's say specifically (as recommended for Flow-IPC users dealing
305 * with non-PoDs in SHM) that `T` is (or contains... but let's keep it simple) an STL-compliant container such as
306 * `vector<char>` or `flow::util::Basic_blob`. `sizeof(T)` then likely won't be holding the actual data;
307 * it'll need to allocate a buffer of size N. Let's say you used the T ctor that would immediately allocate
308 * N bytes. Then, for this to work properly (as opposed to allocating N bytes in regular heap: unhelpful),
309 * `T` must be configured with a SHM-aware allocator that will call `allocate(N)` via this `Ipc_arena` when
310 * needed. #Allocator provides this. So construct() itself will `allocate(sizeof(T))`,
311 * invoke `T{...}`; that will propagate any allocating to the allocator; that will in turn `allocate(N)`.
312 * Conversely, when SHM-jemalloc decides to garbage-collect (see above), it will invoke `~T()`; that will
313 * propagate deallocation of the N-buffer to the allocator; allocator in turn will `deallocate(...)` via
314 * `*this`; and lastly SHM-jemalloc shall `deallocate(p)`, where `p` is the raw pointer in the
315 * handle originally returned by `construct()` here.
316 *
317 * It is also possible, when working with a container such as the hypothetical `T`, to require more allocations
318 * and/or deallocations after ctor `T{...}` here and dtor `~T()` at GC-time. The same principles apply:
319 * `T` (e.g. `vector`) code tells allocator to [de]allocate; allocator forwards to `this->[de]allocate()`.
320 * However! The allocator must know which `Ipc_arena` to in fact use. If you are using `Stateless_allocator`
321 * (#Allocator) (as we recommend) then you will need to use shm::stl::Arena_activator (#Activator) to
322 * thread-locally set the "current" arena to `this`, in-scope of any potentially-[de]allocating ops (e.g.:
323 * `vector::resize()`).
324 *
325 * Integration with shm::stl::Stateless_allocator (#Allocator)
326 * -----------------------------------------------------------
327 * This method, bracketing the invocation of the `T` ctor, sets the thread-local #Activator context to `this`.
328 * Therefore the caller need not do so. If `T` does not store an STL-compliant structure that uses
329 * `Stateless_allocator`, then this is harmless albeit a small perf hit (also skipped for all
330 * trivially-destructible `T`). If `T` does do so, then it is a convenience.
331 *
332 * Arguably more importantly: The returned `shared_ptr` is such that when garbage-collection of the created
333 * data structure does occur -- which may occur in this process, but via lend_object() and borrow_object()
334 * may well occur in another process -- the `T::~T()` *dtor* call shall also be bracketed by the aforementioned
335 * context. Again: If `T` does not rely on `Stateless_allocator`, then it's harmless; but if it *does*, then
336 * doing this is quite essential. That is because the user cannot, typically (or at least sufficiently easily),
337 * control the per-thread allocator context at the time of dtor call -- simply because who knows who or what
338 * will be running when the cross-process ref-count reaches 0.
339 *
340 * Lifetime versus `*this` arena
341 * -----------------------------
342 * Please read "Interrelated lifetimes" in class doc header.
343 *
344 * Errors
345 * ------
346 * ### On `allocate(sizeof(T))` failing ###
347 * The first step in this method is to allocate the outer-layer buffer sized for the new `T` itself. If this
348 * fails: the method throws as allocate(); namely it throws `std::bad_alloc`.
349 *
350 * @see allocate() doc header which describes the relevant throwing semantics. In short: the known
351 * (all non-mainstream) causes are environmental limits -- chiefly the per-process open-file-descriptor
352 * limit, as SHM-jemalloc keeps one descriptor open per SHM-pool -- and an absurdly large `sizeof(T)`.
353 * Contrast with, say, classic::Pool_arena::allocate() which can fail simply because the user-decided
354 * and unchangeable (for a given arena) overall pool capacity is exceeded.
355 *
356 * ### What else can fail? ###
357 * construct() in SHM-jemalloc, following the initial `allocate(sizeof(T))` and the ctor `T{...}`,
358 * performs further setup that assures the aforementioned cross-process GC. A problem can occur. In all
359 * cases, as a result, an exception is thrown upon undoing preceding successful steps.
360 *
361 * As with allocate() essentially all problems are of a non-mainstream nature/environmental limits. There are
362 * however a couple that *could* be of specific interest.
363 *
364 * - Internally the GC system can only handle up to a certain large # of live `construct()`ed objects *per thread*
365 * (per arena). As of this writing it is about 1 million. This is not a problem for most applications, but it
366 * is possible.
367 * - Result: throws `std::bad_alloc` after logging a WARNING. Note this is the same result,
368 * exception-type-wise, as allocate() failing. Both do indicate, usually, some kind of capacity problem.
369 * - Internally the GC system will need to create a (typically small, in terms of actual RAM use) auxiliary
370 * named SHM-pool. This can fail, though again it is unlikely in a setup that is basically working.
371 * - Result: throws `flow::error::Runtime_error` (itself an `std::runtime_error`) after logging a warning.
372 * An `Error_code` and an error string are available through the exception object.
373 * - Any other issue: Throws an exception.
374 *
375 * Last, and in fact not least:
376 *
377 * ### If `T` constructor throws ###
378 * The thrown exception propagates to our caller upon undoing the `allocate(sizeof(T))` preceding the ctor call.
379 *
380 * Corollary + context: A particular sub-case of this is that, while executing `T` ctor, a subordinate --
381 * typically/recommendedly via allocator-furnished container-containing type `T` -- `allocate()` threw `bad_alloc`.
382 * We guarantee that our `allocate(sizeof(T))` is undone. In addition: A properly coded (at all nesting-layers)
383 * container-containing type `T`, using (at all nesting-layers) a proper SHM-supporting allocator shall undo all
384 * `allocate()`s that had succeeded up to the one that failed. This method itself can only take care of its
385 * own `allocate()`, which as just noted it does.
386 *
387 * @tparam T
388 * The object type to be created.
389 * @tparam Ctor_args
390 * The parameter types that are passed to the constructor of T.
391 * @param ctor_args
392 * The arguments passed to the constructor of T.
393 *
394 * @return A shared pointer to an object created in shared memory. Not null.
395 */
396 template<typename T, typename... Ctor_args>
397 Handle<T> construct(Ctor_args&&... ctor_args);
398
399 /**
400 * Performs a non-garbage-collected allocation of a buffer in SHM.
401 *
402 * @warning It is irregular, though not formally disallowed, for the end user to invoke this except inside
403 * STL-allocator code. We provide shm::stl::Stateless_allocator (#Allocator) which shall do so for
404 * you, and we recommend you use that instead of rolling your own.
405 *
406 * On failure to allocate
407 * ----------------------
408 * The topic of how this acts on "failure to allocate" is arguably subtler than one might assume, mainly in
409 * that it's not so simple to define what "failure to allocate" means. We discuss that lower-down; but first
410 * the actual semantic:
411 *
412 * ### What allocate() does on failure to allocate ###
413 * If it is impossible to allocate the `n` bytes: Throws exception `std::bad_alloc`.
414 *
415 * @note This is intended to be consistent across
416 * SHM-providers like SHM-jemalloc and SHM-classic (classic::Pool_arena::allocate()).
417 *
418 * It is salient that an allocator's (e.g., our #Allocator) `allocate()` may forward to us and is
419 * allowed to "throw exceptions" (no particular exception type is mandated or recommended). So we are
420 * behaving consistently with letting an allocator's `allocate()` simply forward to this method (of some `*this`)
421 * and let it throw. There is no need to check for null return.
422 *
423 * ### What is failure to allocate? ###
424 * OK, so what should one do if this allocate() (or, e.g., `construct<T>()` that calls it 1+ times) in fact
425 * throws `bad_alloc`? Could one simply let the `bad_alloc` (if any) be thrown uncaught? That depends,
426 * naturally, on what occurred to result in this reported "failure to allocate."
427 *
428 * The meat-and-potatoes facts, presented for concreteness at the risk of describing some impl details, are as
429 * follows:
430 * - To allocate N bytes, here, is to ask jemalloc (a-la `malloc()`) to allocate an N-buffer in a particular
431 * segregated *jemalloc-arena* A. Basically the `malloc()`-like API returns null <=> failure to allocate.
432 * (We detect this internally and throw as advertised.)
433 * - This arena A does not have any total size that can be exceeded (cf.: classic::Pool_arena). As with
434 * vanilla `malloc()`, extents (vaddr areas) -- mapped (potentially sparsely) to actual RAM blocks -- are used
435 * in some fashion, and when none is sufficient for a given allocate-request, a new extent (vaddr area) is
436 * created by jemalloc corresponding to more RAM blocks (potentially sparsely).
437 * - We internally configure arena A so as to run some tricky Flow-IPC (SHM-jemalloc) code, via *extent hooks*,
438 * so when a new extent is needed (prev bullet) we'll map it to SHM as opposed to regular heap (+ more
439 * activities). If such a hook is invoked, it executes synchronously within allocate() here.
440 *
441 * That's the happy path; so how can it fail? Black-boxily speaking:
442 * - jemalloc code itself might fail in some way.
443 * - The known (non-mainstream) causes are environmental limits -- chiefly the per-process open-file-descriptor
444 * limit, as SHM-jemalloc keeps one descriptor open per SHM-pool -- and a too-large N. The latter
445 * specifically: N approaching or exceeding ~2GiB (at which point the needed SHM-pool would exceed the
446 * max size addressable by our fancy-pointer type) always fails.
447 * - Flow-IPC code itself might fail in some way (and would, potentially, log WARNING(s) as to what that was).
448 *
449 * That said:
450 * - *We do not know of a mainstream situation where this happens*. Unlike with `Pool_arena` in SHM-classic
451 * there is no arena max-size. Moreover, at least in Linux, running out of SHM (<=> tmpfs max-use limit reached)
452 * in a sparse setup like ours means a brutal, unavoidable crash (SIGBUS); so jemalloc-allocate is unlikely
453 * to be the thing that fails. Instead a subsequent touching of an in-extent page, possibly long after
454 * allocate() returns, is typically that thing: Something tries to write into a page's area (4Ki bytes wide
455 * usually); the page is not backed by RAM yet; OS tried to commit page; cannot (limit reached) => crash.
456 * - *We internally nevertheless treat it, defensively, as possible*. So we throw `bad_alloc` as noted.
457 * - Informally: Because of the first bullet above, we suggest that user code *may* treat the possibility
458 * of allocate() (or construct(), or other container-driven allocations) failing as being only as serious
459 * as a heap-alloc failure (e.g., when heap `new` throws `bad_alloc`). That is often defensibly taken to mean,
460 * "it's environmental/an act of god; just let it throw; don't catch it."
461 * - However: If writing code that is generic irrespectively of which SHM-arena/SHM-provider is acting, then
462 * potentially take the opposite approach. E.g., classic::Pool_arena::allocate() and `construct<T>()`
463 * definitely can fail with `bad_alloc`, and often one wants to be defensive/ready for this. So generic
464 * code might want to treat the Ipc_arena counterpart APIs defensively as well.
465 *
466 * Perf notes
467 * ----------
468 * A jemalloc thread cache will be used with this allocation automatically. See deallocate() also.
469 * (As of this writing preprocessor symbol `IPC_SHM_ARENA_LEND_JEMALLOC_NO_TCACHE` can be defined
470 * to forego tcaching entirely.)
471 *
472 * @param size
473 * The allocation size, which must be greater than zero.
474 * @return A non-null pointer to the base address of the allocation.
475 */
476 void* allocate(size_t size);
477
478 /**
479 * Undoes allocate(): deallocates a previously allocated, by this arena, buffer in SHM.
480 *
481 * ### Perf notes ###
482 * A jemalloc thread cache will be used for extra perf, assuming the calling thread has one for `*this`
483 * arena -- which it does if it has ever allocate()d here (a thread's first allocate() creates its cache).
484 * Notably it does *not* matter which thread `allocate(address)`ed: any buffer of this arena may be
485 * deallocated via any thread's cache (see Thread_cache doc header for the mechanism and why this is so).
486 * In the unusual case of a thread deallocating here without *ever* having allocated here, deallocation
487 * still proceeds and succeeds, albeit at a perf penalty (an arena-level mutex lock inside jemalloc) by forgoing
488 * caching. In short: currently deallocate(), for safety reasons around certain teardown phases, does not create
489 * a tcache, if it does not yet exist due to a prior allocate() in the same thread.
490 *
491 * @param address
492 * The address to be deallocated, which must be non-null.
493 */
494 void deallocate(void* address) noexcept;
495
496 /**
497 * Synchronously garbage-collects, in the calling thread, any objects `x = A.construct()`ed by this thread, for *all*
498 * `Ipc_arena`s `A` in existence. To be explicit: the garbage-collection involves only objects `x` such that (1)
499 * there are no *user references* to `x` anymore (see below), and (2) SHM-jemalloc has not yet had a chance to
500 * free `*x`. Freeing an (in-SHM) object involves calling its destructor if any (which may itself free more
501 * in-SHM buffers, typically through allocator #Allocator action) and then freeing its `sizeof(*x)` in-SHM buffer.
502 *
503 * What are *user references*? Answer: Firstly: `x` itself (from the given `A.construct()`) is a `shared_ptr` in a
504 * group; so all `shared_ptr`s in that group are references. Secondly: there is the `Shm_session::lend_object()` +
505 * `Shm_session::borrow_object()` (the latter of which returns a `shared_ptr` similar to `x`) mechanism. Without
506 * delving into exact formal details, essentially: `x2 = S2.borrow_object()`, usually invoked in another process
507 * on `Shm_session S2` in a procedure starting with `S.lend_object(x)` (in our process), returns a `shared_ptr`;
508 * so the `shared_ptr`s in that `shared_ptr` group are also references (albeit typically cross-process ones).
509 * (If the session to that other process is gone -- no more `S` -- then effectively any borrowed references
510 * lent through it are dropped.) To reiterate: Once both types of references are all dropped, condition (1)
511 * above is satisfied.
512 *
513 * ### When to call this ###
514 * Ideally you should not need to call this, and informally we recommend against doing so willy-nilly in the
515 * middle of your SHM-using algorithm(s); not because it is a costly call (it is not), but because it cuts
516 * against the (we humbly hope) easy-to-use design of SHM-jemalloc. In short: Most SHM-jemalloc activities --
517 * including *any* call to construct(), create(), `Shm_session::lend_object()`, `Shm_session::borrow_object()`,
518 * `construct()`ed-object disposer, ditto borrowed, at thread exit -- will already perform the necessary
519 * garbage-collection. Moreover, in many cases (depending on your use patterns), the specific object that has
520 * just reached cross-process ref-count-zero will be *immediately* disposed (no later GC needed).
521 *
522 * However: If a given object `*x` happens to reach cross-process ref-count-zero (meaning the last cross-process
523 * `shared_ptr` group's last `shared_ptr` releases its reference) *not* in the thread that constructed it but
524 * another thread or process, *and* (this is key) that thread stops doing **nearly all** SHM-jemalloc work as
525 * triggered by your code (see above list which may not be exhaustive), then no opportunistic GC shall occur, and
526 * `*x` will not be freed yet. (Eventually it will; worst-case on thread exit.)
527 *
528 * It is arguably a pathological use pattern, but it's certainly possible. So if your thread enters an idle
529 * or SHM-jemalloc-free time period, while arena(s) exist that could have this-thread-`construct()`ed objects
530 * (that are no longer used) extant, then calling this will ensure such things, if any, are freed.
531 *
532 * ### Is it cheap? ###
533 * Above we casually say "it is not" expensive. Informally speaking this is generally true. It *is* possible
534 * to contrive a scenario where so many no-longer-referenced but not-already-GCed (through direct or
535 * piggy-backed internal SHM-jemalloc algorithms alluded-to above) objects have built up, that a given call
536 * is relatively heavy, just by virtue of having to do the actual freeing many times. Even then, though, it
537 * would be a one-time thing. The point is: if there's little or nothing to GC, then it's cheap, because the
538 * check is very cheap.
539 *
540 * ### Thread safety ###
541 * Safe to call anytime. Asterisk: We have not contemplated corner-case scenarios of calling this from a given
542 * thread, when that thread is exiting (such as in some `thread_local`'s destructor). So we formally say:
543 * Do not do it (on pain of undefined behavior). In any case it would be pointless: GC occurs during thread
544 * shutdown already.
545 */
546 static void this_thread_gc();
547
548 /**
549 * Fills-out the stats/info contents of the given stat::Arena_info_dump: a printable bundling of stats/info relevant
550 * to (but not all necessarily owned by) `*this` Ipc_arena at this point in time. To summarize the resulting
551 * `*target_info_dump`:
552 * - Stats/info stored by-value; can be queried in peace. They won't concurrently change nor become invalid
553 * when `*this` Ipc_arena shuts down.
554 * - Can be printed to an `ostream` via `ostream<<`. `->m_fmt` (`Info_dump_format`) has output-format knobs,
555 * adjusting which ahead of the `<<` will affect the output.
556 * - As usual: can `FLOW_LOG_...(*target_info_dump)`, `boost::lexical_cast<string>(*target_info_dump)`,
557 * `flow::util::ostream_op_string(*target_info_dump)`.
558 *
559 * ### Rationale ###
560 * This is a "get me everything this-arena-related, all in one nicely-printable thing" operation.
561 *
562 * Cf.: For finer-grained access to all of the same information, you can access things individually
563 * (the doc headers of each `Arena_info_dump` member states just which Ipc_arena API is the source):
564 * sharded_stats(), memory_manager_stats(), etc. They can be `<< print(...)`ed individually, too.
565 * Plus on these one can perform `flow::util::stat` post-processing: `stats_sum()`, things like that.
566 *
567 * The (fully optional to use) reset ops for these are available on Ipc_arena as well: sharded_stats_reset(),
568 * memory_manager_stats_reset(), etc.
569 *
570 * @param target_info_dump
571 * The non-`->m_fmt` parts shall be assigned. `->m_fmt.m_verbose` is also consulted (so set it, if desired,
572 * before calling); see util::Info_dump_format::m_verbose.
573 */
574 void info_dump(Info_dump* target_info_dump);
575
576 /**
577 * Internally samples certain stats so as to update `_hi_wmark`s that are computed at stat-consume time as
578 * opposed to continuously maintained; as of this writing the ones for memory_manager_stats() and sharded_stats().
579 * (All other `_hi_wmark`s are continuously maintained and don't require any such intervention.)
580 *
581 * Calling memory_manager_stats() and sharded_stats() does this, too; the present method is the idiomatic way
582 * to do it when one does not actually need to consume the current stats.
583 *
584 * @note By default, `Ipc_arena` that is set-up by ipc::session (which is recommended but not required) will
585 * have this done regularly automatically; in which case there is no need for the user to call this.
586 *
587 * ### Thread safety ###
588 * Always safe.
589 */
590 void sample_hi_wmarks();
591
592 /**
593 * Outputs the stat-set stat::Sharded_stats for `*this` arena. See that type's doc header for the
594 * meaning of its sub-`struct`s and stat-members therein.
595 *
596 * `*target_stats` (unless null; see below) is assigned a copy of the currently-accurate set of values.
597 * It is a snapshot copy as of this call and will not change on return. (The `atomic`ity of members is
598 * required by the impl which uses the TL-sharding technique.) Consume via `flow::util::stat::load()` for
599 * best speed; use `stats_assign()` / `print()` (et al); see `flow::util::stat` doc header.
600 *
601 * ### Semantics ###
602 * Generally, as usual, see doc header for the stat-set type (stat::Sharded_stats here). Also:
603 *
604 * The `*_hi_wmark` (`Stat_type::S_HI_WMARK`) stat-members in stat::Sharded_stats have a limited
605 * resolution by the nature of how they are collected (TL-sharding). Supposing `m_x` is a GAUGE,
606 * `m_x_hi_wmark` shall simply be the max value of `m_x` chosen between these points:
607 * - Last time sharded_stats_reset() was called; or `*this` construction if never.
608 * - Each sharded_stats() call since then, including the current one.
609 * - To only update the `_hi_wmark`s, without needing to obtain the resulting `*target_stats`,
610 * use `target_stats == nullptr`. However: the more idiomatic way of doing this is
611 * sample_hi_wmarks().
612 *
613 * ### Thread safety ###
614 * Safe to call concurrently with any other method on the same `*this`. Also applies to our `..._reset()`.
615 *
616 * @param target_stats
617 * The target; see above. If null, the internally stored `_hi_wmark`s are updated only.
618 */
619 void sharded_stats(Sharded_stats* target_stats);
620
621 /// Resets sharded_stats(). The formal meaning of a reset is discussed in `flow::util::stat` doc header.
622 void sharded_stats_reset();
623
624 /**
625 * Returns the stat-set stat::Pool_stats for `*this` arena -- the arena's non-sharded SHM-pool stats (obj-DB
626 * auxiliary-pool RAM + the arena's own SHM-pool create/destroy). See that type's doc header (and its members')
627 * for the meaning of the stat-members.
628 *
629 * The returned reference is to live `atomic<>`s which can change at any moment concurrently; hence even values
630 * grabbed in immediate succession can be slightly mutually incoherent. Consume via `flow::util::stat::load()` /
631 * `stats_assign()` / `print()` (et al); see `flow::util::stat` doc header.
632 *
633 * ### Thread safety ###
634 * Safe to call concurrently with any other method on the same `*this`. Also applies to our `..._reset()`.
635 *
636 * @return See above.
637 */
638 const Pool_stats& pool_stats() const;
639
640 /// Resets pool_stats(). The formal meaning of a reset is discussed in `flow::util::stat` doc header.
641 void pool_stats_reset();
642
643 /**
644 * Returns the memory-manager-level statistics -- one stat::Memory_manager_stats
645 * per native (jemalloc) arena maintained by `*this`, sorted ascending by
646 * native arena-ID -- describing the SHM-provider's supplier-side memory behavior (as of this writing the
647 * page-state / virtual-address-space footprint sub-group).
648 *
649 * @note As of this writing `*this` maintains exactly one such arena, so the returned vector has exactly
650 * one element.
651 *
652 * Each row's identifying fields (stat::Memory_manager_stats::m_uniq_arena_id and `m_native_arena_id`) are
653 * always set. The jemalloc-sourced gauge fields are populated only if the linked jemalloc was built with
654 * statistics support (its `config.stats`); if not, those are left at default (usually zero) rather than producing
655 * partial output. (Some -- not all -- values could potentially still be obtained, but we do not want to
656 * set some and not-set others; thus: no "partial output.")
657 *
658 * Informational purposes only (logging/monitoring/etc.). Values are a fresh, mutually-consistent snapshot
659 * as of this call (to the extent promised by jemalloc), but the reality can change at any moment concurrently
660 * with allocation/deallocation activity.
661 *
662 * ### Semantics ###
663 * Generally, as usual, see doc header for the stat-set type (stat::Memory_manager_stats here). Also:
664 *
665 * The `*_hi_wmark` (`Stat_type::S_HI_WMARK`) stat-members in stat::Memory_manager_stats have a limited
666 * resolution by the nature of the data-source (jemalloc's stats system). Supposing `m_x` is a GAUGE,
667 * `m_x_hi_wmark` shall simply be the max value of `m_x` chosen between these points:
668 * - Last time memory_manager_stats_reset() was called; or `*this` construction if never.
669 * - Each memory_manager_stats() call since then, including the current one.
670 *
671 * Values of `m_x` between those points are not known to us, nor maintained by jemalloc; therefore we can
672 * do no better.
673 *
674 * @note A way of maintaining fairly-useful `_hi_wmark` values here is to consume stats (call
675 * memory_manager_stats()) periodically. Careful: this would be, e.g., second(s) apart -- you don't
676 * want to affect perf of the sytem by making it frequent.
677 * @note ipc::session::shm as of this writing does this automatically to any `Ipc_arena`s it sets up for you.
678 * We recommend for this reason, and many other reasons, to set-up SHM-jemalloc via `ipc::session`.
679 *
680 * ### Thread safety ###
681 * Safe to call concurrently with any other method on the same `*this`. Also applies to our `..._reset()`.
682 * Moreover the returned values remain mutually consistent even if jemalloc statistics are being read elsewhere
683 * in the process concurrently (e.g., by other `Ipc_arena`s or via `Memory_manager::...stats_dump...()`).
684 *
685 * @return See above.
686 */
687 std::vector<Memory_manager_stats> memory_manager_stats();
688
689 /**
690 * Resets memory_manager_stats(). The formal meaning of a reset is discussed in `flow::util::stat` doc header.
691 * See also memory_manager_stats() doc header, "Semantics."
692 */
694
695 /**
696 * Returns the process-global owner-side stat-set
697 * stat::Owner_pool_lookup_global_stats, aggregated across all `Ipc_arena`s in this process. See that type's doc
698 * header for the meaning of its stat-members.
699 *
700 * The returned reference is to live `atomic<>`s which can change at any moment concurrently;
701 * hence even values grabbed in immediate succession can be slightly mutually incoherent. Consume via
702 * `flow::util::stat::load()` / `stats_assign()` / `print()` (et al); see `flow::util::stat` doc header.
703 *
704 * ### Thread safety ###
705 * Safe to call concurrently with anything on any `Ipc_arena` (or none).
706 *
707 * @return See above.
708 */
710
711 /**
712 * Like owner_pool_lookup_global_stats() but returns the process-global stat-set stat::Obj_db_aux_pool_global_stats.
713 * See that type's doc header for the meaning of its stat-members; all notes in owner_pool_lookup_global_stats()
714 * (live `atomic<>`s; consumption; thread safety; reset via global_stats_reset()) apply
715 * equally here.
716 *
717 * @return See above.
718 */
720
721 /**
722 * Resets owner_pool_lookup_global_stats() and obj_db_aux_pool_global_stats(). The formal meaning of a reset is
723 * discussed in `flow::util::stat` doc header.
724 *
725 * @note These are process-global stat-sets; hence this resets them across the entire process.
726 * (Cf. the non-`static` `*_stats_reset()`s.)
727 * To be clear, though, this does not touch anything but the data for the aforementioned
728 * two `static ..._globals_stats()`. Anything accessed per-arena (via non-`static` `*_stats()`) is
729 * reset only via correspoding non-`static` `*_stats_reset()`, for each Ipc_arena of interest.
730 */
731 static void global_stats_reset();
732
733 /**
734 * For informational/stats-adjacent purposes, returns information on currently live SHM-pools within `*this`
735 * arena, sorted by pool ID -- that is, in chronological order of creation. By "live" we mean that these
736 * pools currently have their names in the file-system and are memory-mapped; if a given pool is removed from
737 * the areba, then it would not be included here. (It does *not* necessarily mean
738 * that each pool's entire size Shm_pool_info::m_sz is memory-resident.)
739 *
740 * Informational purposes only (logging/monitoring/etc.). The live pool-set can change at any moment
741 * concurrently, at least if allocation/deallocation activity through `*this` is possible concurrently.
742 *
743 * Same thread-safety notes as memory_manager_stats().
744 *
745 * @return See above.
746 */
747 std::vector<Shm_pool_info> shm_pool_live_info() const;
748
749 /**
750 * Returns the jemalloc-ID for the (only, as of this writing) jemalloc-arena maintained by `*this`;
751 * this identifies that arena to the jemalloc API (e.g. `mallctl()` `"stats.arenas.(id).*"` queries).
752 *
753 * @note The intended use-case for this is for informational purposes, such as if one desires to query
754 * something in the jemalloc-stats API that we perhaps don't already provide in nice form
755 * through the nearby stat-consumption API. Use with care.
756 *
757 * @return See above.
758 */
760
761 /**
762 * (Internal-use) Adds a listener to get updates on shared memory pools. The pointer must remain valid until
763 * after it is removed. The ipc::shm::arena_lend::Owner_shm_pool_listener::notify_initial_shm_pools() method
764 * will be called synchronously within this call.
765 *
766 * @param listener
767 * The listener to add.
768 * @return Whether the listener was registered successfully, which means it wasn't registered previously.
769 */
771
772 /**
773 * (Internal-use) Removes a listener from getting further updates on shared memory pools.
774 *
775 * @param listener
776 * The listener to remove.
777 * @return Whether the listener was deregistered successfully, which means it was registered previously.
778 */
780
781 /* Would be private, but as of this writing some items are used by white-boxy unit-tests.
782 * There's a to-do in class doc header to perhaps change this to a facade/attorney/etc. pattern instead. */
783#ifdef IPC_DOXYGEN_ONLY // Compiler ignores; Doxygen sees: document the below as the private it conceptually is.
784private:
785#else // Compiler sees; Doxygen ignores.
786protected:
787#endif
788 // Constructors.
789
790 /**
791 * Constructor. See create(). It does not call start() however.
792 *
793 * @param logger
794 * See above.
795 * @param memory_manager
796 * See above.
797 * @param pool_name_base
798 * See above.
799 * @param permissions
800 * See above.
801 */
802 Ipc_arena(flow::log::Logger* logger,
803 const std::shared_ptr<Memory_manager>& memory_manager,
804 Shared_name&& pool_name_base,
805 const util::Permissions& permissions);
806
807 // Methods.
808
809 /**
810 * Important helper synchronously invoked, in lieu of `delete arena`, when the `create()`-returned `shared_ptr`
811 * group reaches ref-count zero.
812 *
813 * Impl details for convenience/context: The goal is to indeed `delete this`, and the fact we are being called
814 * implies that all `construct()`ed handles are gone, as are all lent-through `Shm_session`s -- so in that sense
815 * it is okay to destroy the Ipc_arena; but certain non-user-facing items prevent it from being actually
816 * possibly yet; so we must eliminate those first and then `delete this`. Some of these items are potentially
817 * asynchronous, namely when something has to be (opportunistically) done in a different thread.
818 * In chronological order:
819 * -# Thread_lcl_obj_db_admin<Ipc_arena>::forgetting_shm_arena():
820 * Live-`construct()`ed-object-tracking module frees all resources (including aux non-user-data-storing
821 * SHM-pools detail::Lend_tracker_pool).
822 * -# Owner_shm_pool_repository::erase() of every SHM-pool still extant on our behalf.
823 * -# jemalloc::Memory_manager::destroy_arena() of get_jemalloc_arena_id(): Potentially-asynchronous:
824 * -# Destroy every (per-thread) jemalloc-tcache for that jemalloc-arena (creation of each previously
825 * triggered on-demand via 1st `this->allocate()` in that thread; recall that allocate() is always
826 * tcache-enabled).
827 * -# Destroy jemalloc-arena. (Now we can: jemalloc-tcaches all had to be destroyed per jemalloc rules.)
828 * This should unmap vaddr-area + close SHM-pool-handle for every pool still extant on our behalf.
829 * -# `delete this`: (Now we can: jemalloc-arena that had hooks into `*this` = gone.)
830 *
831 * @see destroy_on_obj_db_forgot_us() (helper that does the last N of those steps).
832 */
833 void destroy();
834
835 /**
836 * Creates the jemalloc arena; create() calls it, once, immediately upon construction; and nothing else
837 * must be called before it (allocate() in particular). It is a separate thing (rather than being subsumed
838 * in ctor) due to test harness technicalities; e.g., a test may want to register an event listener
839 * before the first SHM-pool (jemalloc *base* block; see start_impl()) is created within this call.
840 *
841 * If jemalloc fails to create the jemalloc-arena -- a truly exceptional situation -- an exception
842 * shall propagate from inside.
843 *
844 * @warning Not thread-safe.
845 */
846 void start();
847
848 /**
849 * Implements super-class API.
850 *
851 * Among potentially other things: records in #m_shm_pools and in Owner_shm_pool_repository (the latter
852 * making it possible to do pool ID+offset <=> vaddr lookups for at least owner-side Shm_pool_offset_ptr to work)
853 * and notifies registered listeners.
854 *
855 * @param shm_pool
856 * See above.
857 */
858 void on_shm_pool_created(const std::shared_ptr<Shm_pool>& shm_pool) override;
859
860 /**
861 * Implements super-class API: forwards to handle_created_shm_pool().
862 *
863 * Among potentially other things: see note on on_shm_pool_created(): undoes that.
864 *
865 * @param shm_pool
866 * See above.
867 * @param removed_shared_memory
868 * See above.
869 */
870 void on_shm_pool_removed(const std::shared_ptr<Shm_pool>& shm_pool, bool removed_shared_memory) override;
871
872 /**
873 * Returns the memory manager.
874 * @return See above.
875 */
876 const std::shared_ptr<Memory_manager>& get_jemalloc_memory_manager() const;
877
878 /**
879 * Creates a shared memory pool (jemalloc extent hook).
880 *
881 * @todo It would be nice to fill out all the doc headers for the extent hook impls in Ipc_arena with specific
882 * info beyond "see jemalloc docs"; as for example was done with Ipc_arena::create_shm_pool().
883 *
884 * @param address
885 * The desired location to map this memory pool, which can be null for system specification.
886 * @param size
887 * The size of the memory pool to be created.
888 * @param alignment
889 * The value to align the resulting address on, which is generally a multiple of page size.
890 * @param zero
891 * Output parameter indicating whether the contents have been zeroed.
892 * @param commit
893 * Whether the system should designate the pages to be readable and writable (marked active and
894 * can be put into physical memory). If they system is set to overcommit memory, commit is always
895 * enabled. The value will be updated as an output parameter to indicate whether the memory was
896 * committed.
897 * @param arena_id
898 * The memory area that the pool will be placed into.
899 *
900 * @return Upon success, the created memory pool; otherwise, nullptr.
901 */
902 void* create_shm_pool(void* address, size_t size, size_t alignment, bool* zero,
903 bool* commit, arena_id_t arena_id);
904
905 /**
906 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
907 *
908 * @param address
909 * See above.
910 * @param size
911 * See above.
912 * @param committed
913 * See above.
914 * @param arena_id
915 * See above.
916 * @return See above.
917 */
918 bool optional_remove_shm_pool(void* address, size_t size, bool committed, arena_id_t arena_id);
919
920 /**
921 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
922 *
923 * @param address
924 * See above.
925 * @param size
926 * See above.
927 * @param committed
928 * See above.
929 * @param arena_id
930 * See above.
931 * @return See above.
932 */
933 bool remove_shm_pool(void* address, size_t size, bool committed, arena_id_t arena_id);
934
935 /**
936 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
937 *
938 * @param address
939 * See above.
940 * @param size
941 * See above.
942 * @param offset
943 * See above.
944 * @param length
945 * See above.
946 * @param arena_id
947 * See above.
948 * @return See above.
949 */
950 bool commit_memory_pages(void* address, size_t size, size_t offset, size_t length,
951 arena_id_t arena_id);
952
953 /**
954 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
955 *
956 * @param address
957 * See above.
958 * @param size
959 * See above.
960 * @param offset
961 * See above.
962 * @param length
963 * See above.
964 * @param arena_id
965 * See above.
966 * @return See above.
967 */
968 bool decommit_memory_pages(void* address, size_t size, size_t offset,
969 size_t length, arena_id_t arena_id);
970
971 /**
972 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
973 *
974 * @param address
975 * See above.
976 * @param size
977 * See above.
978 * @param offset
979 * See above.
980 * @param length
981 * See above.
982 * @param arena_id
983 * See above.
984 * @return See above.
985 */
986 bool purge_forced_memory_pages(void* address, size_t size, size_t offset, size_t length,
987 arena_id_t arena_id);
988
989 /**
990 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
991 *
992 * @param address
993 * See above.
994 * @param size
995 * See above.
996 * @param size_a
997 * See above.
998 * @param size_b
999 * See above.
1000 * @param committed
1001 * See above.
1002 * @param arena_id
1003 * See above.
1004 * @return See above.
1005 */
1006 bool split_memory_pages(const void* address, size_t size, size_t size_a,
1007 size_t size_b, bool committed, arena_id_t arena_id);
1008
1009 /**
1010 * jemalloc extent hook impl. See jemalloc docs for in/out semantics.
1011 *
1012 * @param address_a
1013 * See above.
1014 * @param size_a
1015 * See above.
1016 * @param address_b
1017 * See above.
1018 * @param size_b
1019 * See above.
1020 * @param committed
1021 * See above.
1022 * @param arena_id
1023 * See above.
1024 * @return See above.
1025 */
1026 bool merge_memory_pages(const void* address_a, size_t size_a,
1027 const void* address_b, size_t size_b,
1028 bool committed, arena_id_t arena_id);
1029
1030
1031private:
1032 // Friends.
1033
1034 /// Friend facade providing privileged access for internal Flow-IPC components.
1035 template<typename Base_t>
1037
1038 // Types.
1039
1040 /// Short-hand for pool ID type.
1042 /// Short-hand for the jemalloc extent-hooks structure with a pointer back to a `this`.
1044 /// Short-hand for a stats type.
1046 /// Single-reader, single-writer mutex.
1047 using Mutex = flow::util::Mutex_non_recursive;
1048 /// Exclusive lock for the mutex.
1049 using Lock = flow::util::Lock_guard<Mutex>;
1050
1051 // Methods.
1052
1053 /**
1054 * Ensures the calling thread has a jemalloc tcache for `*this` arena, creating it if needed -- making
1055 * subsequent deallocate() calls from this thread lock-free (assuming jemalloc knobs have not disabled tcaching
1056 * or something). No-op if tcache use is compiled out (`IPC_SHM_ARENA_LEND_JEMALLOC_NO_TCACHE`).
1057 *
1058 * allocate() will already ensure tcache exists; as of this writing deallocate() will not.
1059 * Hence this is useful if we know or suspect that there shall be potentially many `deallocate()`s
1060 * but not a single (same-arena) `allocate()`. However:
1061 *
1062 * @warning Call it only at known-safe moments (calling thread not exiting -- Thread_cache deinit has not
1063 * started in particular; `*this` alive and not being destroyed). In particular deallocate() itself might
1064 * be happening around that time, so it does not as of this writing do it.
1065 */
1067
1068 /**
1069 * destroy() helper; handles the steps that remain once the possibly-async `forgetting_shm_arena()` finishes.
1070 *
1071 * @param log_ctx
1072 * Concurrently-mutable `Log_context_mt` to use for all remaining synchronous logging for `*this` object.
1073 * Do not use `this->get_logger()` (including via regular `FLOW_LOG_INFO()`/etc. calls)!
1074 * Use `log_ctx`.
1075 */
1077
1078 /**
1079 * start() helper. We lack real support for multiple jemalloc-arenas; but there is a little bit of a start;
1080 * e.g. destroy() properly handles shutting down all the jemalloc-arenas.
1081 *
1082 * There was no reason to blow that stuff away, and we may extend it to be actually useful/used.
1083 * At the moment `n_arenas == 1` always. #m_arena0 holds its ID, so for now we can quickly access it without
1084 * messing with essentially-unneeded #m_arenas container.
1085 *
1086 * @param n_arenas
1087 * # of jemalloc-arenas to create/maintain.
1088 */
1089 void start_impl(unsigned int n_arenas);
1090
1091 /**
1092 * start_impl() helper: creates one jemalloc-arena, wired to our extent hooks, and applies the per-arena
1093 * settings SHM-jemalloc requires. Throws `flow::error::Runtime_error` on failure (see create() "Errors").
1094 *
1095 * @return The jemalloc-arena ID.
1096 */
1098
1099 /**
1100 * Helper core of allocate() that, in reaction to jemalloc-allocate failure, simply returns null (no further action
1101 * such as logging or throwing). It's just tactically convenient to split it up like that.
1102 *
1103 * @param size
1104 * See allocate().
1105 * @return Null if allocate() shall throw; else non-null (<=> success).
1106 */
1107 void* allocate_impl(size_t size);
1108
1109 /**
1110 * Extent hook impl helper for certain extent hooks: given a jemalloc extent at `address` of `size`
1111 * bytes, and a sub-range within it at `offset` bytes extending for `length` bytes, locates the SHM pool
1112 * in #m_shm_pools containing the extent and computes the offset of the sub-range relative to the pool's base address.
1113 *
1114 * Validates that (1) a pool is found at `address`, (2) `[address, address + size)` is entirely within
1115 * that pool, and (3) `offset + length <= size`. On failure logs a warning and returns `false`.
1116 *
1117 * @param address
1118 * Base address of the jemalloc extent.
1119 * @param size
1120 * Size of the jemalloc extent in bytes.
1121 * @param offset
1122 * Byte offset within the extent where the sub-range of interest begins.
1123 * @param length
1124 * Length of the sub-range of interest in bytes; must be positive.
1125 * @param use_case
1126 * Human-readable label for log messages (e.g., "committing", "decommitting").
1127 * Global constant/literals are best for perf.
1128 * @param pool
1129 * On success, set to the SHM pool containing the extent.
1130 * @param pool_offset
1131 * On success, set to the offset of the sub-range start relative to `pool`'s base address
1132 * (i.e., offset-within-pool-of(`address`) + `offset`).
1133 * @return `true` on success; `false` if validation fails.
1134 */
1135 bool compute_pool_and_offset(void* address, size_t size, size_t offset,
1136 size_t length, util::String_view use_case,
1137 std::shared_ptr<Shm_pool>& pool, size_t& pool_offset) const;
1138
1139 /**
1140 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1141 * is stored near `*extent_hooks`). See that method for docs.
1142 *
1143 * @param extent_hooks
1144 * See above.
1145 * @param address
1146 * See above.
1147 * @param size
1148 * See above.
1149 * @param alignment
1150 * See above.
1151 * @param zero
1152 * See above.
1153 * @param commit
1154 * See above.
1155 * @param arena_id
1156 * See above.
1157 * @return See above.
1158 */
1159 static void* create_shm_pool_handler(extent_hooks_t* extent_hooks, void* address,
1160 size_t size, size_t alignment,
1161 bool* zero, bool* commit, unsigned arena_id);
1162
1163 /**
1164 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1165 * is stored near `*extent_hooks`). See that method for docs.
1166 *
1167 * @param extent_hooks
1168 * See above.
1169 * @param address
1170 * See above.
1171 * @param size
1172 * See above.
1173 * @param committed
1174 * See above.
1175 * @param arena_id
1176 * See above.
1177 * @return See above.
1178 */
1179 static bool optional_remove_shm_pool_handler(extent_hooks_t* extent_hooks, void* address,
1180 size_t size, bool committed, unsigned arena_id);
1181
1182 /**
1183 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1184 * is stored near `*extent_hooks`). See that method for docs.
1185 *
1186 * @param extent_hooks
1187 * See above.
1188 * @param address
1189 * See above.
1190 * @param size
1191 * See above.
1192 * @param committed
1193 * See above.
1194 * @param arena_id
1195 * See above.
1196 */
1197 static void remove_shm_pool_handler(extent_hooks_t* extent_hooks, void* address,
1198 size_t size, bool committed, unsigned arena_id);
1199
1200 /**
1201 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1202 * is stored near `*extent_hooks`). See that method for docs.
1203 *
1204 * @param extent_hooks
1205 * See above.
1206 * @param address
1207 * See above.
1208 * @param size
1209 * See above.
1210 * @param offset
1211 * See above.
1212 * @param length
1213 * See above.
1214 * @param arena_id
1215 * See above.
1216 * @return See above.
1217 */
1218 static bool commit_memory_pages_handler(extent_hooks_t* extent_hooks, void* address,
1219 size_t size, size_t offset,
1220 size_t length, unsigned arena_id);
1221
1222 /**
1223 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1224 * is stored near `*extent_hooks`). See that method for docs.
1225 *
1226 * @param extent_hooks
1227 * See above.
1228 * @param address
1229 * See above.
1230 * @param size
1231 * See above.
1232 * @param offset
1233 * See above.
1234 * @param length
1235 * See above.
1236 * @param arena_id
1237 * See above.
1238 * @return See above.
1239 */
1240 static bool decommit_memory_pages_handler(extent_hooks_t* extent_hooks, void* address,
1241 size_t size, size_t offset,
1242 size_t length, unsigned arena_id);
1243
1244 /**
1245 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1246 * is stored near `*extent_hooks`). See that method for docs.
1247 *
1248 * @param extent_hooks
1249 * See above.
1250 * @param address
1251 * See above.
1252 * @param size
1253 * See above.
1254 * @param offset
1255 * See above.
1256 * @param length
1257 * See above.
1258 * @param arena_id
1259 * See above.
1260 * @return See above.
1261 */
1262 static bool purge_forced_memory_pages_handler(extent_hooks_t* extent_hooks, void* address,
1263 size_t size, size_t offset,
1264 size_t length, unsigned arena_id);
1265
1266 /**
1267 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1268 * is stored near `*extent_hooks`). See that method for docs.
1269 *
1270 * @param extent_hooks
1271 * See above.
1272 * @param address
1273 * See above.
1274 * @param size
1275 * See above.
1276 * @param size_a
1277 * See above.
1278 * @param size_b
1279 * See above.
1280 * @param committed
1281 * See above.
1282 * @param arena_id
1283 * See above.
1284 * @return See above.
1285 */
1286 static bool split_memory_pages_handler(extent_hooks_t* extent_hooks, void* address,
1287 size_t size, size_t size_a, size_t size_b,
1288 bool committed, unsigned arena_id);
1289
1290 /**
1291 * jemalloc extent hook impl: forwards to similarly named member function of the proper `this` (which
1292 * is stored near `*extent_hooks`). See that method for docs.
1293 *
1294 * @param extent_hooks
1295 * See above.
1296 * @param address_a
1297 * See above.
1298 * @param size_a
1299 * See above.
1300 * @param address_b
1301 * See above.
1302 * @param size_b
1303 * See above.
1304 * @param committed
1305 * See above.
1306 * @param arena_id
1307 * See above.
1308 * @return See above.
1309 */
1310 static bool merge_memory_pages_handler(extent_hooks_t* extent_hooks,
1311 void* address_a, size_t size_a,
1312 void* address_b, size_t size_b,
1313 bool committed, unsigned arena_id);
1314
1315 /**
1316 * Returns the stat::Uniq_arena_id (owner-process PID + collection-ID) identifying `*this` arena; used by
1317 * the informational/stats accessors (e.g. shm_pool_live_info(), memory_manager_stats()).
1318 *
1319 * @return See above.
1320 */
1322
1323 /**
1324 * Reads the raw jemalloc-sourced stat-members -- the `m_vaddr` and `m_alloc` sub-groups -- of each
1325 * `(*rows)[idx]`, where `idx` is the index of a native arena-ID within #m_arenas; all from one jemalloc stats
1326 * snapshot. Pre-conditions: detail::jemalloc_stats_mutex() locked; jemalloc-stats enabled within jemalloc.
1327 *
1328 * ### Design: Who else refreshes jemalloc stats? ###
1329 * jemalloc serves every `"stats.*"` read from one snapshot for the entire process, replaced by any refresh (see
1330 * detail::stat::epoch_refresh()). Flow-IPC's own stats operations are excluded by detail::jemalloc_stats_mutex()
1331 * (see its doc header, including for what a refresh in the middle of our reads would do). But other code in the
1332 * process may use jemalloc stats/dump APIs directly; nothing in Flow-IPC forbids this, nor should it. No mutex of
1333 * ours can exclude that.
1334 *
1335 * So for those we use jemalloc's epoch counter, which each refresh increments: refresh (noting the epoch); read
1336 * everything; then check the epoch again. It is a time stamp of sorts; the jemalloc `man` page names exactly this
1337 * purpose for it. So: if unchanged then all values came from one snapshot. Otherwise something refreshed
1338 * concurrently, possibly (not necessarily) between two of our reads: read everything again, this time from the
1339 * new snapshot (which is at least as new as ours; so no need to re-refresh). Such outside refreshes are
1340 * presumably rare, so it should take 1, maybe 2, attempts. (In theory outside code refreshing nonstop, more often
1341 * than one attempt takes, would keep us retrying indefinitely.)
1342 *
1343 * @param rows
1344 * `rows->size()` must equal `m_arenas.size()`. Only the `m_vaddr` and `m_alloc` sub-groups are written.
1345 */
1346 void memory_manager_snapshot_read(std::vector<Memory_manager_stats>* rows) const;
1347
1348 /**
1349 * Helper that for the given jemalloc-arena `arena_id` reads the values into stat::Memory_manager_stats::Vaddr
1350 * by querying jemalloc. Pre-conditions: relevant mutex locked (if `*vaddr` is concurrently accessible),
1351 * jemalloc-stats enabled within jemalloc. Intended to be called (only) from memory_manager_snapshot_read().
1352 *
1353 * @param arena_id
1354 * See get_jemalloc_arena_id().
1355 * @param vaddr
1356 * Out-arg: filled with the current gauge values. Must not be null.
1357 */
1359
1360 /**
1361 * Similar to memory_manager_vaddr_stats_read() but applied to stat::Memory_manager_stats::Alloc group.
1362 *
1363 * @param arena_id
1364 * See above.
1365 * @param alloc
1366 * See above.
1367 */
1369
1370 // Data.
1371
1372 /**
1373 * The monotonically increasing collection (arena ID).
1374 *
1375 * One of these globally-uniquely identifies an `Ipc_arena` *of any given one process* (itself identified in relevant
1376 * contexts -- generally `Shm_session` et al when tracking borrowed arenas -- by an `owner_id_t` which as of this
1377 * writing is simply that process's PID). Together those 2 make up a detail::Uniq_collection_id.
1378 */
1379 static std::atomic<collection_id_t> m_collection_id_counter;
1380
1381 /// Owner (this) process's PID, captured at construction; the first half of uniq_arena_id().
1382 uint64_t m_owner_id;
1383
1384 /// The jemalloc memory manager given to create().
1385 const std::shared_ptr<Memory_manager> m_memory_manager;
1386
1387 /// The segregated memory areas. Empty until start(); non-empty and unchanging thereafter.
1388 std::set<arena_id_t> m_arenas;
1389
1390 /// The first (and for now only, in practice) arena-ID in #m_arenas. Used for perf between start() and destroy().
1392
1393 /// The callback functions for jemalloc. Contains the jemalloc extent-hook table + a pointer back to `*this`.
1395
1396 /**
1397 * Start at `false`; at some point in the destroy() flow becomes `true` to prevent event listeners from firing during
1398 * jemalloc-arena destruction which will fire pool-removed hook for each.
1399 *
1400 * It is synchronized out of paranoia: all else being equal, a pool-removed hook can be fired from any end-user
1401 * thread that does (directly or not) deallocate() (maybe even allocate()), and it will check this flag.
1402 * We only *set* this flag in destroy(), and that only should run at the earliest once all `constructed()`ed
1403 * handles are gone; so there would be no [de]allocation by then. Technically if someone called allocate()
1404 * directly without `deallocate()`ing before letting all `Ipc_arena` handles expire, and then did deallocate()
1405 * during the resulting destroy()... there could be concurrency accessing this. Of course in that case probably
1406 * far worse things are about to happen than the lack of perfect cross-thread sync of this silly flag.
1407 * Anyway!!! This is cheap and rarely-accessed, so just whatever. Too many words here!
1408 */
1409 std::atomic<bool> m_destroy_started;
1410
1411 /// Protects access to #m_shm_pools and #m_listeners.
1413
1414 /**
1415 * Collection of created SHM-pools. Insertion and removal is triggered by this chain of events:
1416 * - `[de]allocate()` called =>
1417 * - jemalloc allocate function called (via jemalloc::Memory_manager wrapper) =>
1418 * - jemalloc itself determines a new extent shall be allocated (SHM-pool must be created) or
1419 * ~the opposite; and invokes that extent hook from #m_extent_hooks_wrapper =>
1420 * - create_shm_pool() / remove_shm_pool() is the extent hook impl which locks
1421 * #m_shm_pools_and_listeners_mutex and updates #m_shm_pools + unlocks mutex.
1422 */
1423 std::set<std::shared_ptr<Shm_pool>> m_shm_pools;
1424
1425 /// The SHM pool listeners, each to be informed of relevant events (basically: pool(s) added, pool removed).
1426 std::set<Owner_shm_pool_listener*> m_listeners;
1427
1428 /**
1429 * For `*this` arena, the non-sharded, non-jemalloc-sourced stats, bundled into stat::Pool_stats.
1430 *
1431 * ### Design/performance ###
1432 * Concurrent, non-sharded (as introduced in `flow::util::stat` doc header): each sub-`struct` is one set of
1433 * `atomic`s, updated by various threads and stat-consumed anytime. The aux-pool sub-`struct`'s updates are
1434 * farmed out via detail::Lend_tracker_pool + detail::Use_count_registry -- a handful per `construct()`ing thread
1435 * (1x at the first construct(), ~31x at each newly-activated detail::Use_count_registry quantum, 1x at thread
1436 * exit + last-object GC), i.e. negligible. The owner-pool sub-`struct`'s updates are right in `*this`'s
1437 * extent-hooks (pool create/remove; stat::Owner_pool_stats doc header has more, esp. re. removal).
1438 *
1439 * @see Cf. #m_sharded_stats, which by contrast is full-on TL-sharded.
1440 */
1442
1443 /// Protects #m_sharded_stats, so that sharded_stats() and sharded_stats_reset() can be thread-safe as advertised.
1445
1446 /**
1447 * The stats for `*this` arena that are in the stat-collection purview of detail::Thread_lcl_obj_db_admin
1448 * and detail::Thread_lcl_obj_db_client (collectively the *obj-DB*); as recorded at last stat-consumption (if any;
1449 * else default-cted).
1450 *
1451 * This pattern -- a thread-locally-distributed module (the obj-DB in our case) does TL-sharded stat-collection
1452 * in TL-distributed fashion, while the module's user (that is we) maintains what it returned at last
1453 * stat-consumption (shard-aggregation) if any -- is mandated by the obj-DB module. To wit,
1454 * detail::stat::sharded_stats() and detail::stat::sharded_stats_reset() doc headers specify this.
1455 *
1456 * @see `flow::util::stat` doc header for introduction/discussion of TL-sharding.
1457 * @see Sharded_stats doc header for how this specifically applies to these particular stats.
1458 *
1459 * ### Thread safety ###
1460 * Similar to #m_mem_mgr_base_stats but simpler, as there is no map here, just a single `struct`.
1461 */
1463
1464 /**
1465 * Per-native-arena reset-state ("base") for memory_manager_stats() / memory_manager_stats_reset(): for each
1466 * arena in #m_arenas it holds the since-reset baseline (the ACCUMULATOR values as of the last reset; and the
1467 * running high-water marks). Populated once at start() (one zero-init entry per arena); the structure is fixed
1468 * thereafter (no runtime insert/erase). Its *values* are mutated during stat-consumption/reset; see "Thread
1469 * safety" below.
1470 *
1471 * @see memory_manager_stats(), stat::Memory_manager_stats.
1472 *
1473 * Why `map<>`? Answer: no big reason; as of this writing its `.size() == 1` anyway, because that's the case
1474 * for #m_arenas. Basically #m_arenas is a semi-placeholder (size=1), and the original author used `map<>` for
1475 * it -- in tests arenas were thus auto-sorted by arena-ID, and that was nice? -- so we did the same here;
1476 * that's all. These details will most likely be changed-around for perf (at least), when `.size()` of
1477 * this and of `m_arenas` grows.
1478 *
1479 * ### Thread safety ###
1480 * The public `memory_manager_stats*()` API is advertised as safe to call concurrently with any method on the same
1481 * `*this`, including each other. That is delivered by detail::jemalloc_stats_mutex(), which (for its own,
1482 * process-wide reasons; see its doc header) is held throughout each memory_manager_stats() and
1483 * memory_manager_stats_reset() -- jemalloc reads included, not merely the access to `*this`. It must be held that
1484 * long: otherwise a stat-consumption's raw values could predate a baseline that a concurrent reset installed in
1485 * the meantime, and its since-reset counters would underflow.
1486 *
1487 * Even if that mutex did not deliver this guarantee for free, we would maintain it. Why the contract? In short:
1488 * the various other stats (e.g., #m_pool_stats) have the same property advertised via their relevant API(s).
1489 * There is no black-box reason why this should be any different. To strengthen it further:
1490 *
1491 * Technically the following is not in our purview here but nevertheless --
1492 * `ipc::session::shm::arena_lend::jemalloc`, which is Flow-IPC's recommended method of setting up arenas
1493 * like `*this` (though it can be done standalone also), shall periodically stat-consume (spoiler: partially
1494 * to keep the HI_WMARKs decently useful by sampling regularly, partially perhaps to log) in the background.
1495 * So if this were not thread-safe, the user couldn't safely stat-consume on-demand, as it could be concurrent
1496 * to the background activity. One could argue this "just" demonstrates the usefulness of keeping this
1497 * thread-safe in the first place.
1498 */
1499 std::map<arena_id_t, Memory_manager_stats> m_mem_mgr_base_stats;
1500}; // class Ipc_arena
1501
1502// Template implementations.
1503
1504template<typename T, typename... Ctor_args>
1506{
1511 using Bipc_bad_alloc = ::ipc::bipc::bad_alloc;
1512 using Std_bad_alloc = std::bad_alloc;
1513 // using flow::util::construct_at; // C++20 => can conflict with incidentally included std:: counterpart.
1514 constexpr bool HAS_TRIVIAL_DTOR = std::is_trivially_destructible_v<T>;
1515
1516 static_assert(alignof(T) <= flow::util::max_align_sz(),
1517 "allocate() guarantees only std::max_align_t alignment; over-aligned T is not supported.");
1518
1519 Thread_lcl_obj_db_admin<Ipc_arena>::this_thread_piggy_scan(); // Opportunistic!
1520
1521 // No space => throws Std_bad_alloc as advertised. Nothing else to clean up yet; let it rip.
1522 void* const addr = allocate(sizeof(T));
1523
1524 /* Construct the T itself. As advertised try to help out by setting selves as the current arena.
1525 * As in the disposer below we too try to get a perf boost by not unnecessarily using an arena-activator.
1526 * After all it involves a thread-local variable assignment at the start and then another at the end plus 1-2
1527 * more to remember the previous value; it's quick but not nothing. It's trickier than the dtor situation
1528 * below though. Ideally we'd determine something like "T would not use an allocator to allocate something on its
1529 * behalf." There are some ideas, like maybe checking for the presence of tell-tale STL stuff... but it's tricky and
1530 * might be imperfect and thus arguably not worth it (@todo perhaps revisit). However: using
1531 * is_trivially_destructible_v<T> here too is safe, even though it likely won't catch all the cases -- but
1532 * no false negatives, so it's safe. Basically if it's trivially destructible, it can never allocate things
1533 * on its behalf in any sane way; so that fits the bill. */
1534 const auto obj = static_cast<T*>(addr);
1535 try
1536 {
1537 if constexpr(HAS_TRIVIAL_DTOR)
1538 {
1539 flow::util::construct_at(obj, std::forward<Ctor_args>(ctor_args)...);
1540 }
1541 else
1542 {
1543 Activator ctx{this};
1544 flow::util::construct_at(obj, std::forward<Ctor_args>(ctor_args)...);
1545 }
1546 }
1547 catch (...)
1548 {
1549 /* T ctor threw: before letting it propagate might as well undo what preceded it above (same steps as in
1550 * gc_func() below, minus ~T(), as ctor never completed) -- so as not to leak the buffer in SHM. */
1551 deallocate(addr);
1552 throw;
1553 }
1554
1555 /* Lastly: Set up the (cross-process!!) ref-counted garbage-collection machinery. A global
1556 * ->constructing_obj() + a special shared_ptr disposer (see below) will do it.
1557 * gc_func() = the steps eventually executed at GC-time. Additionally on (yet another) rare
1558 * exception below -- that is while merely setting up GC in here -- we may want to give up and therefore
1559 * "GC" it immediately, before/without the user ever accessing it. Hence this local function (no captures): */
1560
1561 const auto gc_func = [](void* addr, Ipc_arena* arena)
1562 {
1563 if constexpr(!HAS_TRIVIAL_DTOR)
1564 {
1565 Activator ctx{arena};
1566
1567 /* Call object's destructor (works for primitives but might be a pointless perf cost; have seen it in un-good
1568 * STL impls before, though the context was a bit different... but can't hurt to just skip it).
1569 * Key context: an allocator-equipped T (usually STL-compliant container) here will (in T::~T())
1570 * call-through to `arena->deallocate()` for each buffer that T had allocated for itself
1571 * (e.g., for vector<> internal buffer; for list<> the various nodes). In the process more destructors
1572 * may be invoked which would quite possibly do more dtor calling and thus deallocate()ing. And so on.
1573 * Hence the present operator()() is called at just the outer layer, then the inner deallocations (if any)
1574 * happen, and then we call deallocate() on the outer object's memory, last-thing, just below. */
1575 static_cast<T*>(addr)->~T();
1576 }
1577 // else { Trivially destructible => no need to... you get the point. }
1578
1579 arena->deallocate(addr);
1580 }; // auto gc_func =
1581
1582 /* The steps where we set up the cross-process-ref-counted GC machinery are all inside the innermost try{}
1583 * just here. In there is the happy path, but parts can throw, and if one does, we handle the possibilities
1584 * in an intentional best-effort way (keep reading). To wit, assuming something inside the try{} threw:
1585 * -# Catch Bipc_bad_alloc; if that: rethrow as Std_bad_alloc. If not that:
1586 * -# Catch other `bipc::interprocess_exception`s; if that: op_with_possible_bipc_exception() will
1587 * rethrow as Runtime_error with various goodies. If not that:
1588 * -# <whatever other exception E> is just thrown, not caught yet. Intention: let user see E.
1589 * -# All paths there lead to some exception X being thrown.
1590 * So we: catch X (as type ...), cleanup (<= attn!), rethrow X. */
1591
1592 pool_id_t lend_tracker_pool_id;
1593 use_ct_idx_t use_ct_idx;
1594 try
1595 {
1596 op_with_possible_bipc_exception(get_logger(), nullptr, // nullptr => if lambda throws, throw as Runtime_error.
1598 "jemalloc::Ipc_arena::construct():->constructing_obj():likely-aux-pool-creation",
1599 [&]()
1600 {
1601 try
1602 {
1603 Thread_lcl_obj_db_admin<Ipc_arena>::this_thread_obj_db()
1604 ->constructing_obj(&lend_tracker_pool_id, &use_ct_idx, this, &m_pool_stats.m_obj_db_aux_pool, addr,
1605 gc_func);
1606 }
1607 catch (const Bipc_bad_alloc&) // First! Grab it if it's a Bipc_bad_alloc; throw as Std_bad_alloc.
1608 {
1609 /* Bipc_bad_alloc would come from our own mem-algo Use_count_registry::allocate() inside Lend_tracker_pool
1610 * as managed by this_thread_obj_db()->constructing_obj() et al. The log message hedges slightly,
1611 * but as of this writing it should be what it says it probably is.
1612 *
1613 * Subtlety: this must be caught first, as a Bipc_bad_alloc is also a bipc::interprocess_exception
1614 * which would be caught by our next (implied) `catch` inside op_with_possible_bipc_exception(). We want it
1615 * to not be caught by op_...(), once we throw our preferred replacement Std_bad_alloc. */
1616 FLOW_LOG_WARNING("In construct<T>() successfully allocated/constructed the T (type "
1617 "[" << typeid(T).name() << "]), but the cross-process-GC hookup step then "
1618 "threw bipc::bad_alloc (there may be a WARNING with details ahead of this message); "
1619 "throwing as std::bad_alloc. (Probably this thread's per-arena (collection/arena ID "
1620 "[" << get_id() << "]) aux-SHM-pool ran out of space which would indicate a massive "
1621 "number of live construct()ed objects; WARNING above may confirm.)");
1622
1623 throw Std_bad_alloc{};
1624 }
1625 }); /* op_with_possible_bipc_exception():
1626 * Second! Grab it if it's another bipc::interprocess_exception (i.e., not bad_alloc) from
1627 * ->constructing_obj(); op_...() instead WARNs and throws Runtime_error with various goodies. */
1628 // Third! Just let it through if it's something else.
1629 }
1630 catch (...) // If something originally threw, then either it or a replacement exception reaches here.
1631 {
1632 gc_func(addr, this);
1633 throw;
1634 }
1635
1636 /* Recommend reading Disposer a/k/a Owner_obj_disposer_and_mdt class doc header; it is quite instructive
1637 * about the handful of things going on in this disposer, and how it relates to subsequent local
1638 * Shm_session::lend_object() and opposing Shm_session::borrow_object().
1639 *
1640 * Relatedly: Can this throw? Answer: Yes but only in shared_ptr's heap work (std::bad_alloc).
1641 * Normally Flow-IPC code treats heap-alloc-fail as a throw-our-hands-up situation, but let's in this one case think
1642 * about it anyway. In short: by placing this here instead of inside the inner-try{} above, the right
1643 * stuff should happen: shared_ptr should execute our Disposer synchronously -- use-count (currently 1)
1644 * reaches 0 in this_thread_obj_db(), so gc_func() executes. Everything is cleaned up. std::bad_alloc
1645 * then gets propagated out of construct() (which is eminently reasonable on heap-alloc fail). */
1646 return Handle<T>{obj, Disposer{shared_from_this(), lend_tracker_pool_id, use_ct_idx}};
1647} // Ipc_arena::construct()
1648
1649} // namespace ipc::shm::arena_lend::jemalloc
(Internal-use) Thread_lcl_obj_db_admin<A>::this_thread_obj_db() is the current thread's module that t...
Represents/manages a segregated collection of in-SHM (shared memory) objects and buffers allocated us...
Definition: ipc_arena.hpp:177
arena_id_t m_arena0
The first (and for now only, in practice) arena-ID in m_arenas. Used for perf between start() and des...
Definition: ipc_arena.hpp:1391
bool compute_pool_and_offset(void *address, size_t size, size_t offset, size_t length, util::String_view use_case, std::shared_ptr< Shm_pool > &pool, size_t &pool_offset) const
Extent hook impl helper for certain extent hooks: given a jemalloc extent at address of size bytes,...
Definition: ipc_arena.cpp:1299
bool add_shm_pool_listener(Owner_shm_pool_listener *listener)
(Internal-use) Adds a listener to get updates on shared memory pools.
Definition: ipc_arena.cpp:1336
void destroy()
Important helper synchronously invoked, in lieu of delete arena, when the create()-returned shared_pt...
Definition: ipc_arena.cpp:85
arena_id_t create_jemalloc_arena()
start_impl() helper: creates one jemalloc-arena, wired to our extent hooks, and applies the per-arena...
Definition: ipc_arena.cpp:566
void sample_hi_wmarks()
Internally samples certain stats so as to update _hi_wmarks that are computed at stat-consume time as...
Definition: ipc_arena.cpp:1754
void start_impl(unsigned int n_arenas)
start() helper.
Definition: ipc_arena.cpp:520
void pool_stats_reset()
Resets pool_stats(). The formal meaning of a reset is discussed in flow::util::stat doc header.
Definition: ipc_arena.cpp:1537
static void this_thread_gc()
Synchronously garbage-collects, in the calling thread, any objects x = A.construct()ed by this thread...
Definition: ipc_arena.cpp:1565
Uniq_arena_id uniq_arena_id() const
Returns the stat::Uniq_arena_id (owner-process PID + collection-ID) identifying *this arena; used by ...
Definition: ipc_arena.cpp:1468
flow::util::Mutex_non_recursive Mutex
Single-reader, single-writer mutex.
Definition: ipc_arena.hpp:1047
Extent_hooks m_extent_hooks_wrapper
The callback functions for jemalloc. Contains the jemalloc extent-hook table + a pointer back to *thi...
Definition: ipc_arena.hpp:1394
std::map< arena_id_t, Memory_manager_stats > m_mem_mgr_base_stats
Per-native-arena reset-state ("base") for memory_manager_stats() / memory_manager_stats_reset(): for ...
Definition: ipc_arena.hpp:1499
bool decommit_memory_pages(void *address, size_t size, size_t offset, size_t length, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1171
Obj_handle< Pointed_type > Handle
First-class handle – a shared_ptr of some kind by definition – to objects construct<>()ed by this are...
Definition: ipc_arena.hpp:206
static bool merge_memory_pages_handler(extent_hooks_t *extent_hooks, void *address_a, size_t size_a, void *address_b, size_t size_b, bool committed, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:934
std::set< std::shared_ptr< Shm_pool > > m_shm_pools
Collection of created SHM-pools.
Definition: ipc_arena.hpp:1423
std::vector< Shm_pool_info > shm_pool_live_info() const
For informational/stats-adjacent purposes, returns information on currently live SHM-pools within *th...
Definition: ipc_arena.cpp:1497
bool remove_shm_pool(void *address, size_t size, bool committed, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1091
void * allocate(size_t size)
Performs a non-garbage-collected allocation of a buffer in SHM.
Definition: ipc_arena.cpp:638
Mutex m_shm_pools_and_listeners_mutex
Protects access to m_shm_pools and m_listeners.
Definition: ipc_arena.hpp:1412
void on_shm_pool_removed(const std::shared_ptr< Shm_pool > &shm_pool, bool removed_shared_memory) override
Implements super-class API: forwards to handle_created_shm_pool().
Definition: ipc_arena.cpp:1419
static const Obj_db_aux_pool_global_stats & obj_db_aux_pool_global_stats()
Like owner_pool_lookup_global_stats() but returns the process-global stat-set stat::Obj_db_aux_pool_g...
Definition: ipc_arena.cpp:1549
bool purge_forced_memory_pages(void *address, size_t size, size_t offset, size_t length, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1197
static std::shared_ptr< Ipc_arena > create(flow::log::Logger *logger, const std::shared_ptr< Memory_manager > &memory_manager, Shared_name &&pool_name_base, const util::Permissions &permissions)
Creates an instance of this class, factory-style.
Definition: ipc_arena.cpp:63
void sharded_stats_reset()
Resets sharded_stats(). The formal meaning of a reset is discussed in flow::util::stat doc header.
Definition: ipc_arena.cpp:1526
static void * create_shm_pool_handler(extent_hooks_t *extent_hooks, void *address, size_t size, size_t alignment, bool *zero, bool *commit, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:872
void * allocate_impl(size_t size)
Helper core of allocate() that, in reaction to jemalloc-allocate failure, simply returns null (no fur...
Definition: ipc_arena.cpp:668
static std::atomic< collection_id_t > m_collection_id_counter
The monotonically increasing collection (arena ID).
Definition: ipc_arena.hpp:1379
static bool split_memory_pages_handler(extent_hooks_t *extent_hooks, void *address, size_t size, size_t size_a, size_t size_b, bool committed, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:925
Mutex m_sharded_stats_mutex
Protects m_sharded_stats, so that sharded_stats() and sharded_stats_reset() can be thread-safe as adv...
Definition: ipc_arena.hpp:1444
void on_shm_pool_created(const std::shared_ptr< Shm_pool > &shm_pool) override
Implements super-class API.
Definition: ipc_arena.cpp:1375
void memory_manager_snapshot_read(std::vector< Memory_manager_stats > *rows) const
Reads the raw jemalloc-sourced stat-members – the m_vaddr and m_alloc sub-groups – of each (*rows)[id...
Definition: ipc_arena.cpp:1661
bool merge_memory_pages(const void *address_a, size_t size_a, const void *address_b, size_t size_b, bool committed, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1240
uint64_t m_owner_id
Owner (this) process's PID, captured at construction; the first half of uniq_arena_id().
Definition: ipc_arena.hpp:1382
void memory_manager_stats_reset()
Resets memory_manager_stats().
Definition: ipc_arena.cpp:1621
std::set< arena_id_t > m_arenas
The segregated memory areas. Empty until start(); non-empty and unchanging thereafter.
Definition: ipc_arena.hpp:1388
void start()
Creates the jemalloc arena; create() calls it, once, immediately upon construction; and nothing else ...
Definition: ipc_arena.cpp:515
void memory_manager_vaddr_stats_read(arena_id_t arena_id, Memory_manager_stats::Vaddr *vaddr) const
Helper that for the given jemalloc-arena arena_id reads the values into stat::Memory_manager_stats::V...
Definition: ipc_arena.cpp:1694
std::atomic< bool > m_destroy_started
Start at false; at some point in the destroy() flow becomes true to prevent event listeners from firi...
Definition: ipc_arena.hpp:1409
void this_thread_ensure_tcache_exists() const
Ensures the calling thread has a jemalloc tcache for *this arena, creating it if needed – making subs...
Definition: ipc_arena.cpp:865
void memory_manager_alloc_stats_read(arena_id_t arena_id, Memory_manager_stats::Alloc *alloc) const
Similar to memory_manager_vaddr_stats_read() but applied to stat::Memory_manager_stats::Alloc group.
Definition: ipc_arena.cpp:1722
const std::shared_ptr< Memory_manager > m_memory_manager
The jemalloc memory manager given to create().
Definition: ipc_arena.hpp:1385
static void global_stats_reset()
Resets owner_pool_lookup_global_stats() and obj_db_aux_pool_global_stats().
Definition: ipc_arena.cpp:1556
static bool commit_memory_pages_handler(extent_hooks_t *extent_hooks, void *address, size_t size, size_t offset, size_t length, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:898
std::set< Owner_shm_pool_listener * > m_listeners
The SHM pool listeners, each to be informed of relevant events (basically: pool(s) added,...
Definition: ipc_arena.hpp:1426
static void remove_shm_pool_handler(extent_hooks_t *extent_hooks, void *address, size_t size, bool committed, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:890
static bool purge_forced_memory_pages_handler(extent_hooks_t *extent_hooks, void *address, size_t size, size_t offset, size_t length, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:916
Sharded_stats m_sharded_stats
The stats for *this arena that are in the stat-collection purview of detail::Thread_lcl_obj_db_admin ...
Definition: ipc_arena.hpp:1462
Ipc_arena(flow::log::Logger *logger, const std::shared_ptr< Memory_manager > &memory_manager, Shared_name &&pool_name_base, const util::Permissions &permissions)
Constructor.
Definition: ipc_arena.cpp:486
std::vector< Memory_manager_stats > memory_manager_stats()
Returns the memory-manager-level statistics – one stat::Memory_manager_stats per native (jemalloc) ar...
Definition: ipc_arena.cpp:1572
flow::util::Lock_guard< Mutex > Lock
Exclusive lock for the mutex.
Definition: ipc_arena.hpp:1049
arena_id_t get_jemalloc_arena_id() const
Returns the jemalloc-ID for the (only, as of this writing) jemalloc-arena maintained by *this; this i...
Definition: ipc_arena.cpp:1458
bool remove_shm_pool_listener(Owner_shm_pool_listener *listener)
(Internal-use) Removes a listener from getting further updates on shared memory pools.
Definition: ipc_arena.cpp:1357
static const Owner_pool_lookup_global_stats & owner_pool_lookup_global_stats()
Returns the process-global owner-side stat-set stat::Owner_pool_lookup_global_stats,...
Definition: ipc_arena.cpp:1542
Handle< T > construct(Ctor_args &&... ctor_args)
allocate()s sizeof(T) bytes (in SHM), invokes T{...} ctor with the given args, and returns a ref-coun...
Definition: ipc_arena.hpp:1505
static bool optional_remove_shm_pool_handler(extent_hooks_t *extent_hooks, void *address, size_t size, bool committed, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:881
bool split_memory_pages(const void *address, size_t size, size_t size_a, size_t size_b, bool committed, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1227
Pool_stats m_pool_stats
For *this arena, the non-sharded, non-jemalloc-sourced stats, bundled into stat::Pool_stats.
Definition: ipc_arena.hpp:1441
const std::shared_ptr< Memory_manager > & get_jemalloc_memory_manager() const
Returns the memory manager.
Definition: ipc_arena.cpp:1463
void info_dump(Info_dump *target_info_dump)
Fills-out the stats/info contents of the given stat::Arena_info_dump: a printable bundling of stats/i...
Definition: ipc_arena.cpp:1478
void * create_shm_pool(void *address, size_t size, size_t alignment, bool *zero, bool *commit, arena_id_t arena_id)
Creates a shared memory pool (jemalloc extent hook).
Definition: ipc_arena.cpp:943
void destroy_on_obj_db_forgot_us(const flow::log::Log_context_mt *log_ctx)
destroy() helper; handles the steps that remain once the possibly-async forgetting_shm_arena() finish...
Definition: ipc_arena.cpp:277
static bool decommit_memory_pages_handler(extent_hooks_t *extent_hooks, void *address, size_t size, size_t offset, size_t length, unsigned arena_id)
jemalloc extent hook impl: forwards to similarly named member function of the proper this (which is s...
Definition: ipc_arena.cpp:907
const Pool_stats & pool_stats() const
Returns the stat-set stat::Pool_stats for *this arena – the arena's non-sharded SHM-pool stats (obj-D...
Definition: ipc_arena.cpp:1532
void deallocate(void *address) noexcept
Undoes allocate(): deallocates a previously allocated, by this arena, buffer in SHM.
Definition: ipc_arena.cpp:730
void sharded_stats(Sharded_stats *target_stats)
Outputs the stat-set stat::Sharded_stats for *this arena.
Definition: ipc_arena.cpp:1513
bool commit_memory_pages(void *address, size_t size, size_t offset, size_t length, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1146
bool optional_remove_shm_pool(void *address, size_t size, bool committed, arena_id_t arena_id)
jemalloc extent hook impl.
Definition: ipc_arena.cpp:1040
Shm_pool::pool_id_t pool_id_t
Short-hand for pool ID type.
Definition: ipc_arena.hpp:1041
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...
arena_lend::stat::Sharded_stats Sharded_stats
Short-hand for brevity: Thread_lcl_obj_db_* and buddies make frequent mention of this.
uint32_t pool_id_t
Pool ID type.
uint32_t use_ct_idx_t
Index into a given Lend_tracker_pool's bulk storage of arena-construct()ed (outer a/k/a first-class) ...
@ 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-jemalloc SHM-provider. See ipc::shm doc header for introduction.
unsigned int arena_id_t
Arena ID as dictated by jemalloc.
std::shared_ptr< T > Obj_handle
Determines the shared_ptr variety returned by A::construct() and S::borrow_object() for any arena-len...
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
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
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:112
Internal-use shared_ptr disposer object, attached to every cross-process-lendable object handle retur...
This is the friend facade of Owner_shm_pool_collection that exposes non-public APIs hidden from publi...
Printable (with knobs in contained util::stat::Info_dump_format) bundling of stats/info relevant to (...
Sub-group: raw allocation activity – cumulative counts of memory-manager allocate/deallocate operatio...
Sub-group: the arena's virtual-address-space / page-state footprint – how much memory it currently ho...
The memory-manager-level statistics for a single native allocator-arena (for SHM-jemalloc: one jemall...
Similar mission to Obj_db_aux_pool_stats but covers information that, by nature, is not per-arena but...
Similar mission to Owner_pool_stats but covers information that, by nature, is not per-arena but rath...
Bundles together per-arena Owner_pool_stats and Obj_db_aux_pool_stats, so they can be accessed via on...
Obj_db_aux_pool_stats m_obj_db_aux_pool
Sub-struct with individual stats; see type's doc header.
A bundle of several individually-documented stat-sets – object construct/dispose/live and zombie-reap...
This info-struct contains basic properties of a SHM-pool (in a SHM-arena).
IDs an arena, as a GAUGE in contexts like Shm_pool_info and per-arena Borrower_pool_stats,...