Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
lend_tracker_pool.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
33#include "ipc/util/util_fwd.hpp"
35#include <flow/log/log.hpp>
36#include <optional>
37#include <atomic>
38
40{
41
42/**
43 * This internal-use class represents, essentially, a handle to the use-count database (which the class can
44 * open or create via ctor) pertaining to a SHM-arena-lending arena's objects `construct<>()`ed by a single particular
45 * thread (to have constructed at least 1 such object ever) in the arena-owner process. (As of this writing
46 * SHM-jemalloc is the specific arena-lending SHM-provider that uses `*this`es. If other arena-lend
47 * SHM-providers beyond SHM-jemalloc were to exist, they should be able to use it in identical fashion.)
48 *
49 * ### Basic properties ###
50 * The use-count database for that constructing-thread is itself stored in SHM, as a separate pool, with read/write
51 * access, internally composed of essentially only atomic counts. At the expense of some extra RAM use, this allows
52 * both the lender-side process (that originally constructs objects in SHM) and the borrower-side process(es)
53 * (that receives handles into such elsewhere-constructed in-SHM objects) to *performantly* and *safely* (in the face
54 * of crashes at least) track how many extant handles (`shared_ptr`s as of this writing) of each given constructed
55 * object remain; and once a particular object's such ref-count reaches 0 it is possible to garbage-collect it.
56 * The performance comes from the fact that a handle being created just means a ref-count increment, while its
57 * being destroyed (`shared_ptr` group ref-count reaches 0) is a mere decrement: No message-based IPC is required.
58 * The safety comes from the nature of these updates each of which is essentially composed of an atomic increment
59 * or decrement or exchange (etc.).
60 *
61 * ### How to use ###
62 * Assume all discussion is of one specific arena (in SHM-jemalloc represented by a jemalloc::Ipc_arena object) A.
63 * Broadly the users of Lend_tracker_pool can be classified as follows:
64 *
65 * - (Admin mode) When A is asked to `construct<T>(...)` in thread X, it shall either construct or access
66 * a (presumably thread-local) Lend_tracker_pool via the `Create_only` ctor named #m_pool_name (A's choice).
67 * This creates, or accesses, a SHM-pool tracking all objects constructed by this same thread X, until X is joined.
68 * A currently-available use-count slot is then obtained via use_count_new(); the use-count `1` (one) is placed
69 * there (that is: one handle to this `T` is extant).
70 * - When A is asked to `lend_object<T>(p)` a particular handle (to a `T`), `p`, in thread Y (which is often the
71 * original allocating thread X -- but not necessarily), this indicates the handle will be IPC-transmitted to
72 * another process, where via `borrow_object<T>()` that process will obtain its own handle `p1`. To avoid
73 * reaching ref-count 0 before then, the use-count for `p` must immediately (in `lend_object()`) be incremented.
74 * Hence use_count_inc() is called. (The use-count slot index had to be memorized inside the representation of
75 * `p` obtained by `construct()` in preceding bullet point.) E.g., `p` was only just constructed, then
76 * the use-count would reach `2`. (Lending can be done repeatedly, so it can rise further each time.)
77 * - (Admin mode) When a particular handle `p` is invalidated (as of this writing: `shared_ptr<T>` group reaches
78 * ref-count zero, not to be confused with the use-count tracked by a `*this`) in the original constructing
79 * thread X: Such a thread by definition already has a Lend_tracker_pool available, cted as noted above
80 * in "Admin mode." So one calls use_count_dec_admin(), opportunistically so due to thread X being the
81 * thread where this is happening. This method can performantly both decrement the use-count; *and* if it thus
82 * reaches 0 immediately return the use-count slot into the set of available ones.
83 * - Since the use-count slot is returned, thread X can now destroy/deallocate the `T` (none of our business but
84 * that's what happens).
85 * - (Client mode) If a handle `p` or `p1` is invalidated (just as in the preceding bullet), but this occurs
86 * in a borrowing process (not the original constructing one); or in the owner process but in some thread Y
87 * that is not X: The "Admin mode"-cted Lend_tracker_pool object from thread X is thread-local to X and not
88 * applicable in this other thread (or possibly even this process, if this is borrowed `p1` as opposed to
89 * constructed `p`). Therefore one shall either construct or access an (again, presumably thread-local)
90 * Lend_tracker_pool but via the `Open_only` ctor. (The #m_pool_name chosen when originally creating the thing
91 * in first bullet above must be known somehow.) Hence one uses use_count_dec() (cf. use_count_dec_admin())
92 * which shall just decrement the use-count. However, even if it thereby reaches 0, at that moment we cannot
93 * (the way we could in "Admin mode" in bullet 3 above) return the use-count slot just yet. We don't know that
94 * that's what our user would want.
95 *
96 * So far so good, but the last bullet leaves a clear hole: If it was not the original allocating process-and-thread
97 * that happened to be the one to get a given object's use-count down to zero, but instead (and this is more typical)
98 * another thread in the lending or borrowing process, then when/how is the use-count slot returned and/or the
99 * object is destroyed/deallocated? Technically/formally the answer is:
100 * - The user (i.e., object-DB layer above Lend_tracker_pool) must track all extant objects itself; and must
101 * (according to heuristics outside of our purview) at sufficiently frequent times go through them. For each
102 * one, grab its use-count index `idx` (which, again, must be stored inside/alongside each handle like `p`) and
103 * simply call `use_count_return_if_unused(idx)`; this will return `idx` if and only if that use-count is indeed
104 * zero; and if so then return `true`; else `false`. If it returns `true`, the user can destroy/deallocate.
105 * - Formally/technically this can be done by any thread in any process; but read on.
106 * - Because scanning *all* extant constructed objects raises obvious performance concerns, a `*this` provides
107 * two key tools to aid any such algorithm:
108 * - n_unused() returns the total number of extant (in-use/not-returned) use-count slots for which the use-count
109 * has decreased to `0` (zero). Therefore one can stop any exhaustive scan upon reaching this-many
110 * use_count_return_if_unused() calls returning `true`. (It can be a slight, basically temporary under-estimate,
111 * even negative; see n_unused() doc header.)
112 * - emit_unused_ct_idx_hints() emits up to a small number (e.g., 16 as of this writing) of use-count slots
113 * for which the use-count has decreased to zero. Therefore, before (typically even instead of) doing an
114 * exhaustive scan, one can begin with these emitted ones, executing use_count_return_if_unused() on each.
115 * That alone may well already reach `n_unused()` slots; in which case: all done.
116 * - Note: During any such "eviction" algorithm's execution, the value n_unused() and what is returned by
117 * emit_unused_ct_idx_hints() may well change due to concurrent work by other thread(s) and process(es).
118 * Not to worry: They should be viewed as (very good but not at-all-times-exactly-accurate-forever) hints.
119 * Basically: whatever use-count-zero slots one doesn't catch in that sweep, one will catch in the next.
120 * And, if in some situation perf is of no concern, one can always simply do the exhaustive scan.
121 *
122 * ### FYI: when and where the unused-slot scans occur ###
123 * In reality, at least as of this writing -- though the following is not formally required on our account --
124 * the heuristic-scans described just above are probably to be done by each "Admin mode" `Lend_tracker_pool`'s
125 * owner thread X. That means, e.g., the same thread that allocated a thing can naturally deallocate it (helpful
126 * for thread-caching perf of memory-allocation algorithms like jemalloc). As for the heuristic controlling *when*
127 * the scans might occur: As of this writing we piggy-back onto a given thread's alloc-related events triggered
128 * by the end user (e.g., `construct()` itself, `lend_object()`, whatever). The hint tools n_unused() and
129 * emit_unused_ct_idx_hints() make such scans quite quick. Naturally events like thread-joining are also good
130 * triggers for a scan.
131 *
132 * As of this writing these are the next-higher layer of the object-DB functionality.
133 *
134 * @see Thread_lcl_obj_db_admin: The "Admin mode" consumer of `Lend_tracker_pool`s.
135 * @see Thread_lcl_obj_db_client: The "Client mode" consumer of `Lend_tracker_pool`s.
136 *
137 * ### Cleanup: destructors; dead() ###
138 * The destructor of an "Admin mode"-cted Lend_tracker_pool shall remove #m_pool_name-named SHM-pool.
139 * As usual, by the rules of SHM-handles, this only means that pool can no longer be opened by name
140 * (hence a `*this` by that name cannot be "Client mode"-cted), but any existing SHM-handles remain valid.
141 * Indeed this means already-extant "Client mode"-cted `Lend_tracker_pool`s accessing it remain valid.
142 * Bottom line: This is timely cleanup for this particular resource.
143 *
144 * However: In the event of a crash/etc. (where that dtor does not get to run), this will not occur. It is the
145 * user's responsibility to ensure post-crash/etc. cleanup of #m_pool_name. As of this writing SHM-jemalloc, at least,
146 * does just that (by using a certain naming scheme, shared with other related kernel-persistent resources, and
147 * opportunistic cleanup of all such resources at key time points).
148 *
149 * A key point is: when should one destroy an "Admin mode" `*this`? Essentially by definition, if it is destroyed,
150 * then (1) no more objects will be constructed (hence need use-counts) by the owner-process's thread that
151 * precipitated the ction of such an admin-mode `*this`; *and* (2) no more objects tracked in it will be
152 * destroyed by the owner-process either. So once that is the case, that's when.
153 *
154 * This raises the question, what about a client-mode `*this`? When it is destroyed, effectively what matters is
155 * the SHM-pool handle is closed: once this happens to all such client-mode `*this`es pointing at the same pool
156 * (pertaining to threads in the owner -- that aren't the admin/constructing thread -- and threads in the borrowers),
157 * the SHM-memory will actually be returned to the system. This could, over time, be important: if we just let
158 * them live until their respective processes exit/abort, it could be a not-insignificant RAM leak.
159 *
160 * For that reason the SHM-pool itself contains a piece of `Metadata`, an atomic flag accessible by dead() accessor.
161 * It starts `false` (SHM-pool is live); but just before the unlink/pool-handle-closing in admin-mode dtor,
162 * that dtor shall set it to `true` (SHM-pool is dead). No new objects will be tracked by it, and no existing
163 * objects will be destroyed by it, is what `dead() == true` indicates. Therefore our immediate client-mode user
164 * (as of this writing Thread_lcl_obj_db_client in this context) can *periodically* check dead(); if `true` then
165 * it is safe to destroy that Lend_tracker_pool. If they all do this check at some reasonable frequency, then
166 * the RAM-leak is averted.
167 *
168 * What is *periodically*? We leave the answer to that to the (internal) user. It could be time-based and/or
169 * piggy-backed on something. Not our purview. See (as of this writing) Thread_lcl_obj_db_client.
170 *
171 * ### Impl ###
172 * In this case the impl should be reasonably clear from the above description combined with various comments
173 * throughout. The main points are these:
174 * - It keeps a #Lend_tracker_shm (almost = boost.interprocess SHM-pool `basic_managed_shared_memory`),
175 * but instead of any default/general allocation-algorithm we substitute our own Use_count_registry.
176 * - Use_count_registry provides the compact use-count bitmap/array in the form of a very simple allocation-algo
177 * (it can only allocate use-count-integer-sized "objects"). This is where we plop our `atomic<>` use-counts.
178 * - It also provides some header space; in this area we place our Lend_tracker_pool::Metadata which is where we
179 * store the hint data: The count n_unused() and the extant-but-ready-to-return hint array for
180 * emit_unused_ct_idx_hints().
181 */
183 private boost::noncopyable
184{
185public:
186 // Types.
187
188 /// Short-hand for arena_lend::stat::Obj_db_aux_pool_stats;
190
191 // Constants.
192
193 /**
194 * The max number of unused-slot hints emit_unused_ct_idx_hints() can emit.
195 *
196 * Not necessary for algorithms; purely informational such as for logging/testing/etc.
197 */
198 static constexpr size_t S_N_UNUSED_IDX_HINTS = 16;
199
200 // Data.
201
202 /// `true` if admin-mode (create-mode ctor was used); `false` if client-mode (open-mode ctor was used).
203 const bool m_is_creator;
204 /// SHM pool name as set immutably at construction.
206
207 // Constructors/destructor.
208
209 /**
210 * Constructs the admin-mode (creating) handle: creates the SHM-pool named `pool_name` and initializes
211 * its internal metadata. After this, use_count_new() may be called to allocate use-count slots; and
212 * this `*this` is suitable for use_count_dec_admin() as well.
213 *
214 * The created pool also becomes accessible to open-mode (client) handles constructed via the `Open_only`
215 * overload, given the same `pool_name`.
216 *
217 * On failure (e.g., SHM-pool already exists, or OS resource limits hit) an exception is thrown.
218 *
219 * @see update_log_context(): Use it if `log_ctx` and/or `skip_fast_path_verbose_logging_ptr` pointers become
220 * obsolete.
221 *
222 * @param log_ctx
223 * All logging is done through this; it is assumed the `Logger*` stored inside can change at any time.
224 * At each TRACE-or-more-verbose log-call-site, `*log_ctx` is touched only if the
225 * `skip_fast_path_verbose_logging_ptr` fast-path filter passes.
226 * @param skip_fast_path_verbose_logging_ptr
227 * Before any TRACE-or-more-verbose log-op is performed (including the already-cheap `.should_log()`),
228 * this flag's value is loaded (it is assumed it can change at any time); if `false` then no log-op.
229 * @param pool_name
230 * Name of the SHM-pool to create (as for `shm_open()`).
231 * @param mode_tag
232 * Tag type selecting this (create-mode/admin) overload.
233 * @param stats
234 * Stats object to update throughout admin-mode activities. Null will skip stat-keeping.
235 * @param perms
236 * Permissions for the newly created SHM-pool.
237 */
239 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr,
240 const Shared_name& pool_name, util::Create_only mode_tag,
241 Obj_db_aux_pool_stats* stats, const util::Permissions& perms = {});
242
243 /**
244 * Constructs the client-mode (opening) handle: opens the existing SHM-pool named `pool_name` that was
245 * previously created by an admin-mode Lend_tracker_pool (possibly in another process). After this,
246 * use_count_inc() and use_count_dec() may be called to manipulate existing use-count slots.
247 *
248 * ### Error semantics (attention) ###
249 * On failure (e.g., no such pool exists) an exception is thrown, namely `flow::error::Runtime_error`
250 * (itself an `std::runtime_error`).
251 *
252 * In most cases it is best to treat it as a true exceptional condition (typically uncaught or caught somewhere
253 * high-up in the call stack). However, there may be situations (explaining which is outside our purview here)
254 * where the specific error wherein *no such pool name exists* should be treated specially (perhaps seen as
255 * less bad or not at all bad). To check for this condition catch the `Runtime_error exc` and:
256 * `const bool pool_name_not_found = exc.code() == boost::system::errc::no_such_file_or_directory`. E.g.
257 * can catch this; rethrow (`throw;`) if `!pool_name_not_found`; otherwise do something different.
258 *
259 * @param log_ctx
260 * See other ctor.
261 * @param skip_fast_path_verbose_logging_ptr
262 * See other ctor.
263 * @param pool_name
264 * Name of the SHM-pool to open; must have been previously created via the `Create_only` overload.
265 * @param mode_tag
266 * Tag type selecting this (open-mode/client) overload.
267 */
269 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr,
270 const Shared_name& pool_name, util::Open_only mode_tag);
271
272 /**
273 * Destroys this handle. If `*this` is admin-mode (#m_is_creator is `true`), also unlinks the named
274 * SHM-pool from the file system; existing open handles (client-mode `Lend_tracker_pool`s) remain valid
275 * but no new ones can be opened by that name. See the class doc header "Cleanup" section for further
276 * discussion.
277 */
279
280 // Methods.
281
282 /**
283 * Allocates a new use-count slot and initializes its value to 1 (one extant handle: the just-constructed
284 * object). This is to be called in admin mode upon a successful `construct<T>()`.
285 *
286 * If the pool has no free slots (all Use_count_registry::S_USE_COUNTS_CAPACITY are in use), an exception
287 * (likely `bad_alloc`) is thrown.
288 *
289 * @return The index of the newly allocated use-count slot; suitable for storage alongside the constructed
290 * object handle and subsequent use with use_count_inc(), use_count_dec(), etc.
291 */
293
294 /**
295 * Checks whether the use-count at slot `*use_ct_idx_ptr` has reached zero; if so, returns the slot to the
296 * available pool (deallocates it) and sets `*use_ct_idx_ptr` to 0 (not-an-index). Intended for use
297 * in the admin thread's opportunistic-disposal scan: after emit_unused_ct_idx_hints() and n_unused() results
298 * are obtained, one might:
299 * - use_count_return_if_unused() for each of the ones emitted by emit_unused_ct_idx_hints().
300 * - use_count_return_if_unused() for others, perhaps in some heuristic order (e.g., from least-to-most
301 * recently allocated).
302 * - Stop above once use_count_return_if_unused() returned `true` n_unused() times.
303 *
304 * @param use_ct_idx_ptr
305 * Pointer to the use-count-slot index to check. If the slot's use-count is zero, `*use_ct_idx_ptr`
306 * is set to 0 on return.
307 * @return `true` if and only if the use-count was indeed zero and the slot was returned (deallocated).
308 */
309 bool use_count_return_if_unused(use_ct_idx_t* use_ct_idx_ptr);
310
311 /**
312 * Atomically increments the use-count at the given slot. To be called upon `lend_object()`: the handle is
313 * about to be IPC-transmitted, so the use-count must be bumped to prevent premature zero before the
314 * borrowing process creates its own handle (via `borrow_object()`).
315 *
316 * It is a bug to call this on a slot whose use-count is currently zero (the slot must refer to a live object).
317 * If the use-count would overflow the maximum representable value, this is a fatal error (logged, then
318 * `abort()`ed).
319 *
320 * @param use_ct_idx
321 * Index of the use-count slot to increment.
322 * @return The use-count value *after* incrementing.
323 */
324 unsigned int use_count_inc(use_ct_idx_t use_ct_idx);
325
326 /**
327 * Atomically decrements the use-count at the given slot; for use in client mode (#m_is_creator is `false`).
328 * If the use-count thereby reaches zero, this is recorded
329 * internally (incrementing the count readable via n_unused() and, if possible, placing a hint for
330 * emit_unused_ct_idx_hints()).
331 *
332 * Unlike use_count_dec_admin(), this does *not* return the slot even if the count reaches zero; the admin
333 * thread shall do that via use_count_return_if_unused().
334 *
335 * It is a bug to call this on a slot whose use-count is currently zero.
336 *
337 * @param use_ct_idx
338 * Index of the use-count slot to decrement.
339 * @return The use-count value *after* decrementing (0 if unused-referencing).
340 */
341 unsigned int use_count_dec(use_ct_idx_t use_ct_idx);
342
343 /**
344 * Atomically decrements the use-count at the given slot; for use in admin mode (#m_is_creator is `true`: the
345 * thread that created the slot via use_count_new()). If the use-count thereby reaches zero,
346 * the slot is immediately returned (deallocated), and `*use_ct_idx_ptr` is set to 0 (not-an-index).
347 *
348 * This is more efficient than the client-mode use_count_dec() path (see class doc header).
349 *
350 * It is a bug to call this on a slot whose use-count is currently zero.
351 *
352 * @param use_ct_idx_ptr
353 * Pointer to the use-count-slot index. If the count reaches zero, `*use_ct_idx_ptr` is set to 0
354 * on return.
355 * @return The use-count value *after* decrementing (0 if the slot was returned).
356 */
357 unsigned int use_count_dec_admin(use_ct_idx_t* use_ct_idx_ptr);
358
359 /**
360 * Returns the current use-count value at the given slot. This is informational/diagnostic only;
361 * the value may change concurrently, and if one merely wants the present value without intending to
362 * also change it, one likely wants use_count_return_if_unused().
363 *
364 * @param use_ct_idx
365 * Index of the use-count slot to query.
366 * @return The current use-count value (relaxed atomic load).
367 */
368 unsigned int use_count(use_ct_idx_t use_ct_idx) const;
369
370 /**
371 * Collects the indices of use-count slots that have been flagged (by use_count_dec()) as having reached zero
372 * (but not returned to the available set, as use_count_dec_admin() or use_count_return_if_unused() might).
373 * The indices are atomically consumed from internally SHM-stored data; each consumed hint is reset, so that it
374 * can be reused in future similar call(s).
375 *
376 * Typically called by the admin thread's opportunistic-disposal scan as a fast first pass; the caller then
377 * follows up with use_count_return_if_unused() on each emitted index. See the class doc header for the
378 * full suggested algorithm description.
379 *
380 * @tparam Container
381 * A sequence container type supporting `clear()`, `reserve()`, and `push_back()` (e.g., `vector`).
382 * @param container
383 * Output: cleared, then populated with 0 or more use-count-slot indices.
384 */
385 template<typename Container>
386 void emit_unused_ct_idx_hints(Container* container);
387
388 /**
389 * Returns the current count of use-count slots that are unused-referencing (use-count has reached zero
390 * but the slot has not yet been returned/deallocated). This can be used by the admin-thread scan algorithm
391 * as a bound: stop scanning once this-many slots have been found and returned.
392 *
393 * ### Tips ###
394 * The value may change concurrently; it is a hint/estimate, not a hard guarantee for any particular length of time.
395 * However the following truth can be used to your advantage: Unless something might *concurrently return*
396 * (as via use_count_return_if_unused()) slots, the value this returns will not
397 * concurrently go *down*. So if your algorithm only does opportunistic-disposal scans from one thread,
398 * it is never wrong to scan until one has returned N values, where N equals `n_unused()` at scan start.
399 * (If it concurrently *increased*, then a future scan will "see" the new ones.)
400 *
401 * Secondly (foreshadows the thing explained in the next subsection): zero should be treated as "no objects to
402 * reap"; and *less than zero* should *also* be taken to mean that same thing.
403 *
404 * ### Why signed? Can it be negative? How to handle it? ###
405 * (This may feel like a corner case and uninteresting, but it's best not to treat it that way. Dealing with it
406 * is easy, but it's important to understand it.)
407 *
408 * Think of this value -- call it N -- as a tracker of the equilibrium of a simple one-way produce-consume algorithm.
409 * - Product = 1 active use-count slot whose use-count has just reached zero (<=> it has become reapable).
410 * - Producer = use_count_dec() or use_count_dec_admin() which in fact, per name, decrements use-ct -- possibly to
411 * zero. If so:
412 * - Effect on N: `++N`.
413 * - Consumer = *in admin thread* thing that checks a slot and *observes* that use-count
414 * is zero. (Therefore: it frees the slot! Caller reaps the tracked obj.)
415 * - Effect on N: `--N`.
416 *
417 * (Hence N is a good estimate of how many reapable, subsequently-unused objs are currently tracked.) Specifically
418 * then:
419 * - use_count_dec_admin() is delightful, as it opportunistically both produces and immediately consumes.
420 * Per name it runs in admin thread. If it happens to take use-ct to zero (produce!), then (being in admin
421 * thread already) it has "observed" the now-zero-use-ct-bearing slot (consume!). So conceptually it does
422 * `++N` and then immediately `--N` (=> return slot, caller frees obj!). That's why it simply does not touch N.
423 * - use_count_dec(), however, can only (potentially) do the produce part (`++N`); it is running in non-admin
424 * thread. The admin-thread detection of this product has to happen in the next bullet:
425 * - use_count_return_if_unused() (potentially) does the consume part (`--N`)... that is if its hopeful (on
426 * the caller's part) querying of the slot shows use-ct=0. So this is an admin-thread
427 * scan (Thread_lcl_obj_db_admin::unused_obj_scan()) asking `*this` to check that slot based on a suspicion
428 * that it tracks a reapable obj.
429 *
430 * Finally we get to why N can be briefly under-counted or even negative: By design of this overall algorithm, the
431 * opportunistic/heuristic checking via use_count_return_if_unused() occurs orthogonally -- and concurrently -- to
432 * non-admin-thread use_count_dec().
433 * - Produce-code is: produce (set use-ct=0); then account it via `++N`.
434 * - Consume-code is: consume (observe use-ct=0); then account it via `--N`.
435 *
436 * While "produce" happens-before "consume," the accountings-thereof could happen in reverse order. In the window
437 * between the thus-reversed accountings N is under-counted, possibly to below zero. (Observing a negative N
438 * implies under-count, but observing otherwise does not imply lack of under-count.)
439 *
440 * @note If the client thread (recall it's possibly in another process) doing use_count_dec() stalls/dies
441 * before the produce-accounting (`++N`) happens within that function, then the window is not
442 * brief/temporary. In practice this non-briefness/non-transience is rare, but no algorithm should
443 * rely on its being impossible. N can be lastingly-negative, and more generally it can be positive but
444 * lastingly-under-counted. We leave it to the reader to work out the implications on the scan-algo
445 * that uses n_unused(), but in short: future scans will "get it" eventually; and an arena
446 * shutdown will eventually reap everything including any -- very few given how rare a lasting-under-count
447 * is -- possible straggler reapable objs.
448 *
449 * So then lastly: How does one, big-picture, use N (and as a special case, what if it is zero or even negative)?
450 * Answer: Just as we said: "used by the admin-thread scan algorithm as a bound: stop scanning
451 * once this-many slots have been found and returned." Zero means all-done with the reaping; therefore
452 * negative means same.
453 *
454 * @return See above.
455 */
456 signed int n_unused() const;
457
458 /**
459 * Returns `false` (indicating liveness of the SHM-pool we handle) throughout; until the admin-mode
460 * (#m_is_creator is `true`) `*this`'s destructor executes; then `true` forevermore.
461 *
462 * @see Class doc header "Cleanup" section for how and why to use this. Spoilers: In particular note that while it
463 * will work fine regardless of #m_is_creator, we anticipate its key use would be when
464 * `m_is_creator == false` (client-mode). Further: once this returns `true` it should be safe and indeed
465 * advisable to delete `*this`. (For admin-mode `*this`, it would never return `true` until dtor runs
466 * in the first place.)
467 *
468 * The value behaves in fully-synchronized manner (as-if protected by a standard mutex). That is it is always
469 * set and (here) read with `seq_cst` (most restrictive) memory-ordering. As such it is assumed it will not be
470 * accessed sufficiently frequently for this to matter.
471 *
472 * @return See above.
473 */
474 bool dead() const;
475
476 /**
477 * Updates the logging context initially provided via ctor.
478 *
479 * @param log_ctx
480 * See ctor.
481 * @param skip_fast_path_verbose_logging_ptr
482 * See ctor.
483 */
485 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr);
486
487private:
488 // Types.
489
490 /**
491 * The type of value stored in each use-count slot in #m_pool.
492 *
493 * This must (as enforced by a `static_assert`) have width equal to Use_count_registry::S_ALLOC_SZ, which is
494 * the only allowed arg value we can pass to `m_pool->allocate()`. Or in English: the slots that
495 * the SHM-pool allocator system is designed to allocate/deallocate shall store `Atomic_use_ct`s, so
496 * the size of slot it allocates/deallocates must equal the size of the value.
497 *
498 * ### Tuning ###
499 * Why have this be larger? Answer: The max value of `Atomic_use_ct::value_type` (2^32 minus 1, as of this writing)
500 * is the max number of outstanding `Shm_session::lend_object()` calls + 1 Ipc_arena::construct() call (outstanding
501 * meaning the resulting `shared_ptr` group has not reached ref-count 0). If it is exceeded, everything explodes;
502 * so if this is large, then that won't happen.
503 *
504 * Why have it be smaller? Answer: RAM use by the underlying SHM-pool (referenced by #m_pool). So consider,
505 * for example, if `Atomic_use_ct::value_type` is `uint8_t`; max value 255. Really it is only *required* to
506 * `.lend_object(x)` if some process needs access to `x`; if a given process has already borrowed it, then it
507 * is possible to avoid lending it again to *that* process, sending it to it via IPC, etc. If this is always
508 * followed, then this having value 255 means you've loaned `x` to 255 processes -- more than enough for the vast
509 * majority of use cases. That said: knowing that a certain process already has `x`, and thus we shouldn't lend
510 * it again, probably sounds easy cosmically speaking -- but in reality it may not be easy at all, depending on
511 * the design of the applications and all. (We -- ygoldfel and co. -- discovered this in practice; and raised
512 * the width of this value and hence the value Use_count_registry::S_ALLOC_SZ from 1 byte to 4 bytes.)
513 *
514 * ### Formal status: typed access to SHM ###
515 * `Atomic_use_ct`s live in a SHM pool. We routinely use the pattern wherein we have a `use_ct_idx_t` as input,
516 * from who knows where (received over an IPC transport, quite likely); then we call use_ct_idx_to_ptr() on it;
517 * this computes the location of that use-count slot, as a vaddr `Atomic_use_ct*` located inside the SHM-pool
518 * bounds; and then we dereference this `atomic` and do things like `auto use_ct_prev = (...).fetch_sub(1)` to it.
519 * Thing is, given the computation in use_ct_idx_to_ptr(), this breaks aliasing guidelines: a byte address is
520 * computed, `reinterpret_cast<>`ed to an unrelated pointer type `Atomic_use_ct*`, and then dereferenced.
521 * Is it fine + is there any alternative? Short answer: in practice yes and no respectively. Longer answer:
522 *
523 * Omitted for now. It is notoriously difficult not to accidentally lie when talking about the C++ abstract
524 * machine. We'd like to fill this in with formally and practically accurate verbiage (to-do below), but for now
525 * these takeaways:
526 * - The aforementioned pattern (including the dodgy-looking `reinterpret_cast<>` followed by deref) is
527 * reasonable.
528 * - There is no alternative -- such as the usual suggestions (`memcpy()`, placement-new) -- that applies here.
529 * - We properly `construct_at()` each `Atomic_use_ct` in the owner process; further modifications, regardless of
530 * process, are all lock-free (`static_assert()` nearby).
531 *
532 * @note These notes may equally apply to `atomic`s in `struct` Metadata; Metadata::m_n_unused in particular
533 * is important.
534 *
535 * @todo In Lend_tracker_pool::Atomic_use_ct doc header, explain formally and practically how accesses to these
536 * values work from the point C++ abstract-machine PoV.
537 *
538 * @todo The width of Lend_tracker_pool::Atomic_use_ct and the corresponding constant Use_count_registry::S_ALLOC_SZ
539 * is set to 4 bytes as of this writing; but it may be prudent to make this configurable at compile-time instead,
540 * to allow for the trade-off discussed in nearby "Tuning" doc header section.
541 */
542 using Atomic_use_ct = std::atomic<uint32_t>;
543 static_assert(sizeof(Atomic_use_ct::value_type) == Use_count_registry::S_ALLOC_SZ,
544 "Size of 1 slot in the use-count-storing custom pool-allocator must equal the size of the value "
545 "we in fact store in each such slot.");
546 static_assert(Atomic_use_ct::is_always_lock_free,
547 "Atomic_use_ct must be lock-free: a non-lock-free atomic is implemented via per-process lock state "
548 "which cannot work across processes; and lock-freedom implies address-freedom in practice; "
549 "address-freedom makes our cross-process (SHM) use-count ops work. See also its doc header.");
550
551 /**
552 * Type of #m_pool. It is a bipc_ext::Sparse_managed_shm, as opposed to `bipc::managed_shared_memory`, so that
553 * the pool is sparse: a given use-count quantum takes RAM only once first used; see its doc header.
554 */
556
557 /// The header stored in #m_pool, ahead of the actual use-counts.
558 struct Metadata
559 {
560 // Data.
561
562 /// See Lend_tracker_pool::dead().
563 std::atomic<bool> m_dead{false};
564
565 /**
566 * Essential for the perf of related algorithms, this stores the count of slots considered unused-referencing.
567 * An *unused-referencing* slot is defined as follows:
568 * - It refers to an object that has been constructed but not disposed; a/k/a it is live.
569 * It is allocated in #m_pool.
570 * - The live object to which it refers no longer has any referents cross-process; that is
571 * no process to have constructed and/or borrowed the object has a live `shared_ptr` group referencing it.
572 * Therefore it can (and should) be disposed at the earliest opportunity (in our system: by the thread
573 * that constructed it; unless that thread has exited; then [details omitted here]).
574 *
575 * ### Rationale ###
576 * Generally (though not exclusively) objects are disposed opportunistically (as of this writing by
577 * Thread_lcl_obj_db_admin::this_thread_piggy_scan()). When scanning for such objects to dispose, the algorithm
578 * can stop upon encountering #m_n_unused such objects. (Yes, `m_n_unused` can be concurrently incremented during
579 * this procedure; that is fine: we'll get it next time then, when that same algorithm runs opportunistically.)
580 *
581 * ### It can be (slightly) negative ###
582 * Please see n_unused() doc header which provides a nice mental model for what #m_n_unused counts, why
583 * it can temporarily under-count (and even therefore possibly be negative), and what it implies about using
584 * this value.
585 *
586 * Since it can be negative, it is signed.
587 */
588 std::atomic<signed int> m_n_unused{0};
589 static_assert(decltype(m_n_unused)::is_always_lock_free, "Another atomic counter in SHM.");
590
591 /**
592 * Essential for the perf of related algorithms, this stores (a limited number of) indices to unused-referencing
593 * slots (by the definition of *unused-referencing* listed in #m_n_unused doc header). This, too,
594 * is used by the opportunistic-disposal algorithm (see #m_n_unused doc header for brief description):
595 * - While naively that algorithm would simply check all objects, perhaps in some likelier-to-be-effective
596 * order such as LRU-to-MRU, and stop on finding `m_n_unused` objects to dispose...
597 * - ...with #m_unused_idx_hints that algorithm can start directly with slots known to be unused-referencing.
598 * This array is as follows: in no particular order:
599 * - If an element equals 0, it is to be ignored.
600 * - If an element is 1 or higher, then the `use_ct_idx` equal to the stored value, minus 1, is likely
601 * to be unused-referencing. The algorithm must confirm this and dispose if confirmed; and place 0 here
602 * (whether confirmed or not). (The check for non-zero and replacement by zero shall be done with
603 * an atomic-exchange operation.) Hence it is a (highly likely to be accurate) hint.
604 *
605 * This is essentially a very high-perf communication pipe from Lend_tracker_pool (client-type) instances:
606 * if I am a client-type (used open-mode/client ctor) Lend_tracker_pool and have decremented a slot's value to
607 * reach 0, then I shall *communicate* the index of that slot to the admin-type Lend_tracker_pool, so that
608 * the admin thread can opportunistically check the slot having *consumed* this index.
609 *
610 * The effectiveness of this is really only limited by the size of this array `m_unused_idx_hints`. If it is
611 * "full" (no element currently equals 0), then we cannot add a hint to it; the scan algorithm will have to
612 * handle the hints that are there and then find the un-hinted unused-referencing slot(s) with a more naive
613 * scan (but still bounded by #m_n_unused).
614 *
615 * ### Tuning/performance notes ###
616 * The larger it is, the more hints can be stored. However there is a trade-off: it takes longer to scan
617 * the hints array itself -- it can be (and typically is, even) empty (storing only zeroes), yet all of it
618 * makes sense to be scanned. (That can be bounded by #m_n_unused actually; but even with that, it requires
619 * scanning when inserting as well; and anyway until `m_n_unused` is exhausted, one may have had to scan
620 * many empty/zero elements first.)
621 *
622 * So #S_N_UNUSED_IDX_HINTS should typically be chosen to make #m_unused_idx_hints fast-scannable with a
623 * `find_if()` type search; perhaps 16 or 32. Side note: for random-access-iterable containers, gcc STL
624 * `find_if()` (and derived) algorithm impl has manual unrolling that'll check several elements per loop
625 * iteration -- which is to say there is good reason to expect such a search to be quite quick, if
626 * #S_N_UNUSED_IDX_HINTS is kept low, particularly.
627 */
628 boost::array<std::atomic<use_ct_idx_t>, S_N_UNUSED_IDX_HINTS> m_unused_idx_hints;
629 static_assert(decltype(m_unused_idx_hints)::value_type::is_always_lock_free,
630 "Another atomic counter in SHM.");
631 }; // struct Metadata
632
633 // Friends.
634
635 // For access to our internals.
636 friend std::ostream& operator<<(std::ostream&, const Lend_tracker_pool&);
637
638 // Methods.
639
640 /**
641 * Helper that, given the pre-condition that use-count slot `*use_ct_idx` has been decremented to 0 but is
642 * currently referring to a live object, makes it no longer refer to a live object. The latter means: use_count_new()
643 * in a relevant admin Lend_tracker_pool -- possibly but not necessarily `*this` -- shall be able to
644 * return `*use_ct_idx`.
645 *
646 * For discipline, `*use_ct_idx` shall be set to 0 (not-an-index), indicating that the value at entry to this
647 * function is no longer usable.
648 *
649 * @param was_not_counted_as_unused
650 * If `false`, Metadata::m_n_unused (in #m_metadata pointee) had been incremented to count the 0-storing
651 * slot `*use_ct_idx`. (Therefore we shall decrement it.) If `true`, it had not been counted (therefore
652 * we won't decrement it). Rationale: if we are admin then for efficiency we may be able to pass `true` here.
653 * @param use_ct_idx
654 * See above.
655 */
656 void use_count_return_bc_unused(use_ct_idx_t* use_ct_idx, bool was_not_counted_as_unused);
657
658 /**
659 * The reverse of use_ct_idx_to_ptr(), this converts a process-local vaddr pointing at a use-count slot
660 * to a process-independent index referring to that slot (suitable for transmission via IPC, storage in SHM, etc.).
661 *
662 * @param use_ct_ptr
663 * Pointer to a use-count slot in #m_pool.
664 * @return See above.
665 */
666 use_ct_idx_t use_ct_ptr_to_idx(const Atomic_use_ct* use_ct_ptr) const;
667
668 /**
669 * The reverse of use_ct_ptr_to_idx().
670 *
671 * @param use_ct_idx
672 * See use_ct_ptr_to_idx() (namely what it returns).
673 * @return See above.
674 */
676
677 /**
678 * Forwards to `m_pool->allocate(sz)` while recording -- accurately but performantly -- into `*m_stats`
679 * as needed. Admin-mode only. Can throw (if ran out of use-count slots).
680 *
681 * @warning Do not use `m_pool->allocate()` directly.
682 *
683 * @param sz
684 * As for `m_pool->allocate()`.
685 * @return As from `m_pool->allocate()`.
686 */
687 void* pool_allocate(size_t sz);
688
689 /**
690 * Forwards to `m_log_ctx->log_while_locked()`. We do not sub-class `Log_context_mt`, and doing this
691 * results in quicker, or at least more direct, code than via FLOW_LOG_SET_LOCKED_CONTEXT().
692 *
693 * This allows the use of `FLOW_LOG_..._LOCKED()` (or `log_while_locked()` can be used directly).
694 *
695 * @param logging_task
696 * See `flow::log::Log_context_mt::log_while_locked()`.
697 */
698 template<typename Logging_task>
699 void log_while_locked(Logging_task&& logging_task) const;
700
701 /**
702 * Reads #m_skip_fast_path_verbose_logging_ptr pointee's current value.
703 * @return See above.
704 */
706
707 // Data.
708
709 /// See update_log_context().
711
712 /// See update_log_context().
713 const std::atomic<bool>* m_skip_fast_path_verbose_logging_ptr;
714
715 /**
716 * Un-nullified permanently in ctor, this is the handle to the high-perf use-count-tracking SHM-pool pertaining
717 * to objects constructed from this thread (if create-mode/admin ctor invoked) or another thread in this or
718 * other process (if open-mode/client ctor invoked).
719 */
720 std::optional<Pool> m_pool;
721
722 /**
723 * Pointer to the Metadata in #m_pool; also used as the base for `use_ct_idx` indices produced or consumed
724 * by `*this` APIs.
725 * - In its dereferenced `Metadata` capacity:
726 * - If we are in admin mode (create-mode/admin ctor invoked): We initially populate this in ctor right
727 * after creating the SHM-pool #m_pool.
728 * - Otherwise (client mode: open-mode/client ctor invoked): It is ready-to-go by the time we open
729 * the SHM-pool #m_pool in ctor.
730 * - As an offset base: Since Use_count_registry::get_metadata() is formally guaranteed to point to the
731 * start of the data area, with actual use-count slots stored in bulk following the Metadata:
732 * - use_ct_ptr_to_idx(), given a pointer to a use-count slot, subtracts `m_metadata` (the pointer)
733 * to get the offset in bytes; then divides by the slot width to obtain the index.
734 * - It is quick: subtract, then shift right.
735 * - use_ct_idx_to_ptr(), conversely, will first multiply by the slot width, then add `m_metadata` (the pointer).
736 * That gets the actual pointer to the slot.
737 * - It is quick: shift left, then add.
738 * - Hence, say `Metadata` (with padding determined by Use_count_registry) uses 256 bytes; and
739 * `sizeof(Atomic_use_ct) == 4` (the slot width). Then the lowest possible index returned by
740 * use_ct_ptr_to_idx() is (256 / 4) = 64. Then the next ones are 65 (at 65 * 4 bytes off base), 66, etc.
741 * It is not necessary to really be aware of that typically; use_ct_ptr_to_idx() and use_ct_idx_to_ptr()
742 * will work symmetrically, and that's that; but when reading logs it helps to be able to interpret the
743 * indices and addresses.
744 */
746
747 /// Stats to update in admin-mode; null => skips stat-keeping.
749}; // class Lend_tracker_pool
750
751// Free functions: in *_fwd.hpp.
752
753// Template implementations.
754
755template<typename Container>
757{
758 constexpr auto N = decltype(Metadata::m_unused_idx_hints)::static_size;
759
760 container->clear(); // With vector<> typically just updates a scalar size member.
761 container->reserve(N); // Prevent reallocations on ->push_back().
762
763 auto& hints = m_metadata->m_unused_idx_hints;
764 use_ct_idx_t use_ct_idx;
765 for (auto& hint : hints)
766 {
767 use_ct_idx = hint.exchange(0, std::memory_order_relaxed);
768 if (use_ct_idx != 0)
769 {
770 // Reminder: it is saved ++ed, as 0 is a special value.
771 container->push_back(--use_ct_idx);
772
773 /* It might be tempting here to do the following (and promise it in our contract):
774 * return use_ct_idx into the available pool (use_count_return()), knowing that the caller will presumably
775 * clean up the actual object assigned to use_ct_idx, due to our emitting it to them.
776 * That is not safe though; it is possible a client thread (while we are currently in admin thread)
777 * placed the hint here just after decrementing use_ct_idx-th slot to 0, but between those two events
778 * this (admin) thread had noticed (perhaps in an earlier piggy-scan pass) that slot=0 while doing
779 * the non-hints-array-enabled search phase; so they might have already returned it to the pool, hence
780 * it is not 0 -- it is not an active slot at all. It might even have been assigned to another object
781 * entirely! To tell the difference, at the least we'd have to be able to check whether a slot is
782 * allocate()d or not (as of this writing we can't; anyway Use_count_registry would need to expose such a thing,
783 * with some computational cost, checking its internal bitmap and all).
784 *
785 * So that'd be wrong, or at least non-trivial to make right. So keep it simple and clean:
786 * the hint array hints; our caller has the real info on which objects are live and can be returned;
787 * it will use the hints to hopefully get to the slot=0 ones, but if they happen to not help, which
788 * is the worst case, then that's fine. Whereas the above mess would not have been fine. */
789 }
790 } // for (hint in hints)
791
793 {
794 log_while_locked([&](auto&& get_logger, auto&& get_log_component)
795 {
796 const auto logger_ptr = get_logger();
797 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_TRACE, get_log_component()))
798 {
799 for (const auto use_ct_idx : *container)
800 {
801 FLOW_LOG_TRACE_WITHOUT_CHECKING
802 ("Lend-tracker pool [" << *this << "]: Emitted/reset unused-object-not-yet-deleted hint: "
803 "use-count is/was 0 at idx [" << use_ct_idx << "], "
804 "process-local addr [" << use_ct_idx_to_ptr(use_ct_idx) << "] off base addr "
805 "[" << m_metadata << "].");
806 /* This would be nice to log too, but we chose to optimize by not logging inside the search loop.
807 * "The hint is (was) in hint-array slot "
808 * "[" << (&hint - hints.begin()) << "] (0-based) of [" << N << "] (1-based).");
809 * @todo Maybe keep an array of those if logging enabled? */
810 }
811 FLOW_LOG_TRACE_WITHOUT_CHECKING
812 ("Lend-tracker pool [" << *this << "]: Emitted/reset [" << container->size() << "] "
813 "unused-object-not-yet-deleted hints.");
814 }
815 });
816 } // if (!skip_fast_path_verbose_logging())
817 // else { Skip ~any cycle use on logging along fast-path. }
818} // Lend_tracker_pool::emit_unused_ct_idx_hints()
819
820template<typename Logging_task>
821void Lend_tracker_pool::log_while_locked(Logging_task&& logging_task) const
822{
823 m_log_ctx->log_while_locked(logging_task);
824}
825
826} // namespace ipc::shm::arena_lend::detail
This internal-use class represents, essentially, a handle to the use-count database (which the class ...
use_ct_idx_t use_ct_ptr_to_idx(const Atomic_use_ct *use_ct_ptr) const
The reverse of use_ct_idx_to_ptr(), this converts a process-local vaddr pointing at a use-count slot ...
void emit_unused_ct_idx_hints(Container *container)
Collects the indices of use-count slots that have been flagged (by use_count_dec()) as having reached...
friend std::ostream & operator<<(std::ostream &, const Lend_tracker_pool &)
Prints string representation of the given Lend_tracker_pool to the given ostream.
Lend_tracker_pool(const flow::log::Log_context_mt *log_ctx, const std::atomic< bool > *skip_fast_path_verbose_logging_ptr, const Shared_name &pool_name, util::Create_only mode_tag, Obj_db_aux_pool_stats *stats, const util::Permissions &perms={})
Constructs the admin-mode (creating) handle: creates the SHM-pool named pool_name and initializes its...
void update_log_context(const flow::log::Log_context_mt *log_ctx, const std::atomic< bool > *skip_fast_path_verbose_logging_ptr)
Updates the logging context initially provided via ctor.
const flow::log::Log_context_mt * m_log_ctx
See update_log_context().
const Shared_name m_pool_name
SHM pool name as set immutably at construction.
unsigned int use_count_inc(use_ct_idx_t use_ct_idx)
Atomically increments the use-count at the given slot.
std::optional< Pool > m_pool
Un-nullified permanently in ctor, this is the handle to the high-perf use-count-tracking SHM-pool per...
static constexpr size_t S_N_UNUSED_IDX_HINTS
The max number of unused-slot hints emit_unused_ct_idx_hints() can emit.
const std::atomic< bool > * m_skip_fast_path_verbose_logging_ptr
See update_log_context().
std::atomic< uint32_t > Atomic_use_ct
The type of value stored in each use-count slot in m_pool.
signed int n_unused() const
Returns the current count of use-count slots that are unused-referencing (use-count has reached zero ...
bool dead() const
Returns false (indicating liveness of the SHM-pool we handle) throughout; until the admin-mode (m_is_...
Obj_db_aux_pool_stats * m_stats
Stats to update in admin-mode; null => skips stat-keeping.
bool skip_fast_path_verbose_logging() const
Reads m_skip_fast_path_verbose_logging_ptr pointee's current value.
void log_while_locked(Logging_task &&logging_task) const
Forwards to m_log_ctx->log_while_locked().
bool use_count_return_if_unused(use_ct_idx_t *use_ct_idx_ptr)
Checks whether the use-count at slot *use_ct_idx_ptr has reached zero; if so, returns the slot to the...
unsigned int use_count_dec_admin(use_ct_idx_t *use_ct_idx_ptr)
Atomically decrements the use-count at the given slot; for use in admin mode (m_is_creator is true: t...
void use_count_return_bc_unused(use_ct_idx_t *use_ct_idx, bool was_not_counted_as_unused)
Helper that, given the pre-condition that use-count slot *use_ct_idx has been decremented to 0 but is...
Metadata * m_metadata
Pointer to the Metadata in m_pool; also used as the base for use_ct_idx indices produced or consumed ...
unsigned int use_count_dec(use_ct_idx_t use_ct_idx)
Atomically decrements the use-count at the given slot; for use in client mode (m_is_creator is false)...
use_ct_idx_t use_count_new()
Allocates a new use-count slot and initializes its value to 1 (one extant handle: the just-constructe...
void * pool_allocate(size_t sz)
Forwards to m_pool->allocate(sz) while recording – accurately but performantly – into *m_stats as nee...
const bool m_is_creator
true if admin-mode (create-mode ctor was used); false if client-mode (open-mode ctor was used).
unsigned int use_count(use_ct_idx_t use_ct_idx) const
Returns the current use-count value at the given slot.
Atomic_use_ct * use_ct_idx_to_ptr(use_ct_idx_t use_ct_idx) const
The reverse of use_ct_ptr_to_idx().
static constexpr size_t S_ALLOC_SZ
This important constant defines the mandatory size of each allocation – that is argument to allocate(...
Similar to bipc's basic_managed_shared_memory, except that if a ctor created the SHM-pool in the file...
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
Segregated private stuff for ipc::shm::arena_lend.
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) ...
shm::bipc_ext::Sparse_managed_shm< Use_count_registry, ::ipc::bipc::null_index > Lend_tracker_shm
The managed-SHM-pool type underlying each Lend_tracker_pool: Use_count_registry as the memory-algorit...
bipc::permissions Permissions
Short-hand for Unix (POSIX) permissions class.
Definition: util_fwd.hpp:158
bipc::open_only_t Open_only
Tag type indicating an ideally-atomic open-if-exists-else-fail operation.
Definition: util_fwd.hpp:152
bipc::create_only_t Create_only
Tag type indicating a create-unless-exists-else-fail operation.
Definition: util_fwd.hpp:155
The header stored in m_pool, ahead of the actual use-counts.
std::atomic< bool > m_dead
See Lend_tracker_pool::dead().
boost::array< std::atomic< use_ct_idx_t >, S_N_UNUSED_IDX_HINTS > m_unused_idx_hints
Essential for the perf of related algorithms, this stores (a limited number of) indices to unused-ref...
std::atomic< signed int > m_n_unused
Essential for the perf of related algorithms, this stores the count of slots considered unused-refere...
Stats about the RAM use of the arena-lending SHM-provider's auxiliary (non-user-data) SHM-pools which...