Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
capnp_msg_builder.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
26#include "ipc/shm/shm.hpp"
30#include <flow/error/error.hpp>
31#include <boost/container/list.hpp>
32#include <cstdint>
33
35{
36
37// Types.
38
39/**
40 * A `capnp::MessageBuilder` used by shm::Builder and for general capnp users: similar to a `MallocMessageBuilder`
41 * with the `GROW_HEURISTICALLY` alloc-strategy but allocating via a SHM-provider (of template-arg-specific
42 * type) in SHM instead of the heap via `malloc()`.
43 *
44 * It can be used as a #Capnp_msg_builder_interface (`capnp::MessageBuilder`) independently of the rest of
45 * ipc::transport::struc or even ::ipc (excepting the SHM-provider supplied as template arg `Shm_arena`).
46 * For example our optional capnp-RPC integration in sub-namespace shm::rpc uses shm::Capnp_message_builder
47 * and Capnp_message_reader in key ways.
48 *
49 * @see also Capnp_message_reader.
50 *
51 * Contrast this with Heap_fixed_builder_capnp_message_builder which allocates in regular heap.
52 * The `*this`-user-facing output API -- meaning the thing invoked by struc::Builder::emit_serialization() --
53 * is lend(). Cf. Heap_fixed_builder_capnp_message_builder::emit_segment_blobs().
54 * Why are they so different? Answer:
55 * - The latter is meant to emit M segments, each (some) bytes long, to all be transmitted directly over IPC.
56 * So it outputs them, to copy into the IPC transport!
57 * - We are meant to emit a *handle to a data structure storing those M segments* to be transmitted directly over
58 * IPC. The handle is transmitted; not the entire segments. So it outputs that handle! It just so happens
59 * to output it via a capnp mutator call. (It could instead emit a `flow::util::Blob` and let the caller
60 * transmit it however it wants. Why bother though? Just do it. However do see a related to-do in
61 * lend() doc header.)
62 *
63 * ### Move-ctible and move-assignable ###
64 * Please see similar section in Heap_fixed_builder_capnp_message_builder doc header; it applies
65 * very similarly to us. Spoiler alert: the super-class is not movable (hence neither are we); wrap `*this`
66 * in a `unique_ptr` if it must be relocatable (shm::Builder does exactly that).
67 *
68 * @tparam Shm_arena
69 * See shm::Builder doc header, same spot.
70 */
71template<typename Shm_arena>
74 public flow::log::Log_context
75{
76public:
77 // Types.
78
79 /// Short-hand for, you know.
80 using Arena = Shm_arena;
81
82 /// Short-hand for the SHM-aware allocator used in our central data structure holding the capnp serialization.
83 template<typename T>
85
86 /**
87 * The inner data structure stored in SHM representing one capnp-requested segment storing all or part of
88 * the serialization. `.capacity()` is how much was allocated which is at least what capnp-requested via
89 * allocateSegment() `virtual` API we implement. `.size()` is how many bytes of that were in fact ultimately
90 * used by capnp during the *last* serialization as capped by lend(). If `*this` is
91 * reused, then capnp may write past `.size()` (but not past `.capacity()`); lend()
92 * will then re-correct `.size()` to the true segment size used by capnp as reported by
93 * `this->getSegmentsForOutput()`. Probably not needed (publicly) if one uses a Capnp_message_builder.
94 *
95 * ### Choice of container type ###
96 * In the past this was, first, `std::vector<uint8_t>` (which needed `Default_init_allocator` to avoid
97 * 0-filling during `.resize()` -- see lend()); then `boost::container::vector<uint8_t>` (which needed
98 * `.resize(n, default_init_t)` extension for the same reason ). Then, as intended originally, it became
99 * `flow::util::Basic_blob<>`. Why that over `vector<uint8_t>`? Answer: `Basic_blob`'s express purpose
100 * is to do just this; some of its main documented aspects (lack of zero-init, iron-clad known perf) are
101 * directly counted-upon by us. So we use it for similar reasons as using `flow::util::Blob` all over the
102 * code for such purposes -- maybe even more so.
103 *
104 * So really the only thing missing, before we could use it, was its SHM-friendly/custom-allocator support.
105 * `Blob` cannot do it. Once the latter was generalized to `Basic_blob<Allocator>` we could switch to it,
106 * leaving behind a number of rather annoying caveats of the various `vector<uint8_t>` impls
107 * (0-init especially on `.resize()`, slow destructor on large blobs, and more).
108 *
109 * For reasons stated in its doc header `Basic_blob` does not log in normal fashion (memorizing a `Logger*`
110 * via ctor) but only if supplied an optional `Logger*` in each particular call. (`Blob` is a sub-class
111 * that adds such functionality at the expense of a bit of RAM/perf, but this is impossible with a custom SHM
112 * allocator.) So that's why `get_logger()` is passed to the few APIs we call on our `Basic_blob`.
113 */
114 using Segment_in_shm = flow::util::Basic_blob<Allocator<uint8_t>>;
115
116 /**
117 * The outer data structure stored in SHM representing the entire list of capnp-requested segments #Segment_in_shm.
118 * Probably not needed (publicly) if one uses a Capnp_message_builder.
119 *
120 * ### Rationale (`boost::container::` vs `std::`) ###
121 * Why `boost::container::list` and not `std::list`? Answer:
122 * `std::list`, at least in gcc-8.3.0, gave a compile error fairly clearly implying `std::list` stores
123 * `Node*` instead of `Allocator<Node>::pointer`; in other words it is not compatible with SHM
124 * (which bipc docs did warn people about -- but that could easily have been outdated).
125 *
126 * Curiously `std::vector` did not have that problem and worked fine, as far as that went, but we prefer
127 * a linked-list here.
128 */
129 using Segments_in_shm = boost::container::list<Segment_in_shm, Allocator<Segment_in_shm>>;
130
131 // Constructors/destructor.
132
133 /**
134 * Constructs the message-builder, memorizing the SHM engine it shall use to construct/allocate data internally
135 * on-demand via allocateSegment() (capnp-invoked from capnp-generated mutator API as invoked by the user).
136 *
137 * @param logger_ptr
138 * Logger to use for logging subsequently.
139 * @param arena
140 * See shm::Builder ctor.
141 * @param segment_sz_init_pre_growth
142 * This is chosen as the min size of the segment for the first allocateSegment().
143 * If it can be estimated, reasonably tightly, to be large enough for the entire message, then
144 * it can lead to significant perf wins: fewer `calloc()/memset()` cycles spent, and the entire message
145 * being in 1 segment.
146 * @param stats
147 * Optional pointer to the cumulative stats to update. If not null, pointee must outlive `*this`.
148 */
149 explicit Capnp_message_builder(flow::log::Logger* logger_ptr, Arena* arena,
150 size_t segment_sz_init_pre_growth
151 = sizeof(::capnp::word) * ::capnp::SUGGESTED_FIRST_SEGMENT_WORDS,
152 struc::stat::Serializer_stats::Snd* stats = nullptr);
153
154 /// Decrements owner-process count by 1; if current count is 1 deallocates SHM-stored data.
156
157 // Methods.
158
159 /**
160 * To be called after being done mutating underlying structured data, increments owner-process count
161 * by 1 via `shm_session->lend_object()`; and populates a capnp-`struct` field, saving the encoding of the
162 * outer SHM handle to the serialization-segment data structure #Segments_in_shm into that field.
163 *
164 * You may call this method more than once per `*this`. In particular this is necessary if sending the SHM-handle
165 * via IPC more than once -- even if one has already sent it to that same process (or another).
166 * Even if the bits populated into `*capnp_root` shall always be the same for a given `*this`, it is
167 * nevertheless required to call it repeatedly when sharing repeatedly.
168 *
169 * @todo Would be nice to provide a more-general counterpart to existing
170 * Capnp_message_builder::lend() (in addition to that one which outputs into a capnp structure),
171 * such as one that outputs a mere `Blob`. The existing one is suitable for the main use-case which is internally by
172 * shm::Builder; but Capnp_message_builder is also usable as a `capnp::MessageBuilder` directly. If a user were to
173 * indeed leverage it in that latter capacity, they may want to transmit/store the SHM-handle some other way.
174 *
175 * @internal
176 *
177 * For context: As of this writing Flow-IPC internals use these types for `Capnp_root_builder`:
178 * - `schema::detail::ShmTopSerialization::Builder` (used by shm::Builder),
179 * - `schema::detail::CapnpRpcMsgTopSerialization::Builder` (used by rpc::Session_vat_network).
180 *
181 * @endinternal
182 *
183 * @tparam Session_t
184 * Let `S1 = Session_t` and `S2` be the `Session_t` in the opposing Capnp_message_reader::borrow() method.
185 * Then `S1::lend_object()`, with signature and semantics matching (e.g.) shm::Pool_arena::lend_object()
186 * must exist; `S2::borrow_object()`, with sig/semantics matching (e.g.) shm::Pool_arena::borrow_object()
187 * must exist; and those two methods -- and the actual args `shm_session` to our `lend()` and
188 * to opposing Capnp_message_reader::borrow() -- must interoperate. In particular here are `S1 + S2`
189 * pairs that work (this may not be exhaustive). Caution: Mixing `S1` from one pair with `S2` from another
190 * is undefined behavior. (In practice it will work in some cases and will reliably fail in others, even
191 * when the implied SHM-provider is the same; e.g. session::shm::classic::Client_session + shm::Pool_arena.)
192 * Here are the aforementioned pairs available out of the box:
193 * session::shm::classic::Client_session + session::shm::classic::Server_session;
194 * session::shm::arena_lend::jemalloc::Client_session + session::shm::arena_lend::jemalloc::Server_session;
195 * shm::Pool_arena + ditto;
196 * session::shm::arena_lend::jemalloc::Shm_session + ditto.
197 * @tparam Capnp_root_builder
198 * See capnp_set_lent_shm_handle(), to which this is forwarded: a capnp-generated `X::Builder`, where
199 * `struct X` declares field `shmHandleSerialization :Data`.
200 * @param capnp_root
201 * The target SHM-handle serialization root to populate as noted above. Untouched if `false` returned.
202 * @param shm_session
203 * The `.borrow_object()` provider; see `Session_t` doc above.
204 * @return `true` on success; `false` if and only if `shm_session->lend_object()` failed (returned empty blob).
205 * Assuming general buglessness of the code up to this point the latter means the session is permanently
206 * down; which is eminently possible in a normally functioning system.
207 */
208 template<typename Session_t, typename Capnp_root_builder>
209 bool lend(Capnp_root_builder* capnp_root, Session_t* shm_session);
210
211 /**
212 * Implements `MessageBuilder` API. Invoked by capnp, as the user mutates via `Builder`s. Do not invoke directly.
213 *
214 * Throws a `bad_alloc`-like exception if and only if the #Arena does so when allocating on behalf of the
215 * STL-compliant inner code of #Segments_in_shm.
216 *
217 * @note The strange capitalization (that goes against standard Flow-IPC style) is because we are implementing
218 * a capnp API.
219 *
220 * @param min_sz_words
221 * See `MessageBuilder` API.
222 * The allocated segment will allow for a serialization of at *least* `min_sz_words * sizeof(word)` bytes.
223 * The actual amount grows progressively similarly to the `MallocMessageBuilder` GROW_HEURISTICALLY
224 * strategy, starting at the same recommended first-segment size as `MallocMessageBuilder` as well.
225 * @return See `MessageBuilder` API.
226 * The ptr and size of the area for capnp to serialize-to.
227 */
228 kj::ArrayPtr<::capnp::word> allocateSegment(unsigned int min_sz_words) override;
229
230private:
231 // Types.
232
233 /// Short-hand for the SHM-arena activator coupled with #Allocator.
235
236 // Data.
237
238 /// See ctor.
240
241 /**
242 * Minimum size of the next segment allocated by allocateSegment(); a word-multiple.
243 * The actual size will be the higher of `min_sz` or this. Its initial value (seg 1's) comes from ctor arg.
244 * Its subsequent value is the sum of sizes of the previous segments; meaning itself plus whatever allocateSegment()
245 * decided to allocate. This results in exponential growth... ish.
246 *
247 * This follows `MallocMessageBuilder` GROW_HEURISTICALLY logic, straight-up lifted from their source code.
248 */
250
251 /// Outer SHM handle to the data structure in SHM that stores the capnp-requested serialization segments.
252 typename Arena::template Handle<Segments_in_shm> m_serialization_segments;
253
254 /// Cumulative stats target, or null if no stats recording is configured.
256
257 /**
258 * Running tally: sum of `seg_sz` chosen for each allocateSegment() call. Sampled
259 * into #m_stats at dtor; also used to decrement the alloc-bytes-outstanding gauge.
260 * It is only used for stats, so it's untouched if null #m_stats.
261 */
262 uint64_t m_alloc_sz;
263
264 /**
265 * Gate: `false` until the first big-leaf event happens; then flipped to `true`, and `m_msgs_with_big_leaf` stat
266 * goes up. It is only used for stats, so it's untouched if null #m_stats.
267 */
269}; // class Capnp_message_builder
270
271/**
272 * A `capnp::MessageReader` used by shm::Reader and for general capnp users: able to access capnp-serialized
273 * structure previously created by Capnp_message_builder (a `MessageBuilder` impl).
274 *
275 * It can be used as a #Capnp_msg_reader_interface (`capnp::MessageReader`) independently of the rest of
276 * ipc::transport::struc or even ::ipc (excepting the SHM-provider supplied as template arg `Shm_arena`).
277 *
278 * @tparam Shm_arena
279 * See Capnp_message_reader.
280 */
281template<typename Shm_arena>
284 public flow::log::Log_context,
285 private boost::noncopyable
286{
287public:
288 // Types.
289
290 /// Short-hand for, you know.
291 using Arena = Shm_arena;
292
293 /**
294 * For easier outside generic programming, this is the read-only-borrower counterpart to
295 * Capnp_message_builder::Allocator. See also #Segments_in_shm_borrowed.
296 * Might be useful if one does not use a Capnp_message_reader and/or must reimplement (parts of) it for some reason.
297 *
298 * @internal
299 * @todo Use `rebind` in the impl of Capnp_message_reader::Borrower_allocator.
300 */
301 template<typename T>
304
305 /**
306 * For easier outside generic programming, this is the read-only-borrower counterpart to
307 * Capnp_message_builder::Segment_in_shm. See also #Segments_in_shm_borrowed.
308 * Might be useful if one does not use a Capnp_message_reader and/or must reimplement (parts of) it for some reason.
309 *
310 * @internal
311 * @todo Use `rebind` in the impl of Capnp_message_reader::Segment_in_shm_borrowed.
312 */
313 using Segment_in_shm_borrowed = flow::util::Basic_blob<Borrower_allocator<uint8_t>>;
314
315 /**
316 * For easier outside generic programming, this is the read-only-borrower counterpart to
317 * Capnp_message_builder::Segments_in_shm: identical but using #Borrower_allocator instead of
318 * Capnp_message_builder::Allocator.
319 * This type shall be used with `borrow_object()` on the deserializing side when decoding
320 * the `Segments_in_shm` written by a Capnp_message_builder.
321 * Might be useful if one does not use a Capnp_message_reader and/or must reimplement (parts of) it for some reason.
322 *
323 * @internal
324 * @todo Use `rebind` in the impl of Capnp_message_reader::Segments_in_shm_borrowed.
325 */
327 = boost::container::list<Segment_in_shm_borrowed, Borrower_allocator<Segment_in_shm_borrowed>>;
328
329 // Constructors/destructor.
330
331 /**
332 * Boring constructor.
333 *
334 * @param logger_ptr
335 * Logger to use for logging subsequently.
336 */
337 explicit Capnp_message_reader(flow::log::Logger* logger_ptr);
338
339 /**
340 * Any SHM-stored structure currently held as a result of the last successful borrow() is no longer held. If no other
341 * Capnp_message_builder or Capnp_message_reader (cross-process) holds it, the SHM-stored data are deallocated.
342 */
344
345 // Methods.
346
347 /**
348 * Upon dropping structure loaded by the preceding borrow() call (if any), loads the SHM-stored structure
349 * by interpreting the SHM-handle-encoding provided by the caller. Assuming success one can then use
350 * the usual techniques of our inherited #Capnp_msg_reader_interface (a/k/a `capnp::MessageReader`) -- most
351 * notably `.getRoot<...>()` -- to access the SHM-stored data.
352 *
353 * @tparam Session_t
354 * See Capnp_message_builder::lend() doc header's note for same-named template parameter.
355 * @tparam Capnp_root_reader
356 * See capnp_get_shm_handle_to_borrow(), to which this is forwarded: the `X::Reader` counterpart of the
357 * `Capnp_root_builder` given to the opposing Capnp_message_builder::lend().
358 * @param capnp_root
359 * The SHM-handle serialization root to interpret.
360 * So if Capnp_message_builder::lend() used the `Builder` to set it, this is the `Reader` counterpart to
361 * interpret it on the receiving side.
362 * @param shm_session
363 * The `.borrow_object()` provider; see `Session_t` doc above.
364 * @param err_code
365 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
366 * struc::error::Code::S_DESERIALIZE_FAILED_INSUFFICIENT_SEGMENTS (add_serialization_segment() never called;
367 * or somehow opposing builder serialized an empty segment list -- this would be a bug on their part),
368 * struc::error::Code::S_DESERIALIZE_FAILED_SEGMENT_MISALIGNED (add_serialization_segment()-returned segment
369 * was modified subsequently to start at a misaligned address; or somehow the opposing
370 * builder supplied a segment that starts at a misaligned address or has a non-word-multiple size --
371 * this would be a bug on their part),
372 * error::Code::S_DESERIALIZE_FAILED_SESSION_HOSED (the SHM-session was unable to determine the location
373 * of the serialization in SHM, because its `borrow_object()` method indicated the session is down, or the
374 * information transmitted over IPC was in some way invalid).
375 */
376 template<typename Session_t, typename Capnp_root_reader>
377 void borrow(const Capnp_root_reader& capnp_root, Session_t* shm_session, Error_code* err_code = 0);
378
379 /**
380 * Return `false` if and only if borrow() has been invoked at least once, and the last time it was successful.
381 * In other words: whether the `MessageReader` is usable at this time.
382 * @return `false` if `*this` is a usable `MessageReader` at this time; `true` if not.
383 */
384 bool empty() const;
385
386 /**
387 * Implements `MessageReader` API. Invoked by capnp, as the user accesses `*this` via `Reader`s.
388 *
389 * @note The strange capitalization (that goes against standard Flow-IPC style) is because we are implementing
390 * a capnp API.
391 *
392 * @param id
393 * See `MessageReader` API.
394 * @return See `MessageReader` API.
395 */
396 kj::ArrayPtr<const ::capnp::word> getSegment(unsigned int id) override;
397
398private:
399 // Types.
400
401 /// Similar to Heap_reader::Capnp_word_array_ptr.
402 using Capnp_word_array_ptr = kj::ArrayPtr<const ::capnp::word>;
403
404 // Data.
405
406 /**
407 * The outer SHM handle to the #Segments_in_shm_borrowed containing the serialization yielded by e.g.
408 * `this->getRoot<...>()`. Null until the first successful borrow(),
409 * where it's assigned from `shm_session->borrow_object<T>()`.
410 * In plain(er?) English this a list of blobs, each of which is a capnp segment -- and #m_btm_capnp_segments is a
411 * view into those.
412 */
413 typename Arena::template Handle<Segments_in_shm_borrowed> m_btm_serialization_shm_handle;
414
415 /**
416 * Analogous to Heap_reader::m_capnp_segments; just points into SHM.
417 * It's a view into #m_btm_serialization_shm_handle; or empty if that is null.
418 */
419 std::vector<Capnp_word_array_ptr> m_btm_capnp_segments;
420}; // class Capnp_message_reader
421
422// Free functions: in *_fwd.hpp.
423
424// Template implementations.
425
426template<typename Shm_arena>
428 (flow::log::Logger* logger_ptr, Arena* arena, size_t segment_sz_init_pre_growth,
430
431 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
432 m_arena(arena),
433 m_segment_sz(flow::util::round_to_multiple(segment_sz_init_pre_growth, sizeof(::capnp::word))),
434 // Construct the data structure holding the segments, saving a small shared_ptr handle into SHM.
435 m_serialization_segments(m_arena->template construct<Segments_in_shm>()), // Can throw.
436 m_stats(stats),
437 m_alloc_sz(0),
438 m_saw_big_leaf(false)
439{
440 FLOW_LOG_TRACE("SHM builder [" << *this << "]: Created; input segment-size-pre-growth = "
441 "[" << segment_sz_init_pre_growth << "] bytes => rounded next-size = [" << m_segment_sz << "] bytes.");
442}
443
444template<typename Shm_arena>
446{
447 using flow::util::stat::fetch_sub;
448
449 FLOW_LOG_TRACE("SHM builder [" << *this << "]: Destroyed. The following may SHM-dealloc the serialization, "
450 "if recipient was done with it before us, or if we hadn't done lend() yet.");
451
452 /* Stats: end-of-life sampling. No-op if no stats configured, or if `*this` was never used to
453 * actually allocate (e.g., a Msg_out was constructed but never serialized).
454 *
455 * @todo This code is, at least at a glance, almost identical to Heap_fixed_builder_capnp_message_builder's dtor.
456 * allocateSegment() is similar but not the same; but the below is 95%+ the same. Should find a way
457 * to factor this out (without sacrificing perf). Incidentally: note we log above -- and it's a useful log when
458 * tracking details of the lifetime of an actual payload -- but the other guy does not. So if there's a refactor,
459 * this detail would figure into it somehow.
460 *
461 * For the moment keeping comments light; see comments in-line in the other guy. */
462 if ((!m_stats) || m_serialization_segments->empty())
463 {
464 return;
465 }
466 // else
467
468#ifndef NDEBUG
469 const auto prev_alloc_outstanding_sz =
470#endif
471 fetch_sub(&m_stats->m_alloc_outstanding_sz, m_alloc_sz);
472 assert((prev_alloc_outstanding_sz >= m_alloc_sz)
473 && "GAUGE underflow: dtor's per-msg subtraction exceeds the cumulative outstanding.");
474
475#ifndef NDEBUG
476 const auto prev_msgs_outstanding =
477#endif
478 fetch_sub(&m_stats->m_msgs_outstanding, 1);
479 assert((prev_msgs_outstanding >= 1)
480 && "GAUGE underflow: dtor decrement on already-zero msgs-outstanding.");
481
482 m_stats->m_histo_msg_alloc_sz.record_value(m_alloc_sz);
483
484 uint64_t used_bytes = 0;
485 for (const auto& capnp_seg : getSegmentsForOutput())
486 {
487 used_bytes += capnp_seg.asBytes().size();
488 }
489 m_stats->m_histo_msg_used_sz.record_value(used_bytes);
490
491 m_stats->m_histo_segs_per_msg.record_value(m_serialization_segments->size());
492
493 // m_serialization_segments Handle<> (shared_ptr<>) ref-count will decrement here (possibly to 0).
494} // Capnp_message_builder::~Capnp_message_builder()
495
496template<typename Shm_arena>
497template<typename Session_t, typename Capnp_root_builder>
498bool Capnp_message_builder<Shm_arena>::lend(Capnp_root_builder* capnp_root, Session_t* shm_session)
499{
500 using util::Blob_const;
501 using flow::util::buffers_dump_string;
502
503 assert(capnp_root);
504 assert(shm_session);
505
506 /* Firstly read the paragraph about this method versus
507 * Heap_fixed_builder_capnp_message_builder::emit_segment_blobs() (in our class doc header).
508 * That sets up some mental context. Then come back here.
509 * Spiritually we're doing something similar here: they've got a list-of-Blobs; we've got the same;
510 * we need to adjust the latters' `.size()`s down from `capacity()` to actual space used in serialization.
511 * The differences are:
512 * - They're stored in SHM via Stateless_allocator; need to ensure thread-local active arena is m_arena.
513 * - To emit, we just emit the outer SHM handle to the whole list-o'-blobs (they emit the actual list, to be
514 * copied).
515 *
516 * Well... let's go then. */
517
518 {
519 /* As noted: activate the arena, in case the below .resize() causes allocation. (It shouldn't... we're
520 * resizing down. Better safe than sorry, plus it's more maintainable. (What if it becomes a deque<> later
521 * or something?)) */
522 Arena_activator arena_ctx{m_arena};
523
524 // All of the below is much like Heap_fixed_builder_capnp_message_builder::emit_segment_blobs() except as noted.
525
526 Segments_in_shm& blobs = *m_serialization_segments;
527 assert((!blobs.empty())
528 && "Should not be possible for serialization to be empty with our use cases. Investigate.");
529
530 const auto capnp_segs = getSegmentsForOutput();
531 assert((capnp_segs.size() == blobs.size())
532 && "Somehow our MessageBuilder created fewer or more segments than allocateSegment() was called?!");
533
534 size_t idx;
535 typename Segments_in_shm::iterator blob_it;
536 for (idx = 0, blob_it = blobs.begin(); idx != capnp_segs.size(); ++idx, ++blob_it)
537 {
538 const auto capnp_seg = capnp_segs[idx].asBytes();
539 const auto seg_sz = capnp_seg.size();
540
541 auto& blob = *blob_it;
542
543 assert((capnp_seg.begin() == &(blob.front()))
544 && "Somehow capnp-returned segments are out of order to allocateSegment() calls; or something....");
545 assert((seg_sz != 0)
546 && "capnp shouldn't be generating zero-sized segments.");
547 assert((seg_sz <= blob.capacity())
548 && "capnp somehow overflowed the area we gave it.");
549
550 /* This .resize() call is interesting (and was quite treacherous when Segment_in_shm was a vector<uint8_t>).
551 * A regular .resize(n) is uncontroversial when .size() exceeds or equals n.
552 * It just adjusts an internal m_size thing. Suppose `n <= capacity()` (always the case for us and ensured
553 * above). Suppose now though that `.size() < n`. It works fine in Blob: we wrote past .size() but not
554 * past .capacity(), and the .resize() "corrects" m_size accordingly. With vector<uint8_t>, without taking
555 * special measures (std::vector<Default_init_allocator<...>> or
556 * boost::container::vector<>::resize(n, default_init)) it would also catastrophically (for us) zero-fill the
557 * bytes between size() and n: If lend() is being called on a *this that has already been lend()ed -- the case
558 * in particular where an out-message is serialized, sent, modified (to require more space in an existing
559 * segment), serialized again, sent again. Then this .resize() would zero out the added new bytes in the
560 * serialization!
561 * Uncarefully-written user code might even .initX(n) (where x = List or Data, say) a field that
562 * was previously .initX(n)ed; capnp does not simply reuse the space but rather orphans the previous X
563 * and creates a new List/Data X in a later, new part in the same segment (if there's space left).
564 * Now the deserializing side will observe the X is all zeroes... WTF?!
565 *
566 * Anyway, I mention that for posterity/education and to point out the fact we might be writing past
567 * .size() temporarily, until the present method executes; and that's somewhat unusual (but legal).
568 * Segment_in_shm=Basic_blob does not have the zeroing problem. */
569 blob.resize(seg_sz,
570 flow::util::Blob::S_UNCHANGED, // Can be removed if next arg is removed.
571 get_logger()); // (TRACE-log if enabled.) Must be removed if Segment_in_shm becomes non-Blob.
572
573 FLOW_LOG_TRACE("SHM builder [" << *this << "]: "
574 "Serialization segment [" << idx << "] (0-based, of [" << capnp_segs.size() << "], 1-based): "
575 "SHM-arena buffer @[" << static_cast<const void*>(&(blob.front())) << "] "
576 "sized [" << seg_sz << "]: Serialization of segment complete.");
577 FLOW_LOG_DATA("Segment contents: "
578 "[\n" << buffers_dump_string(Blob_const{&(blob.front()), blob.size()}, " ") << "].");
579 } // for (idx in [0, size()))
580 } // Arena_activator arena_ctx{m_arena};
581
582 /* And now just record the process-agnostic serialization of the handle to the whole thing. Nice and small!
583 * The rest is inside `blobs` which is wholly in SHM and needs no encoding. */
584
585 // Source blob (bits encoding handle):
586 const auto handle_serialization_blob = shm_session->template lend_object<Segments_in_shm>(m_serialization_segments);
587
588 if (handle_serialization_blob.empty())
589 {
590 /* This can surely happen; perhaps we are the first to notice the session being down (or our user has ignored
591 * any earlier sign(s) such as channel/session error handler(s) firing). It is interesting and should be rare
592 * (not verbose), so a high-severity log message seems worthwhile (even if other similarly-themed messages
593 * might appear nearby). */
594 FLOW_LOG_WARNING("SHM builder [" << *this << "]: "
595 "After finalizing capnp-serialization in a SHM arena, SHM-session failed to register "
596 "attempt to lend a SHM-handle to this serialization to the opposing process. "
597 "The data structure cannot be transmitted to the opposing process. Assuming no bugs "
598 "up to this point, the session is down (usually means opposing process is down).");
599 return false;
600 }
601 // else
602
603 /* Target: the `Data` field inside the capnp struct. (The helper reuses the field's space if already
604 * init...()ed: re-lend() of the same *this is a normal thing.) Copy handle-encoding bits (only a few bytes,
605 * by Session contract) from source to target: */
606 capnp_set_lent_shm_handle(capnp_root, handle_serialization_blob);
607
608 /* Process-count in m_serialization_segments incremented ahead of transmission (this is logged), probably to 2
609 * (higher if lend() called more than 1x).
610 * Now underlying SHM-stored segments won't be dealloc-ed until the other side receives it and later indicates
611 * that process is done with them (if send succeeds) + *this is destroyed. */
612
613 return true;
614} // Capnp_message_builder::lend()
615
616template<typename Shm_arena>
617kj::ArrayPtr<::capnp::word>
618 Capnp_message_builder<Shm_arena>::allocateSegment(unsigned int min_sz_words) // Virtual.
619{
620 using flow::util::stat::fetch_add;
621 using flow::util::stat::update_hi_wmark;
622 using Word = ::capnp::word;
623 using Capnp_word_buf = kj::ArrayPtr<Word>;
624 using flow::util::ceil_div;
625 constexpr size_t WORD_SZ = sizeof(Word);
626 const size_t min_sz = size_t(min_sz_words) * WORD_SZ; // Don't forget: in their API min_sz is in `word`s.
627
628 /* Background from capnp: They're saying they need the allocated space for serialization to store at least min_sz:
629 * probably they're going to store some object that needs at least this much space. So typically it's some
630 * scalar leaf thing, like 4 bytes or whatever; but it could be larger -- or even huge (e.g., a Data or List
631 * of huge size, because the user mutated it so via a ::Builder). Oh, and it has to be zeroed, as by calloc().
632 *
633 * So all we *have* to allocate is min_sz exactly in that sense. But the idea is to try to allocate more, so that
634 * capnp can efficiently shove more objects in there too without calling allocateSegment() for each one.
635 * And we're supposed to grow exponentially each time, so we keep track of the next size in m_segment_sz, same
636 * as capnp::MallocMessageBuilder internally does (check its source code). Of course, if min_sz exceeds that,
637 * then we have no choice but to allocate the larger amount min_sz. */
638
639 const bool big_leaf = min_sz > m_segment_sz;
640 const size_t seg_sz = big_leaf ? min_sz : m_segment_sz; // m_segment_sz is maintained to be a word-multiple.
641
642 FLOW_LOG_TRACE("SHM builder [" << *this << "]: allocateSegment request for >=[" << min_sz_words << "] words; "
643 "SHM-allocing ~max(that x sizeof(word), next-size=[" << m_segment_sz << "]) = [" << seg_sz << "] "
644 "bytes.");
645
646 uint8_t* buf_ptr;
647 {
648 Arena_activator arena_ctx{m_arena};
649
650 // Go to it! This can throw (which as noted elsewhere is treated as a catastrophe a-la `new` bad_alloc for now).
651 buf_ptr = &(m_serialization_segments->emplace_back
652 (seg_sz,
653 flow::util::CLEAR_ON_ALLOC,
654 // (TRACE-log in this ctor if enabled.) Must be removed if Segment_in_shm becomes non-Blob.
655 get_logger()).front());
656
657 /* Attn! capnp requires: it must be zeroed. And Basic_blob(CLEAR_ON_ALLOC) ctor we used *does* zero it.
658 * So do not memset() it. This is, at worst, syntactic sugar; at best it can bring significant perf improvements
659 * (e.g., calloc(64Ki) we've seen be ~20% faster than malloc() + memset()).
660 *
661 * @todo That said: in our case (where the allocator is SHM-aware) Basic_blob will be (as of this writing;
662 * see Basic_blob::reserve_impl()) forced to internally separately allocate-then-memset-zeroes, meaning it's mere
663 * syntactic sugar in that case. There are as of this writing some notes there about how we might, for
664 * SHM-classic and SHM-jemalloc specifically even, be able to do better here: perhaps by somehow triggering
665 * <relevant allocator>::allocate() to in fact alloc-and-clear to the best of the SHM-provider's ability.
666 * So see those notes in Basic_blob::reserve_impl() and come back to Flow-IPC-land to see what we might do
667 * for a perf bump here.
668 *
669 * Caution! If you choose to change-over to vector<..., flow::util::Default_init_allocator<...>> instead, then
670 * you'll need to have `std::memset(buf_ptr, 0, seg_sz)` (or some equivalent) here. */
671 } // Arena_activator arena_ctx{m_arena};
672
673 // Since we are supposed to grow exponentially, increase this for next time (if any):
674 m_segment_sz += seg_sz; // Word-multiple + word-multiple = word-multiple.
675 /* @todo MallocMessageBuilder does some bounding according to some maximum. Probably we must do the same.
676 * Get back to this and follow capnp-interface reqs and/or follow what their internal logic does. */
677
678 FLOW_LOG_TRACE("SHM builder [" << *this << "]: Next-size grew exponentially to [" << m_segment_sz << "] "
679 "for next time.");
680
681 /* Stats: Mirror Heap_fixed_builder_capnp_message_builder (we do similar things as it does but in SHM
682 * instead of heap). Keeping comments light.
683 *
684 * Differences (we just don't touch those stats; they remain zero if only we are given that stats struct):
685 * - The segment growth is not constrained in our case.
686 * - We don't need to leave space for any frame-prefix. */
687 if (m_stats)
688 {
689 if (m_serialization_segments->size() == 1)
690 {
691 fetch_add(&m_stats->m_msgs, 1);
692 update_hi_wmark(&m_stats->m_msgs_outstanding_hi_wmark,
693 fetch_add(&m_stats->m_msgs_outstanding, 1) + 1);
694 }
695
696 fetch_add(&m_stats->m_alloc_lifetime_sz, seg_sz);
697 update_hi_wmark(&m_stats->m_alloc_outstanding_sz_hi_wmark,
698 fetch_add(&m_stats->m_alloc_outstanding_sz, seg_sz) + seg_sz);
699 m_alloc_sz += seg_sz;
700
701 if (big_leaf)
702 {
703 fetch_add(&m_stats->m_big_leaf_alloc_count, 1);
704 m_stats->m_histo_big_leaf_sz.record_value(min_sz);
705 if (!m_saw_big_leaf)
706 {
707 m_saw_big_leaf = true;
708 fetch_add(&m_stats->m_msgs_with_big_leaf, 1);
709 }
710 }
711 } // if (m_stats)
712
713 return Capnp_word_buf{reinterpret_cast<Word*>(buf_ptr),
714 reinterpret_cast<Word*>(buf_ptr + seg_sz)};
715} // Capnp_message_builder::allocateSegment()
716
717template<typename Shm_arena>
719 /* MessageReader isn't an abstract interface; it takes this ReaderOptions struct which defaults to certain values.
720 * In typical capnp use the user would do this themselves; but in our case we do it for them.
721 * @todo This should be part of our public API throughout (where relevant). Surely a ticket is filed. Until then:
722 * The defaults seem fine, except:
723 * The particular traversalLimitInWords option can cause trouble with large structures. (See capnp source message.h
724 * for its official docs.) It is a security feature for stuff transmitted over the wire; but as of *this* writing
725 * we do local IPC and explicitly assume trust. So until this is made configurable (and it really should be)
726 * it's an OK work-around to just shove a giant value here. Otherwise the mere act of reading a structure with
727 * many sub-structs (in absolute terms, not in terms of depth) can (and has, such as in our test suite's perf_demo)
728 * throw a capnp exception. */
729 Capnp_msg_reader_interface(::capnp::ReaderOptions{ std::numeric_limits<uint64_t>::max() / sizeof(::capnp::word),
730 ::capnp::ReaderOptions{}.nestingLimit }),
731 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT)
732{
733 FLOW_LOG_TRACE("SHM reader [" << *this << "]: Created.");
734}
735
736template<typename Shm_arena>
738{
739 FLOW_LOG_TRACE("SHM reader [" << *this << "]: Destroyed. The following may SHM-dealloc the serialization.");
740 // m_btm_serialization_shm_handle Handle<> (shared_ptr<>) ref-count will decrement here (possibly to 0).
741}
742
743template<typename Shm_arena>
744template<typename Session_t, typename Capnp_root_reader>
745void Capnp_message_reader<Shm_arena>::borrow(const Capnp_root_reader& capnp_root,
746 Session_t* shm_session, Error_code* err_code)
747{
748 using Blob = flow::util::Blob_sans_log_context;
749 using ::capnp::word;
750 using util::Blob_const;
751 using flow::util::buffers_dump_string;
752 using std::vector;
753
754 if (flow::error::exec_void_and_throw_on_error
755 ([&](Error_code* actual_err_code) { borrow(capnp_root, shm_session, actual_err_code); },
756 err_code, "Capnp_message_reader::borrow()"))
757 {
758 return;
759 }
760 // else
761
762 assert(shm_session);
763
764 /* This is just to satisfy the contract wherein if we fail, the existing serialization if any
765 * is dropped. Plus we can log. */
766 if (!empty())
767 {
768 FLOW_LOG_TRACE("SHM reader [" << *this << "]: About to deserialize; hence dropping existing structure. "
769 "The following may SHM-dealloc the serialization.");
770 m_btm_capnp_segments.clear();
771 m_btm_serialization_shm_handle.reset(); // Dealloc if any would happen here (ref-count decrement is here).
772 }
773
774 /* Now get the bottom serialization out of SHM. To do so, really we mirror what Heap_reader does --
775 * a-la SegmentArrayMessageReader -- but instead of getting it out of direct-serialized stuff from segments in
776 * regular heap, get it out of SHM based on the handle to list<Basic_blob>, where that handle is
777 * the one little thing stored in capnp_root. */
778 {
779 Blob handle_serialization_blob;
780 capnp_get_shm_handle_to_borrow(capnp_root, &handle_serialization_blob);
781
782 /* And, as documented in the .capnp file -- and can be seen in Builder -- the handle is
783 * to Segments_in_shm, which is the aforementioned list<Basic_blob>. So interpret it that way
784 * (but read-only: we will never modify it). */
785 m_btm_serialization_shm_handle
786 = shm_session->template borrow_object<Segments_in_shm_borrowed>(handle_serialization_blob);
787 }
788 if (!m_btm_serialization_shm_handle)
789 {
790 /* This can surely happen; perhaps we are the first to notice the session being down (or our user has ignored
791 * any earlier sign(s) such as channel/session error handler(s) firing). It is interesting and should be rare
792 * (not verbose), so a high-severity log message seems worthwhile (even if other similarly-themed messages
793 * might appear nearby). */
794 FLOW_LOG_WARNING("SHM reader [" << *this << "]: "
795 "After receiving SHM-handle to capnp-serialization in a SHM arena, SHM-session failed "
796 "to interpret that SHM-handle. "
797 "The data structure cannot be accepted from the opposing process. Assuming no bugs "
798 "up to this point, the session is down (usually means opposing process is down).");
800 return;
801 }
802 // else
803
804 /* Subtlety: We are about to work with serialization_segments, an STL-compliant structure stored directly in
805 * SHM. In so doing we'll have to traverse it with iterators and so on. Needn't we do some kind of
806 * Stateless_allocator/Arena_activator context-setting (e.g., as shm::Capnp_message_builder does when building
807 * up the thing we're now reading)? If so, we have a problem: what "arena" do we even activate? Fortunately
808 * (and it's not a fortunate happenstance thing but rather makes sense in terms of the generic asymmetric design
809 * that separates the write-side Arena and the -- in our case -- read Session) we in fact needn't. This bears
810 * explanation for context:
811 *
812 * Our access to this STL-compliant data structure is read-only. Our ref is formally to a const
813 * list-of-vectors-of-bytes (note: not a const list-of-POINTERS-to-vectors-of-bytes). Anything we do will *not*
814 * invoke any <allocator>.<do stuff -- allocate/deallocate()>() calls. It will certainly access
815 * <allocator>::pointer *type* which will invoke (crucially!) the proper fancy-pointer logic to dereference SHM-stored
816 * (not raw T*) pointers which allows following iterators among other things; but that ::pointer must be able
817 * to generate raw pointers (deref itself) without any context-setting help. So: we are fine.
818 *
819 * While the following question is not formally relevant to the present code, one might still wonder, so I'll
820 * comment on it: What about the destruction of this data structure down the line, like in our dtor when
821 * we let go of m_btm_serialization_shm_handle? If that is the last owner process to hold the cross-process
822 * handle to this in-SHM structure, won't "it" need to call its (list-of-vectors or w/e) dtor? And if so
823 * doesn't "something" need to set the allocator-context before the dtor is called? Well, firstly, when
824 * the Handle logic, in some process, decides it's time to call the dtor, it is in charge of setting the
825 * allocator-context. So somehow it must do the right thing. But for general education, how *does* it do the
826 * right thing? It's easiest to answer on the level of specific impls.
827 * - shm::classic::Pool_arena: This simple symmetric core class is both Arena and Session, and both sides
828 * (in our case the builder and the reader) open the same-named SHM pool in identical ways.
829 * In its case cross-process ref-count-0 is detected by either process; and whichever one it is
830 * simply sets itself (Arena::this) as the allocator-context before invoking dtor. So if it's us, the reader
831 * process, then it'll do that. This was ensured when *we* called Session::borrow_object(), which invisibly
832 * supplied the custom deleter which will in fact do what I just described. This Session::borrow_object(),
833 * really, what is it? It's shm::classic::Pool_arena::borrow_object(), because Session = Pool_arena.
834 * - SHM-jemalloc provider: This asymmetric setup involves, on the builder side, an Arena and a separate
835 * Session; and on the reader side a Session that is able to interpret the opposing Session's
836 * Session::lend_object() in its Session::borrow_object(). The reader-side Session::borrow_object() will set
837 * up a custom deleter, as it must, and that guy will (internally) IPC-inform the builder side Session that our
838 * local Handle ref-count=0. Hence the builder side is in charge of invoking the dtor, when the time comes,
839 * regardless of which side was last to local ref-count=0.
840 *
841 * Specifics aside, generically speaking: The custom deleter on the Handle is in charge of safely disposing
842 * of the structure when no other holder holds it any longer either -- and it can do it however it needs to.
843 * Part of its charge is to -- when it indeed invokes the STL-compliant structure's dtor -- set the proper
844 * allocator-context to ensure the inner data are disposed of properly first. */
845
846 const Segments_in_shm_borrowed& serialization_segments = *m_btm_serialization_shm_handle; // Attn: const!
847 // OK! So now just do the usual SegmentArrayMessageReader-like stuff as mentioned above (similarly to Heap_reader).
848
849 if (serialization_segments.empty())
850 {
851 FLOW_LOG_WARNING("SHM reader [" << *this << "]: The top serialization was valid; and the SHM handle "
852 "therein does point to a list of segments; but that list is empty. Emitting error. "
853 "Other side misbehaved?");
855 m_btm_serialization_shm_handle.reset();
856 assert(m_btm_capnp_segments.empty() && "We should not have set this up yet.");
857 return;
858 }
859 // else
860
861 auto& capnp_segs = m_btm_capnp_segments;
862 assert(capnp_segs.empty());
863 capnp_segs.reserve(serialization_segments.size());
864
865 size_t idx = 0;
866 for (const auto& serialization_segment : serialization_segments) // Reminder: serialization_segment is a Basic_blob.
867 {
868 const void* data_ptr = &(serialization_segment.front());
869 const size_t seg_size = serialization_segment.size();
870
871 if (((reinterpret_cast<uintptr_t>(data_ptr) % sizeof(word)) != 0) || ((seg_size % sizeof(word)) != 0))
872 {
873 FLOW_LOG_WARNING("SHM reader [" << *this << "]: "
874 "Serialization segment [" << idx << "] "
875 "(0-based, of [" << serialization_segments.size() << "], 1-based): "
876 "SHM-heap buffer @[" << data_ptr << "] sized [" << seg_size << "]: "
877 "Starting pointer is not capnp-word-aligned, and/or size is not a capnp-word-multiple. Bug? "
878 "Misuse of Capnp_message_reader? Other side misbehaved? "
879 "Either is against the API use requirements; capnp would complain and fail.");
881 capnp_segs.clear();
882 m_btm_serialization_shm_handle.reset();
883 return;
884 }
885 // else
886
887 FLOW_LOG_TRACE("SHM reader [" << *this << "]: "
888 "Serialization segment [" << idx << "] "
889 "(0-based, of [" << serialization_segments.size() << "], 1-based): "
890 "SHM-heap buffer @[" << data_ptr << "] sized [" << seg_size << "]: "
891 "Feeding into capnp deserialization engine.");
892 FLOW_LOG_DATA("Segment contents: "
893 "[\n" << buffers_dump_string(Blob_const{data_ptr, seg_size}, " ") << "].");
894
895 capnp_segs.emplace_back(static_cast<const word*>(data_ptr),
896 seg_size / sizeof(word)); // (An exact multiple: the size check above ensured it.)
897
898 ++idx;
899 } // for (const auto& serialization_segment : serialization_segments)
900
901 err_code->clear();
902} // Capnp_message_reader::borrow()
903
904template<typename Shm_arena>
906{
907 return !m_btm_serialization_shm_handle;
908}
909
910template<typename Shm_arena>
911kj::ArrayPtr<const ::capnp::word> Capnp_message_reader<Shm_arena>::getSegment(unsigned int id)
912{
913 assert((!empty()) && "Are you trying to access via MessageReader before loading SHM-handle into borrow()?");
914 return (id < m_btm_capnp_segments.size()) ? m_btm_capnp_segments[id] : nullptr;
915}
916
917template<typename Shm_arena>
918std::ostream& operator<<(std::ostream& os, const Capnp_message_builder<Shm_arena>& val)
919{
920 return os << '@' << &val;
921}
922
923template<typename Shm_arena>
924std::ostream& operator<<(std::ostream& os, const Capnp_message_reader<Shm_arena>& val)
925{
926 return os << '@' << &val;
927}
928
929} // namespace ipc::transport::struc::shm
RAII-style class operating a stack-like notion of the given thread's currently active SHM-aware Arena...
Stateless allocator usable with STL-compliant containers to store (or merely read) them directly in S...
A capnp::MessageBuilder used by shm::Builder and for general capnp users: similar to a MallocMessageB...
~Capnp_message_builder()
Decrements owner-process count by 1; if current count is 1 deallocates SHM-stored data.
bool m_saw_big_leaf
Gate: false until the first big-leaf event happens; then flipped to true, and m_msgs_with_big_leaf st...
flow::util::Basic_blob< Allocator< uint8_t > > Segment_in_shm
The inner data structure stored in SHM representing one capnp-requested segment storing all or part o...
uint64_t m_alloc_sz
Running tally: sum of seg_sz chosen for each allocateSegment() call.
size_t m_segment_sz
Minimum size of the next segment allocated by allocateSegment(); a word-multiple.
Arena::template Handle< Segments_in_shm > m_serialization_segments
Outer SHM handle to the data structure in SHM that stores the capnp-requested serialization segments.
Capnp_message_builder(flow::log::Logger *logger_ptr, Arena *arena, size_t segment_sz_init_pre_growth=sizeof(::capnp::word) *::capnp::SUGGESTED_FIRST_SEGMENT_WORDS, struc::stat::Serializer_stats::Snd *stats=nullptr)
Constructs the message-builder, memorizing the SHM engine it shall use to construct/allocate data int...
boost::container::list< Segment_in_shm, Allocator< Segment_in_shm > > Segments_in_shm
The outer data structure stored in SHM representing the entire list of capnp-requested segments Segme...
kj::ArrayPtr<::capnp::word > allocateSegment(unsigned int min_sz_words) override
Implements MessageBuilder API.
struc::stat::Serializer_stats::Snd *const m_stats
Cumulative stats target, or null if no stats recording is configured.
bool lend(Capnp_root_builder *capnp_root, Session_t *shm_session)
To be called after being done mutating underlying structured data, increments owner-process count by ...
A capnp::MessageReader used by shm::Reader and for general capnp users: able to access capnp-serializ...
Arena::template Handle< Segments_in_shm_borrowed > m_btm_serialization_shm_handle
The outer SHM handle to the Segments_in_shm_borrowed containing the serialization yielded by e....
bool empty() const
Return false if and only if borrow() has been invoked at least once, and the last time it was success...
void borrow(const Capnp_root_reader &capnp_root, Session_t *shm_session, Error_code *err_code=0)
Upon dropping structure loaded by the preceding borrow() call (if any), loads the SHM-stored structur...
Capnp_message_reader(flow::log::Logger *logger_ptr)
Boring constructor.
boost::container::list< Segment_in_shm_borrowed, Borrower_allocator< Segment_in_shm_borrowed > > Segments_in_shm_borrowed
For easier outside generic programming, this is the read-only-borrower counterpart to Capnp_message_b...
~Capnp_message_reader()
Any SHM-stored structure currently held as a result of the last successful borrow() is no longer held...
flow::util::Basic_blob< Borrower_allocator< uint8_t > > Segment_in_shm_borrowed
For easier outside generic programming, this is the read-only-borrower counterpart to Capnp_message_b...
kj::ArrayPtr< const ::capnp::word > getSegment(unsigned int id) override
Implements MessageReader API.
std::vector< Capnp_word_array_ptr > m_btm_capnp_segments
Analogous to Heap_reader::m_capnp_segments; just points into SHM.
kj::ArrayPtr< const ::capnp::word > Capnp_word_array_ptr
Similar to Heap_reader::Capnp_word_array_ptr.
Small group of miscellaneous utilities to ease work with capnp (Cap'n Proto), joining its capnp names...
@ S_DESERIALIZE_FAILED_INSUFFICIENT_SEGMENTS
Structured message deserialization: Tried to deserialize without enough input segments (e....
@ S_DESERIALIZE_FAILED_SEGMENT_MISALIGNED
Structured message deserialization: An input segment is not aligned as required.
@ S_DESERIALIZE_FAILED_SESSION_HOSED
Structured message deserialization: SHM-session borrow_object() indicated failure when interpreting a...
Segregates zero-copy/SHM implementations of concepts residing in parent namespace ipc::transport::str...
std::ostream & operator<<(std::ostream &os, const Capnp_message_builder< Shm_arena > &val)
Prints string representation of the given Capnp_message_builder to the given ostream.
void capnp_get_shm_handle_to_borrow(const Capnp_root_reader &shm_handle_root, flow::util::Blob_sans_log_context *arg_to_borrow)
Utility that's the reverse of capnp_set_lent_shm_handle() to be invoked on the deserializing side.
Definition: util.hpp:55
void capnp_set_lent_shm_handle(Capnp_root_builder *shm_handle_root, const flow::util::Blob_sans_log_context &lend_result)
Utility that saves the result of a Shm_session1::lend_object<T>(const shared_ptr<T>&) into the given ...
Definition: util.hpp:30
::capnp::MessageReader Capnp_msg_reader_interface
Alias for capnp's MessageReader interface. Rationale: as part of our API, we use our identifier style...
Definition: struc_fwd.hpp:221
boost::asio::const_buffer Blob_const
Short-hand for an immutable blob somewhere in memory, stored as exactly a void const * and a size_t.
Definition: util_fwd.hpp:131
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Send-side (during-serialization) stats, captured at hooks within the Struct_builder impl: typically e...