Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
lend_tracker_pool.cpp
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
28#include "ipc/util/util.hpp"
32#include <flow/util/util.hpp>
33#include <limits>
34#include <cstdint>
35
37{
38
40 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr,
41 const Shared_name& pool_name, util::Create_only,
42 Obj_db_aux_pool_stats* stats, const util::Permissions& perms) :
43 m_is_creator(true),
44 m_pool_name(pool_name),
45 m_log_ctx(log_ctx),
46 m_skip_fast_path_verbose_logging_ptr(skip_fast_path_verbose_logging_ptr),
47 m_stats(stats)
48{
50 using flow::util::stat::fetch_add;
51 using flow::util::stat::update_hi_wmark;
52 // using flow::util::construct_at; // C++20 => can conflict with incidentally included std:: counterpart.
53 using boost::io::ios_all_saver;
54
55 const auto POOL_SZ = Pool::Segment_manager::get_min_size() + Use_count_registry::base_offset();
56
57 log_while_locked([&](auto&& get_logger, auto&& get_log_component)
58 {
59 const auto logger_ptr = get_logger();
60 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_INFO, get_log_component()))
61 {
62 ios_all_saver saver{*(logger_ptr->this_thread_ostream())}; // Revert std::oct/etc. soon.
63 FLOW_LOG_INFO_WITHOUT_CHECKING
64 ("Lend-tracker pool (addr [" << this << "]): " // Do not print *this yet -- not until we're ready to rock.
65 "Constructing heap handle to tracker pool at name [" << m_pool_name << "] in "
66 "create-only (admin) mode; pool size [" << flow::util::ceil_div(POOL_SZ, size_t(1024)) << "Ki]; "
67 "perms = [" << std::setfill('0') << std::setw(4) << std::oct << perms.get_permissions() << "]. "
68 "Then will initialize state.");
69 }
70
71 // m_pool is null. Try to create+open it; it may throw exception which is allowed by contract.
73 (logger_ptr, nullptr, error::Code::S_SHM_BIPC_MISC_LIBRARY_ERROR, "Lend_tracker_pool(): Pool()", [&]()
74 {
75 m_pool.emplace(util::CREATE_ONLY, m_pool_name, POOL_SZ, perms);
76 /* The pool is sparse by design (Pool docs): we never commit() it, as that is the whole point (only the
77 * use-count quanta actually used shall take RAM). So the SHM-object handle (FD) is useless to us;
78 * drop it (FDs are a resource). */
79 m_pool->close_shm_object_handle();
80 });
81 }); // log_while_locked()
82
83 // Great... and we are the creating guy... so get it ready for use (whether via *this or others cted via other ctor).
84
85 // Cannot do the following due to null_index: construct<Metadata>(::ipc::bipc::anonymous_instance)().
86 m_metadata = static_cast<decltype(m_metadata)>(pool_allocate(sizeof(*m_metadata)));
87 flow::util::construct_at(m_metadata);
88
89 /* This atomic<> m_metadata->m_n_unused is constructed via atomic<>{0} (see Metadata definition).
90 * Thus omitting: m_metadata->m_n_unused = 0; */
91
92 /* However m_metadata->m_unused_idx_hints is an array<atomic<use_ct_idx_t>>, and array<> elements are emphatically
93 * *not* initialized. So formally it would appear the most correct thing to do is use construct_at() on each
94 * element. */
95 auto& hints = m_metadata->m_unused_idx_hints;
96 for (auto hint = hints.begin(); hint != hints.end(); ++hint)
97 {
98 flow::util::construct_at(hint, 0);
99 }
100
101 assert((m_metadata == m_pool->core()->get_segment_manager()->get_memory_algorithm().get_metadata<Metadata>())
102 && "By definition this should be that: we just made it!");
103} // Lend_tracker_pool::Lend_tracker_pool(Create_only)
104
106 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr,
107 const Shared_name& pool_name, util::Open_only) :
108 m_is_creator(false),
109 m_pool_name(pool_name),
110 m_log_ctx(log_ctx),
111 m_skip_fast_path_verbose_logging_ptr(skip_fast_path_verbose_logging_ptr),
112 m_stats(nullptr) // It's an admin-mode thing.
113{
114 log_while_locked([&](auto&& get_logger, auto&& get_log_component)
115 {
116 // m_pool is null. Try to open it; it may throw exception which is allowed by contract.
118 (get_logger(), nullptr, error::Code::S_SHM_BIPC_MISC_LIBRARY_ERROR, "Lend_tracker_pool(): Pool()", [&]()
119 {
121 m_pool->close_shm_object_handle(); // See creator ctor.
122 });
123
124 m_metadata = m_pool->core()->get_segment_manager()->get_memory_algorithm().get_metadata<Metadata>();
125
126 FLOW_LOG_INFO
127 ("Lend-tracker pool [" << *this << "]: "
128 "Constructed heap handle to tracker pool at name [" << m_pool_name << "] in open-only (client) mode.");
129 });
130} // Lend_tracker_pool::Lend_tracker_pool(Open_only)
131
133{
134 using flow::util::stat::fetch_sub;
135
136 if (m_is_creator)
137 {
138 FLOW_LOG_INFO_LOCKED
139 ("Lend-tracker pool [" << *this << "]: Creating/admin pool (essentially pool handle) shutting down. This "
140 "indicates no tracked-objects relevant to this pool will ever be allocated or deallocated. Therefore "
141 "(1) setting the DEAD flag to true in pool; and (2) "
142 "removing pool-name [" << m_pool_name << "] from file-system (any already-opened handles will not be "
143 "disrupted, though for unrelated reasons they too should be shutting down or already shut-down).");
144
145 // See doc header "Cleanup" section and dead() doc header for explanation.
146 m_metadata->m_dead.store(true);
147 /* Detail: We used seq_cst (fully-synchronized) memory ordering there as advertised on dead().
148 * Why do this here (and also the .load() in dead())? Answer: Basically it means no surprises and not having
149 * to think about corner cases. Only question is, is dead() called often enough for this to affect perf.
150 * Formally speaking dead() doc header says it is assumed it will not be called frequently enough for that.
151 * How to guarantee that is outside Lend_tracker_pool's (our) purview. */
152
153 // Remove SHM-pool from file-system; RAM is returned once all handles -- `Lend_tracker_pool`s -- get destroyed.
154 Error_code err_code_ignored;
155 log_while_locked([&](auto&& get_logger, auto&&)
156 {
157 shm::classic::Pool_arena::remove_persistent(get_logger(), m_pool_name, &err_code_ignored);
158 });
159
160 if (m_stats)
161 {
162 m_pool->core()->get_segment_manager()->get_memory_algorithm().stats_record_at_deletion(m_stats);
163 }
164 }
165 else
166 {
167 FLOW_LOG_INFO_LOCKED
168 ("Lend-tracker pool [" << *this << "]: Non-creating/admin pool (essentially pool handle) shutting down.");
169
170 assert((!m_stats)
171 && "As of this writing the convention is that client-mode Lend_tracker_pool keeps no stats. "
172 "Maintenance bug in Lend_tracker_pool?");
173 }
174} // Lend_tracker_pool::~Lend_tracker_pool()
175
177{
178 return m_metadata->m_dead.load();
179 // See dtor for relevant details (including why we just used seq_cst instead of a more relaxed access mode).
180}
181
183{
184 // using flow::util::construct_at; // C++20 => can conflict with incidentally included std:: counterpart.
185 using std::exception;
186
187 Atomic_use_ct* use_ct_ptr{};
188 try
189 {
190 /* Cannot do the following due to null_index:
191 * m_pool->core()->construct<Atomic_use_ct>(::ipc::bipc::anonymous_instance)(1). */
192 use_ct_ptr = static_cast<decltype(use_ct_ptr)>(pool_allocate(sizeof(*use_ct_ptr)));
193 }
194 catch (const exception& exc)
195 {
196 FLOW_LOG_WARNING_LOCKED
197 ("Lend-tracker pool [" << *this << "]: "
198 "Unable to obtain a new use-count slot; appears a thread created a huge # of live objects at "
199 "the same time; we cannot handle this. Our code may need to change to allow for more slots "
200 "at the cost of somewhat higher mandatory aux RAM use. Will re-throw likely bad_alloc: "
201 "[" << exc.what() << "].");
202 throw;
203 }
204 assert(use_ct_ptr && "allocate() would have thrown (with WARNING logged), else should have returned non-null.");
205
206 flow::util::construct_at(use_ct_ptr, 1);
207
208 const auto idx = use_ct_ptr_to_idx(use_ct_ptr);
210 {
211 FLOW_LOG_TRACE_LOCKED("Lend-tracker pool [" << *this << "]: "
212 "New object presumably constructed: new use-count `1` registered at idx [" << idx << "], "
213 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "].");
214 }
215 // else { Skip ~any cycle use on logging along fast-path. }
216
217 return idx;
218} // Lend_tracker_pool::use_count_new()
219
221{
222 auto const use_ct_ptr = use_ct_idx_to_ptr(use_ct_idx);
223 auto use_ct = static_cast<unsigned int>(use_ct_ptr->fetch_add(1, std::memory_order_relaxed));
224
225 assert((use_ct != 0)
226 && "Use-count starts at 1; and on reaching 0 must never be touched again. Bug?");
227
228 // (There's a minor, very unlikely problem here; see discussion at the end of function.)
229 constexpr auto MAX_USE_CT = std::numeric_limits<Atomic_use_ct::value_type>::max();
230 if (use_ct == MAX_USE_CT)
231 {
232 FLOW_LOG_FATAL_LOCKED("Lend-tracker pool [" << *this << "]: "
233 "Object use-count INCremented from [" << use_ct << "] at idx [" << use_ct_idx << "], "
234 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "]; "
235 "overflow! This means you've lent -- and held simultaneously without dropping by receiving "
236 "process -- this object that many times, plus one. Either reconsider your use case (are you "
237 "lending many times to one process? if so can you detect this and simply copy the shared_ptr "
238 "instead?); or modify Use_count_registry::S_ALLOC_SZ and Lend_tracker_pool::Atomic_use_ct "
239 "definitions to handle even more lending. Aborting.");
240 assert(false && "Object use-count INCremented from the max-value of Atomic_use_ct::value_type; "
241 "overflow! This means you've lent -- and held simultaneously without dropping by receiving "
242 "process -- this object that many times, plus one. Either reconsider your use case (are you "
243 "lending many times to one process? if so can you detect this and simply copy the shared_ptr "
244 "instead?); or modify Use_count_registry::S_ALLOC_SZ and Lend_tracker_pool::Atomic_use_ct "
245 "definitions to handle even more lending. Aborting.");
246 std::abort();
247 }
248 // else
249
250 ++use_ct;
251
253 {
254 FLOW_LOG_TRACE_LOCKED("Lend-tracker pool [" << *this << "]: "
255 "Object use-count INCremented to [" << use_ct << "] at idx [" << use_ct_idx << "], "
256 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "].");
257 }
258 // else { Skip ~any cycle use on logging along fast-path. }
259
260 return use_ct;
261
262 /* @todo (Possibly) The above code that checks for overflow is nice and elegant, but it comes with a caveat:
263 * The post-increment ->fetch_add(1) that returns MAX_USE_CT will cause an ~instant crash, at which point loosely
264 * speaking all bets are off. However, the corrupt value (0) would still be written. This in itself wouldn't
265 * dealloc that slot; but a concurrent scan in an admin-mode other Lend_tracker_pool (assuming we are another
266 * thread => client-mode Lend_tracker_pool trying to lend => trying to use_count_inc()) could (assuming the
267 * hint mechanism + n_unused() don't prevent it) do use_count_return_if_unused(), detect the corrupt-0 value,
268 * dealloc, return true, destroy object (general mayhem). This would be racing against the abort() which would
269 * end all this in more civilized (relatively) fashion. In this extremely unlikely -- even given how unlikely we
270 * feel the overflow itself is -- scenario, the result probably would still look like a crash of some kind...
271 * probably... or maybe some other madness (but soon followed by the abort() here).
272 *
273 * A suggested fix, roughly:
274 * do
275 * {
276 * if (use_ct == MAX_USE_CT) { ...log/assert/abort()... }
277 * new_use_ct = use_ct + 1;
278 * }
279 * while (!use_ct_ptr->compare_exchange_weak(use_ct, new_use_ct, std::memory_order_relaxed));
280 * This should avoid storing the bad value while remaining close to equally as fast in terms of the instructions
281 * generated. However it is somewhat precious given the actual danger involved. Still, it should guard against
282 * any theoretical entropy present in the existing code. Reconsider at some point; for now we don't rock the boat. */
283} // Lend_tracker_pool::use_count_inc()
284
286{
287 using std::find_if;
288
289 auto const use_ct_ptr = use_ct_idx_to_ptr(use_ct_idx);
290
291 /* Attn: `release`, not `relaxed`. This is (almost) the classic atomic ref-count pattern: whoever's decrement brings
292 * the count to 0 (or, in our case, whoever later observes the 0 -- the admin thread, possibly in another
293 * process) goes on to destroy the object and deallocate its memory (which jemalloc may then reuse). Every
294 * holder's accesses to the object must happen-before that. `release` here, paired with an
295 * `atomic_thread_fence(acquire)` in the destroying thread once it sees 0 (see use_count_dec_admin(),
296 * use_count_return_if_unused()), guarantees it. (With `relaxed` it is formally a data race.)
297 * The (almost) same pattern (and a longer discussion) = in shm::classic::Pool_arena::handle_deleter_impl(); there all
298 * parties are in the deleter, whereas here the 0-observer can also be use_count_return_if_unused().
299 *
300 * The other ops on Atomic_use_ct (use_count_new()'s initialization, use_count_inc()) are `relaxed`: as with
301 * `shared_ptr` increments, the new holder's access to the object is already ordered by whatever conveyed the
302 * handle to it (IPC in particular). */
303 auto use_ct = static_cast<unsigned int>(use_ct_ptr->fetch_sub(1, std::memory_order_release));
304
305 assert((use_ct != 0) && "Use-count on reaching 0 must never be touched again... yet has become negative. Bug?");
306
307 --use_ct;
308
309 decltype(Metadata::m_n_unused)::value_type n_unused{};
310 decltype(Metadata::m_unused_idx_hints)::iterator found_hint_ptr{};
311 if (use_ct == 0)
312 {
313 n_unused = m_metadata->m_n_unused.fetch_add(1, std::memory_order_relaxed);
314
315 // (Log about it below.)
316
317 auto& hints = m_metadata->m_unused_idx_hints;
318 use_ct_idx_t to_replace;
319
320 /* At least in gcc-9 STL, there's a random-accessor-iterator loop-unroll specialization of find_if(), so this
321 * can actually help perf in this search, versus a manual `for` and the like. */
322 ++use_ct_idx; // 0 is used as a special value, so add 1 before saving in hint array.
323 found_hint_ptr = find_if(hints.begin(), hints.end(), [&](auto& hint)
324 {
325 to_replace = 0; // It might be overwritten by ->compare_exchange_strong(), so have to keep reassigning.
326 if (hint.compare_exchange_strong(to_replace, use_ct_idx, std::memory_order_relaxed))
327 {
328 return true;
329 }
330 // else
331 return false;
332 });
333
334 // Either we saved it and are done (and will log below), or it was full and are done (and will not so log).
335 } // if (use_ct == 0)
336 // else if (use_ct > 0) { That's it for now; festivities begin once another use_count_dec() gets it to zero. }
337
338 // Combine all fast-path logging in one code area for perf.
339 if (!skip_fast_path_verbose_logging())
340 {
341 log_while_locked([&](auto&& get_logger, auto&& get_log_component)
342 {
343 const auto logger_ptr = get_logger();
344 if (logger_ptr && logger_ptr->should_log(flow::log::Sev::S_TRACE, get_log_component()))
345 {
346 FLOW_LOG_TRACE_WITHOUT_CHECKING
347 ("Lend-tracker pool [" << *this << "]: "
348 "Object use-count DECremented to [" << use_ct << "] at idx [" << (use_ct_idx - 1) /* <- was ++ed */ << "], "
349 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "]. If "
350 "reached zero will record this in pool metadata area presently.");
351 if (use_ct == 0)
352 {
353 FLOW_LOG_TRACE_WITHOUT_CHECKING
354 ("Yes, reached zero: count of all such (unused-object-not-yet-deleted) slots is now INCremented to "
355 "[" << (n_unused + 1) << "]; recorded in pool metadata area. Communicated this fact "
356 "via hint array, if possible.");
357 const auto& hints = m_metadata->m_unused_idx_hints;
358 if (found_hint_ptr != hints.end())
359 {
360 FLOW_LOG_TRACE_WITHOUT_CHECKING
361 ("Recorded index of unused-object-not-yet-deleted slot in hint-array slot "
362 "[" << (found_hint_ptr - hints.begin()) << "] (0-based) of "
363 "[" << decltype(Metadata::m_unused_idx_hints)::static_size << "] (1-based).");
364 }
365 }
366 } // if (TRACE-logging on)
367 }); // log_while_locked()
368 } // if (!skip_fast_path_verbose_logging())
369 // else { Skip ~any cycle use on logging along fast-path. }
370
371 return use_ct;
372} // Lend_tracker_pool::use_count_dec()
373
374unsigned int Lend_tracker_pool::use_count_dec_admin(use_ct_idx_t* use_ct_idx_ptr)
375{
376 using std::find_if;
377
378 auto& use_ct_idx = *use_ct_idx_ptr;
379 auto const use_ct_ptr = use_ct_idx_to_ptr(use_ct_idx);
380 // Attn: `release` (+ atomic_thread_fence() below if 0 reached). See use_count_dec() for explanation.
381 auto use_ct = static_cast<unsigned int>(use_ct_ptr->fetch_sub(1, std::memory_order_release));
382
383 assert((use_ct != 0) && "Use-count on reaching 0 must never be touched again... yet has become negative. Bug?");
384
385 --use_ct;
386
387 if (!skip_fast_path_verbose_logging())
388 {
389 FLOW_LOG_TRACE_LOCKED
390 ("Lend-tracker pool [" << *this << "]: "
391 "Object use-count DECremented admin-style to [" << use_ct << "] at idx [" << use_ct_idx << "], "
392 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "]. If "
393 "reached zero will (admin-style!) return this slot of use-count immediately. "
394 "(If so then presumably caller, as admin, can finish the tracked-object's existence immediately, so "
395 "no need to record this use-ct=0 in pool metadata.)");
396 }
397 // else { Skip ~any cycle use on logging along fast-path. }
398
399 if (use_ct == 0)
400 {
401 std::atomic_thread_fence(std::memory_order_acquire); // Attn: caller shall destroy object. See use_count_dec().
402 use_count_return_bc_unused(use_ct_idx_ptr,
403 // We never incremented m_n_unused -- and must not decrement it! Admin-style.
404 true);
405 }
406
407 return use_ct;
408} // Lend_tracker_pool::use_count_dec_admin()
409
410bool Lend_tracker_pool::use_count_return_if_unused(use_ct_idx_t* use_ct_idx_ptr)
411{
412 auto& use_ct_idx = *use_ct_idx_ptr;
413 auto const use_ct_ptr = use_ct_idx_to_ptr(use_ct_idx);
414 if (use_ct_ptr->load(std::memory_order_relaxed) != 0)
415 {
416 if (!skip_fast_path_verbose_logging())
417 {
418 FLOW_LOG_TRACE_LOCKED("Lend-tracker pool [" << *this << "]: Object would be deleted if use-count were 0: "
419 "but it is not; ignoring; registered at idx [" << use_ct_idx << "], "
420 "process-local addr [" << use_ct_ptr << "] off base addr [" << m_metadata << "].");
421 }
422 // else { Skip ~any cycle use on logging along fast-path. }
423 return false;
424 }
425 // else
426
427 /* Attn: caller shall destroy object. / See use_count_dec() for explanation; as it says there, we are the observer
428 * of use-ct=0 it had potentially set. So in a classic shared_ptr ref-count pattern this would be physically
429 * near the release-decrement code/run right after it. (You can see that in the alternative/optimized path:
430 * use_count_dec_admin().) In contrast for us it's in a separate function (use_count_dec() versus us) -- but more
431 * saliently use_count_dec() runs in another thread, often in another process (from
432 * Thread_lcl_obj_db_client::disposing_obj()); then presumably some other user code would execute, and then an
433 * opportunistic Thread_lcl_obj_db_admin::unused_obj_scan() in the admin thread would call us. Formally the
434 * atomic-fu situation, as far as this use-ct slot is concerned, is the ~same either way. */
435 std::atomic_thread_fence(std::memory_order_acquire);
436
437 use_count_return_bc_unused(use_ct_idx_ptr,
438 // Non-admin-style: having earlier reached 0, m_n_unused was incremented; so decrement it.
439 false);
440 return true;
441} // Lend_tracker_pool::use_count_return_if_unused()
442
443void Lend_tracker_pool::use_count_return_bc_unused(use_ct_idx_t* use_ct_idx_ptr, bool was_not_counted_as_unused)
444{
445 auto& use_ct_idx = *use_ct_idx_ptr;
446 auto const use_ct_ptr = use_ct_idx_to_ptr(use_ct_idx);
447
448 if (was_not_counted_as_unused)
449 {
450 if (!skip_fast_path_verbose_logging())
451 {
452 FLOW_LOG_TRACE_LOCKED("Lend-tracker pool [" << *this << "]: Object presumably being deleted: "
453 "returning slot of use-count (by contract equal to 0) registered at idx "
454 "[" << use_ct_idx << "], process-local addr [" << use_ct_ptr << "] off base addr "
455 "[" << m_metadata << "]. Admin-style, count of all unused-object-not-yet-deleted slots "
456 "was *neither* incremented nor decremented.");
457 }
458 // else { Skip ~any cycle use on logging along fast-path. }
459 }
460 else // if (!was_not_counted_as_unused)
461 {
462 auto n_unused = m_metadata->m_n_unused.fetch_sub(1, std::memory_order_relaxed);
463
464 if (!skip_fast_path_verbose_logging())
465 {
466 FLOW_LOG_TRACE_LOCKED("Lend-tracker pool [" << *this << "]: Object presumably being deleted: "
467 "returning slot of use-count (by contract equal to 0) registered at idx "
468 "[" << use_ct_idx << "], process-local addr [" << use_ct_ptr << "] off base addr "
469 "[" << m_metadata << "]. Count of all unused-object-not-yet-deleted slots is now "
470 "DECremented to [" << (n_unused - 1) << "]; recorded in pool metadata area.");
471 }
472 // else { Skip ~any cycle use on logging along fast-path. }
473 } // else if (!was_not_counted_as_unused)
474
475 // Cannot do the following due to null_index: m_pool->core()->destroy_ptr(use_ct_ptr).
476 use_ct_ptr->~Atomic_use_ct();
477 m_pool->core()->deallocate(use_ct_ptr);
478
479 use_ct_idx = 0;
480} // Lend_tracker_pool::use_count_return_bc_unused()
481
482unsigned int Lend_tracker_pool::use_count(use_ct_idx_t use_ct_idx) const
483{
484 return use_ct_idx_to_ptr(use_ct_idx)->load(std::memory_order_relaxed);
485}
486
487use_ct_idx_t Lend_tracker_pool::use_ct_ptr_to_idx(const Atomic_use_ct* use_ct_ptr) const
488{
489 constexpr auto USE_CT_SZ = static_cast<use_ct_idx_t>(sizeof(Atomic_use_ct));
490 return static_cast<use_ct_idx_t>(reinterpret_cast<uintptr_t>(use_ct_ptr)
491 - reinterpret_cast<uintptr_t>(m_metadata))
492 / USE_CT_SZ;
493}
494
495Lend_tracker_pool::Atomic_use_ct* Lend_tracker_pool::use_ct_idx_to_ptr(use_ct_idx_t use_ct_idx) const
496{
497 constexpr auto USE_CT_SZ = static_cast<use_ct_idx_t>(sizeof(Atomic_use_ct));
498 return reinterpret_cast<Atomic_use_ct*>
499 (reinterpret_cast<uintptr_t>(m_metadata)
500 + (use_ct_idx * USE_CT_SZ));
501}
502
503signed int Lend_tracker_pool::n_unused() const
504{
505 return m_metadata->m_n_unused.load(std::memory_order_relaxed);
506}
507
508void* Lend_tracker_pool::pool_allocate(size_t sz)
509{
510 /* Basically we just want to forward to m_pool->core()->allocate(sz) which really forwards to
511 * Use_count_registry::allocate(). However, to get nice but performant stats in m_stats, follow
512 * the protocol documented on Use_count_registry::stats_record(). (See also dtor + stats_record_at_deletion().)
513 *
514 * @todo Use_count_registry::stats_record() doc header "Rationale" section explains why we do this; but upon
515 * gazing at the below 5-line dance, just to get the stats to work, maybe it would be after all better to
516 * access mem_algo.allocate() directly and have it take m_stats and just update it as-needed based on
517 * its own, local, tracking of what is now accessed by us via stat_quanta_active(). It would at least produce
518 * shorter code; as for total perf impact... probably small. Though it has not as of this writing been profiled. */
519 if (m_stats)
520 {
521 auto& mem_algo = m_pool->core()->get_segment_manager()->get_memory_algorithm();
522 const auto qta = mem_algo.stat_quanta_active();
523 const auto ret = m_pool->core()->allocate(sz);
524 mem_algo.stats_record(qta, m_stats); // To restate some of aforementioned doc header: almost always no-op.
525 return ret;
526 }
527 // else
528 return m_pool->core()->allocate(sz);
529}
530
531void Lend_tracker_pool::update_log_context(const flow::log::Log_context_mt* log_ctx,
532 const std::atomic<bool>* skip_fast_path_verbose_logging_ptr)
533{
534 m_log_ctx = log_ctx;
535 m_skip_fast_path_verbose_logging_ptr = skip_fast_path_verbose_logging_ptr;
536}
537
538bool Lend_tracker_pool::skip_fast_path_verbose_logging() const
539{
540 return m_skip_fast_path_verbose_logging_ptr->load(std::memory_order_relaxed);
541}
542
543std::ostream& operator<<(std::ostream& os, const Lend_tracker_pool& val)
544{
545 constexpr auto USE_CT_SZ = sizeof(Lend_tracker_pool::Atomic_use_ct);
546
547 return os << (val.m_is_creator ? "adm" : "cli")
548 << "[sh_name[" << val.m_pool_name << "] unused-cts[" << val.n_unused() << "] use-cts-used/free"
549 "[" << (((Use_count_registry::S_USE_COUNTS_CAPACITY * USE_CT_SZ) - val.m_pool->core()->get_free_memory())
550 / USE_CT_SZ)
551 << '/'
552 << (val.m_pool->core()->get_free_memory() / USE_CT_SZ) << "]]@" << &val;
553}
554
555} // 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 ...
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...
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...
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().
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).
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 base_offset()
The number of bytes between the start of the SHM-pool (segment) storing us and us (*this).
static void remove_persistent(flow::log::Logger *logger_ptr, const Shared_name &name, Error_code *err_code=nullptr)
Removes the named SHM pool object.
Definition: pool_arena.cpp:411
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) ...
@ S_SHM_BIPC_MISC_LIBRARY_ERROR
Low-level boost.ipc.shm: boost.interprocess emitted miscellaneous library exception sans a system cod...
std::ostream & operator<<(std::ostream &os, const Msg_batch_in< Mutable_buffer_sequence_t, Msg_resource_t > &val)
Serializes a Msg_batch_in to a standard output stream.
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
const Open_only OPEN_ONLY
Tag value indicating an atomic open-if-exists-else-fail operation.
Definition: util.cpp:37
bipc::create_only_t Create_only
Tag type indicating a create-unless-exists-else-fail operation.
Definition: util_fwd.hpp:155
void op_with_possible_bipc_exception(flow::log::Logger *logger_ptr, Error_code *err_code, const Error_code &misc_bipc_lib_error, String_view context, const Func &func)
Internal (to ipc) utility that invokes the given function that invokes a boost.interprocess operation...
Definition: util.hpp:35
const Create_only CREATE_ONLY
Tag value indicating an atomic create-unless-exists-else-fail operation.
Definition: util.cpp:38
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
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...