Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
shm_fwd.hpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <flow/util/stat/stat_fwd.hpp>
23
24/**
25 * Modules for SHared Memory (SHM) support. At a high level ipc::shm is a collection of sub-modules, each
26 * known as a *SHM-provider*, as of this writing most prominently ipc::shm::classic and ipc::shm::arena_lend::jemalloc;
27 * plus provider-agnostic support for SHM-stored native-C++ STL-compliant data structures. See the doc headers
28 * for each sub-namespace for further information.
29 *
30 * @see The guided Manual chapter @ref transport_shm covers these topics, including the choice of SHM-provider.
31 *
32 * That said here's an overview.
33 *
34 * ### SHM-providers (ipc::shm::classic, ipc::shm::arena_lend) ###
35 * Generally speaking there are two approaches to making use of ipc::shm:
36 * -# directly; or
37 * -# via ipc::session.
38 *
39 * While there are applications for approach 1, approach 2 is best by default. It makes the setup
40 * of a SHM environment far, far easier -- it is essentially done for you, with many painful details such as
41 * naming and cleanup (whether after graceful exit or otherwise) taken care of without your input. Furthermore
42 * it standardizes APIs in such a way as to make it possible to swap between the available SHM-providers without
43 * changing code. (There are some exceptions to this; they are well explained in docs.)
44 *
45 * Regardless of approach, you will need to choose which SHM-provider to use for your application. I (ygoldfel)
46 * would informally recommend looking at it from the angle of approach 2, as the ipc::session paradigm might clarify
47 * the high-level differences between the SHM-providers. I will now describe and contrast the SHM-providers.
48 *
49 * As of this writing there are two known types of SHM-providers: *arena-sharing* and *arena-lending*, of which
50 * only the latter is formalized in terms of its general properties.
51 * - Arena-sharing: We have just one such SHM-provider available as of this writing: shm::classic (SHM-classic;
52 * boost.ipc-SHM; boost.interprocess-SHM). In brief: it's built around shm::classic::Pool_arena, a single-pool
53 * thin wrapper around an OS SHM object (pool) handle with a boost.ipc-supplied simple memory allocation algorithm.
54 * Both processes in an IPC conversation (session) share one SHM arena (in this case consisting of 1 pool); both
55 * can allocate in that shared arena and cross-deallocate. Internally auto-deallocation is handled via an atomic
56 * ref-count stored directly in the same SHM arena as the allocated object. Due to SHM-classic's intentional
57 * simplicity, the lend/borrow aspects of it (where side/process 1 of a session *lends* a SHM-allocated object to
58 * side/process 2 which *borrows* it, thus incrementing the conceptual cross-process ref-count to 2) are
59 * properties of the arena-object `Pool_arena` itself.
60 * - Arena-lending: We have one SHM-provider as of this writing: shm::arena_lend::jemalloc (SHM-jemalloc); it is an
61 * application of the formalized arena-lending-SHM-provider paradigm specifically to the commercial-grade
62 * 3rd party open-source `malloc()` provider (memory manager): [jemalloc](https://jemalloc.net). It could be
63 * applied to other memory managers; e.g., tcmalloc. (At the moment we feel jemalloc is the most
64 * advanced and customizable open-source `malloc()`er around.) Generally the memory-manager-agnostic aspects
65 * live in shm::arena_lend; while the SHM-jemalloc-specific ones go into shm::arena_lend::jemalloc.
66 * A major aspect of arena-lending SHM-providers is the separation of the arena from the lend/borrow
67 * engine (SHM-session). (Those aspects live in session::shm::arena_lend and session::shm::arena_lend::jemalloc;
68 * again, the memory-manager-agnostic and -non-agnostic aspects respectively.) With an arena-lending SHM-provider,
69 * *each* of the two processes in a session creates/maintains its own arena, in which the other side cannot
70 * allocate; then via the session object the other side *borrows* an allocated object which it can at least
71 * read (but not deallocate; and not write-to). Thus process 1 maintains a jemalloc-managed arena;
72 * process 2 borrows objects from it and reads them; and conversely process 2 maintains a jemalloc-managed arena;
73 * process 1 borrows objects from it and reads them. Hence there are 2 process-local *SHM-arenas* and 1
74 * *SHM-session* for bidirectional lending/borrowing.
75 *
76 * @note It says above, "(but not deallocate; and not write-to)." Regarding, specifically, not being able to write to
77 * (already allocated, by owner-side, buffers): it is a subtle situation. As of this writing, for SHM-jemalloc,
78 * this is in fact disallowed, in that internally borrower-side we open the SHM areas with read-only
79 * enforcement by the OS; writing a byte causes a fault. (As mentioned below this has potentially important
80 * safety benefits.) However, it would not be crazy to make this read-only policy optional; a version of
81 * SHM-jemalloc subsequent to this note may do so. At the cost of losing those safety benefits, which may
82 * not apply to some applications in practice, one would enable a range of algorithms and techniques
83 * (in-SHM mutexes for example). *That* said: it is important to remember that, while that degree of freedom
84 * (allow versus disallow writing) is essentially available for arena-lending SHM-providers, the same
85 * is *not* true of whether the borrower side can [de]allocate or not. By design of the arena-lending
86 * SHM-provider type, the borrower side cannot [de]allocate (in the borrowed arena). That limits some
87 * algorithmic possibilities. For example, supposing borrower-side writing ability were enabled: one could
88 * write to an element of a `vector v`, but `v.push_back()` would only work if it kept
89 * `v.size() <= v.capacity()`.
90 *
91 * shm::classic is deliberately minimalistic. As a result it is very fast around setup (which involves, simply,
92 * an OS SHM-open operation on each side) and around lend/borrow time (when a process wants to share a SHM-stored datum
93 * with another process). The negatives are:
94 * - It is not (and would be -- at best -- extremely difficult to become)
95 * integrated with a commercial-grade memory manager, with features such as anti-fragmentation and thread-caching;
96 * hence the allocation/deallocation of objects may be slower compared to heap-based over time. We rely on
97 * boost.ipc's algorithm which lacks the maturity of a jemalloc; and while a custom one could surely replace it,
98 * it would be challenging to improve-upon without bringing in a 3rd-party product; such products are not usually
99 * designed around being placed *entirely* into shared memory.
100 * - Two processes (at least) intensively write to the same memory area; this in the presence of
101 * crashing/zombifying/bugs -- where one process goes bad, while others are fine -- increases entropy and
102 * complicates recovery. If process X of multiple co-sharing processes goes down or is ill, the entirety
103 * of the SHM-stored data in this system is suspect and should probably be freed, all algorithms restarted.
104 *
105 * There are no particular plans to make shm::classic more sophisticated. It fulfills its purpose; and in fact it is
106 * suitable for many applications.
107 *
108 * In contrast shm::arena_lend is sophisticated. A process creates an *arena* (or arenas);
109 * one can allocate objects in arenas. A real memory manager is in charge of the mechanics of allocation; except
110 * when it would normally just `mmap()` a vaddr space for local heap use, it instead executes our internal hooks that
111 * `mmap()` to a SHM-pool; SHM-pools are created and destroyed as needed.
112 *
113 * The other process might do the same. It, thus, maintains its own memory manager, for allocations in SHM invoked
114 * from that process. Without further action, the two managers and the arenas they maintain are independent and only
115 * touched by their respective processes.
116 *
117 * To be useful for IPC one would share the objects between the processes. To be able to do
118 * so, during setup each process establishes a *SHM-session* to the other process (multiple
119 * sessions if talking to multiple processes). Then one registers each local arena with a
120 * SHM-session; this means objects from that arena can be sent to the process on the opposing side of the SHM-session.
121 * From that point on, any object constructed in a given arena can be *lent* (sent) to any process on the opposing
122 * side of a session with which that given arena has been registered. This negates the negatives of SHM-classic:
123 * - A commercial-grade memory manager efficiently manages the in-SHM heap. If you trust the performance of your
124 * regular `malloc()`/`new`/etc., then you can trust this equally.
125 * - Process 1 does not write to (or at least does not allocate in) a pool managed by process 2; and vice versa.
126 * Hence if process X goes down or is ill, the arenas created by the other processes in the system can continue
127 * safely.
128 *
129 * The negatives are a large increase in complexity and novelty; and the reduced flexibility w/r/t what
130 * algorithms/techniques one can use, since only the owning side can [de]allocate in, or write to, a given data
131 * structure. In terms of initial setup, it is -- if one does not use ipc::session to do it -- more demanding:
132 * Just to establish a SHM-session between processes A and B, one must provide a low-level A-B IPC channel for the
133 * SHM-session's internal use to synchronize pool collections.
134 *
135 * Lastly, as it stands, the arena-lending paradigm does lack one capability of SHM-classic; it is fairly
136 * advanced and may or may not come up as an actual problem:
137 *
138 * Imagine long-lived application A and relatively short-lived application B, with (say) serially appearing/ending
139 * processes B1, B2, B3 in chronological order. A can allocate and fill an object X1 while B1 is alive; it will
140 * persist even after B1 dies and through B2 and B3; B1 through B3 can all read it. But can B1 *itself* do so?
141 * - With shm::classic: Yes. It's all one arena shared by everyone, readable and writable and allocatable by all.
142 * - With shm::arena_lend: No. Anything B1 allocates, by definition, must disappear once B1 exits. The entire
143 * arena disappears by the time B2 appears. B2 can read anything that *A* allocated including before B2 was
144 * born, because A is alive as is the arena it maintains; but B1 -- no.
145 *
146 * In the ipc::session paradigm this type of data is known as *app-scope* in contrast to most data which are
147 * *session-scope*. For data relevant only to each conversation A-B1, A-B2, A-B3, there is no asymmetry: Internally
148 * there are 2 arenas in each of the 3 sessions, but conceptually it might as well be 1 common arena, since both
149 * sides have symmetrical capabilities (allocate, read/write, lend; borrow, read -- and, with SHM-classic, write; see
150 * @note above). So for session-scope data shm::classic and shm::arena_lend are equivalent for most purposes.
151 *
152 * ### STL support ###
153 * The other major sub-module, as mentioned, is agnostic to the specific SHM-provider. It allows one to store
154 * complex native C++ data directly in SHM: namely, arbitrary combinations of STL-compliant containers, `struct`s,
155 * fixed-length arrays, and scalars. (Even pointers can be stored; one only needs to use `Arena::Pointer<T>`
156 * in lieu of `T*`.) Both SHM-providers above (shm::classic and shm::arena_lend::jemalloc) provide the semantics
157 * required to correctly plug-in to this system. See doc header for namespace shm::stl to continue exploring
158 * this topic.
159 */
160namespace ipc::shm
161{
162
163// Types.
164
165/// Short-hand for util::Shared_name; used in particular for SHM pool names at least.
167
168// Find doc headers near the bodies of these compound types.
169
170template<typename Arena>
172
173} // namespace ipc::shm
174
175/// Stats-related sub-namespace, for ADL segregation and general organization.
177{
178
179// Types.
180
181// Find doc headers near the bodies of these compound types.
182
183struct Owner_obj_stats;
184struct Lender_obj_stats;
185struct Borrower_obj_stats;
186struct Live_obj_stats;
188
189// Free functions.
190
191/**
192 * Declares the stats for Owner_obj_stats. Not invoked directly except by `flow::util::stat` internals,
193 * or when composing this stat-set into another.
194 * @see `flow::util::stat` namespace doc header for background on the declare/visit mechanism.
195 *
196 * @tparam Visitor
197 * See above.
198 * @param name_prefix
199 * See above.
200 * @param src_stats
201 * See above.
202 * @param target_stats
203 * See above.
204 * @param visitor
205 * See above.
206 */
207template<typename Visitor>
208void declare_stats(const flow::util::stat::Stat_name& name_prefix,
209 const Owner_obj_stats* src_stats, Owner_obj_stats* target_stats,
210 Visitor&& visitor);
211
212/**
213 * Declares the stats for Lender_obj_stats. Not invoked directly except by `flow::util::stat` internals,
214 * or when composing this stat-set into another.
215 * @see `flow::util::stat` namespace doc header for background on the declare/visit mechanism.
216 *
217 * @tparam Visitor
218 * See above.
219 * @param name_prefix
220 * See above.
221 * @param src_stats
222 * See above.
223 * @param target_stats
224 * See above.
225 * @param visitor
226 * See above.
227 */
228template<typename Visitor>
229void declare_stats(const flow::util::stat::Stat_name& name_prefix,
230 const Lender_obj_stats* src_stats, Lender_obj_stats* target_stats,
231 Visitor&& visitor);
232
233/**
234 * Declares the stats for Borrower_obj_stats. Not invoked directly except by `flow::util::stat` internals,
235 * or when composing this stat-set into another.
236 * @see `flow::util::stat` namespace doc header for background on the declare/visit mechanism.
237 *
238 * @tparam Visitor
239 * See above.
240 * @param name_prefix
241 * See above.
242 * @param src_stats
243 * See above.
244 * @param target_stats
245 * See above.
246 * @param visitor
247 * See above.
248 */
249template<typename Visitor>
250void declare_stats(const flow::util::stat::Stat_name& name_prefix,
251 const Borrower_obj_stats* src_stats, Borrower_obj_stats* target_stats,
252 Visitor&& visitor);
253
254/**
255 * Declares the stats for Live_obj_stats. Not invoked directly except by `flow::util::stat` internals,
256 * or when composing this stat-set into another.
257 * @see `flow::util::stat` namespace doc header for background on the declare/visit mechanism.
258 *
259 * @tparam Visitor
260 * See above.
261 * @param name_prefix
262 * See above.
263 * @param src_stats
264 * See above.
265 * @param target_stats
266 * See above.
267 * @param visitor
268 * See above.
269 */
270template<typename Visitor>
271void declare_stats(const flow::util::stat::Stat_name& name_prefix,
272 const Live_obj_stats* src_stats, Live_obj_stats* target_stats,
273 Visitor&& visitor);
274
275/**
276 * Declares the stats for Shared_arena_obj_stats. Not invoked directly except by `flow::util::stat` internals,
277 * or when composing this stat-set into another.
278 * @see `flow::util::stat` namespace doc header for background on the declare/visit mechanism.
279 *
280 * @tparam Visitor
281 * See above.
282 * @param name_prefix
283 * See above.
284 * @param src_stats
285 * See above.
286 * @param target_stats
287 * See above.
288 * @param visitor
289 * See above.
290 */
291template<typename Visitor>
292void declare_stats(const flow::util::stat::Stat_name& name_prefix,
293 const Shared_arena_obj_stats* src_stats, Shared_arena_obj_stats* target_stats,
294 Visitor&& visitor);
295
296} // namespace ipc::shm::stat
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
Stats-related sub-namespace, for ADL segregation and general organization.
Definition: shm_fwd.hpp:177
void declare_stats(const flow::util::stat::Stat_name &name_prefix, const Owner_obj_stats *src_stats, Owner_obj_stats *target_stats, Visitor &&visitor)
Declares the stats for Owner_obj_stats.
Definition: shm_stats.hpp:501
Modules for SHared Memory (SHM) support.
util::Shared_name Shared_name
Short-hand for util::Shared_name; used in particular for SHM pool names at least.
Definition: shm_fwd.hpp:166
Implementation of Arena_to_borrower_allocator_arena_t.
Definition: shm.hpp:36
Flow-IPC SHM-module: SHM-provider-agnostic stats about borrowing objects, typically across a process ...
Definition: shm_stats.hpp:335
Flow-IPC SHM-module: SHM-provider-agnostic stats about an arena if viewed as being capable or lending...
Definition: shm_stats.hpp:257
Stats struct thematically relevant to gauging outstanding (not-yet-garbage-collected) first-class obj...
Definition: shm_stats.hpp:384
Flow-IPC SHM-module: SHM-provider-agnostic stats about an arena object if viewed as solely a memory-m...
Definition: shm_stats.hpp:120
Stats struct tracking quantities applicable only to the arena-sharing SHM-provider type (at least SHM...
Definition: shm_stats.hpp:433