Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
heap_serializer.hpp
Go to the documentation of this file.
1/* Flow-IPC: Structured Transport
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26#pragma once
27
31#include "ipc/util/util_fwd.hpp"
32#include <flow/error/error.hpp>
33#include <boost/move/make_unique.hpp>
34#include <utility>
35
37{
38
39// Types.
40
41/**
42 * Value for Struct_builder::Session when no extra information is needed when serializing Struct_builder for
43 * subsequent sending to another process.
44 *
45 * @see Struct_builder::Session concept.
46 */
47struct Null_session {};
48
49/**
50 * Simple structure describing how a pre-split single segment was turned into 2+ post-split component sub-segments.
51 * @see #Split_segments for full context first.
52 */
54{
55 // Data.
56
57 /// The index into a fully-post-split #Segment_bufs (or similar) containing the first component of the split segment.
59
60 /**
61 * How many elements immediately following the one at #m_start_idx (but not including it!) compose the pre-split
62 * segment. This value shall be greater than or equal to 1. (0 would mean the segment was split into 1 components,
63 * and by our convention in that degenerate case the Split_segment shall not be generated at all.)
64 */
66};
67
68/**
69 * ~Simplest possible impl of Struct_builder concept that represents the capnp payload directly in heap memory
70 * (outer serialization only), with a segment-allocation strategy similar to `capnp::MallocMessageBuilder`. The
71 * `Struct_builder`-required seg 1 header-space minutiae are also followed.
72 *
73 * That is, each mutation via payload_msg_builder() may, as needed, trigger a `malloc()` (or similar) of size N,
74 * where N is (roughly speaking) either a fixed, configured value; or grows roughly exponentially each time up to
75 * N. (`capnp::MallocMessageBuilder` has similar knobs.) Plus seg 1 shall be preceded by
76 * a (zero-filled, untouched further by `*this`) header area of a specified size if so configured.
77 *
78 * @note This can/should be viewed as the core impl of Struct_builder concept. Other impls thereof most likely
79 * should be built on top of/around a Heap_fixed_builder which would implement the outer serialization, an
80 * element in practice required in a uniform way by any Struct_builder. For example shm::Builder does so: it
81 * maintains a Heap_fixed_builder for the outer serialization (even though in its case it's always one small
82 * segment, certainly without any splitting/joining needed) while itself handling the inner serialization
83 * in SHM.
84 *
85 * @internal
86 *
87 * ### Implementation ###
88 * If one is familiar with capnp, then one can probably quickly guess how this class template accomplishes
89 * what it promises in just the brief summary at the top of this doc header:
90 * Heap_fixed_builder_capnp_message_builder is what we use internally; it's like `MallocMessageBuilder`
91 * but has support for a framing prefix coexisting within at least the first allocated buffer/segment. As it must, that
92 * builder (like `MallocMessageBuilder`) is documented to allocate a larger-than-N segment if necessary to encode a
93 * particular too-large leaf. We do not stop this in and of itself; but emit_serialization() -- if supplied
94 * a split_segs out-arg -- will emit sub-segments in split fashion where needed. (If `split_segs` is null, then
95 * per concept requirements emit_serialization() will emit an error and barf, if a particular segment is too large.)
96 *
97 * @endinternal
98 *
99 * @see Heap_reader
100 * Counterpart Struct_reader implementation that can deserialize data that this has serialized.
101 * @see Struct_builder: implemented concept.
102 */
104 public flow::log::Log_context
105{
106public:
107 // Types.
108
109 /**
110 * Implements Struct_builder::Config sub-concept. In particular, since this is the core outer-serialization
111 * Struct_builder impl -- used either by itself or (realistically) in any other impl for its mandated
112 * outer-serialization aspect -- it takes most of its members directly from the concept description.
113 * Namely: #m_frame_prefix_sz; and (since indeed Heap_fixed_builder *can* produce non-small segments)
114 * #m_seg_and_frame_sz_cap. The latter is the max size of each segment (emit_serialization() will split any
115 * segment if it was forced to make a larger one). Lastly there is #m_segment_sz_init which is first (ideally
116 * only) segment's size.
117 *
118 * @note If `m_segment_sz_init == m_seg_and_frame_sz_cap` (ignoring `m_frame_prefix_sz` for simplicity of exposition),
119 * then subsequent segments cannot grow any further (the cap is already reached). This is in effect
120 * how `MallocMessageBuilder`'s `FIXED_SIZE` strategy acts, whereas if `m_segment_sz_init` is smaller, then
121 * we work like `GROW_HEURISTICALLY` strategy instead.
122 *
123 * @note Heap_fixed_builder_capnp_message_builder is internally used. It implements `capnp::MessageBuilder` much as
124 * capnp's out-of-the-box `MallocMessageBuilder` does. We mention this in case you needed a `MessageBuilder`
125 * directly for whatever reason.
126 */
127 struct Config
128 {
129 // Types.
130
131 /// Implements concept API.
133
134 // Data.
135
136 /// Logger to use for logging subsequently.
137 flow::log::Logger* m_logger_ptr;
138
139 /**
140 * Cap for how large a buffer size to choose for any header+segment buffer. The header size is controlled
141 * by #m_frame_prefix_sz; and is that value for seg 1 but zero for all subsequent ones.
142 *
143 * ### capnp behavior discussion ###
144 * Let N be this value minus the header size for that segment (so `m_frame_prefix_sz` or 0).
145 * Per capnp rules, a given segment may still exceed N, namely if capnp asks -- when calling
146 * `capnp::MessageBuilder::allocateSegment()` API -- for a value exceeding N. This occurs if and only if a
147 * particular capnp-leaf, probably a list/blob/string, is by itself of that size; a leaf can't be split across
148 * segments.
149 */
151
152 /**
153 * Seg 1 size; a value exceeding `m_seg_and_frame_sz_cap - m_frame_prefix_sz` is clamped to that.
154 *
155 * ### capnp behavior discussion ###
156 * capnp will request a segment 1 of size ~0 regardless of how the user acts; as I (ygoldfel) recall before even any
157 * capnp-generated mutators are called by the user. Therefore seg 1 shall be sized according to this value
158 * (as the larger of ~0 and X is X; in this case X being `m_seg_and_frame_sz_cap - m_frame_prefix_sz`);
159 * it doesn't matter what the first leaf the user adds to the message. Subsequent `allocateSegment()` calls,
160 * however, will actually be based on what the capnp-user needs from that point on.
161 *
162 * The point here is that seg 1 has a known, certain size, which is a nice thing to be deterministic about;
163 * after that it depends. In particular if `m_seg_and_frame_sz_cap - m_frame_prefix_sz` is sufficient for
164 * the entire message, then that's it.
165 *
166 * Therefore it is important for efficiency to select a nice `m_segment_sz_init` that's larger (ideally) than
167 * what is ultimately necessary for the whole message but doesn't leave too much slack. Zero-filling allocations
168 * are fairly expensive (that is to say, `calloc(N)` is more expensive than `calloc(M)`, where N > M).
169 */
171
172 /**
173 * Size of header to precede seg 1 in the first buffer allocated by the Heap_fixed_builder. The size must
174 * be alignment-friendly (per align-size `sizeof(capnp::word)`). Zero is allowed.
175 *
176 * No segment after seg 1 shall be preceded by such a header.
177 */
179
180 /**
181 * See Struct_builder::Config concept. Pointee (null OK = no stats kept) for cumulative
182 * snd-stats; must outlive each Heap_fixed_builder built with `*this`.
183 */
185 }; // class Config
186
187 /**
188 * Implements concept API. This being a non-zero-copy Struct_builder, no information is needed for
189 * emit_serialization() beyond the payload itself.
190 */
192
193 // Constructors/destructor.
194
195 /// Implements concept API.
197
198 /**
199 * Implements concept API. See Config doc header for information on how `*this` behavior is controlled by `config`.
200 *
201 * @param config
202 * See above.
203 */
204 explicit Heap_fixed_builder(const Config& config);
205
206 /// Disallow copy construction.
208
209 /**
210 * Implements concept API.
211 *
212 * @param src
213 * See above.
214 */
216
217 /// Implements concept API. In this impl: frees all segments allocated on-demand so far.
219
220 // Methods.
221
222 /// Disallow copy assignment.
224
225 /**
226 * Implements concept API.
227 *
228 * @param src
229 * See above.
230 * @return See above.
231 */
233
234 /**
235 * Implements concept API.
236 *
237 * @return See above.
238 */
240
241 /**
242 * Implements concept API.
243 *
244 * @param target_blobs
245 * See above.
246 * @param hdr_blob
247 * See above.
248 * @param split_segs
249 * See above.
250 * @param session
251 * See above. In this impl: the proper value is `NULL_SESSION`.
252 * @param err_code
253 * See above. Generated codes:
254 * error::Code::S_INTERNAL_ERROR_SERIALIZE_LEAF_TOO_BIG (see above; reminder: possible only if
255 * `split_segs == nullptr`).
256 */
257 void emit_serialization(Segment_bufs* target_blobs, util::Blob_mutable* hdr_blob, Split_segments* split_segs,
258 const Session& session, Error_code* err_code = nullptr) const;
259
260 /**
261 * Implements concept API.
262 *
263 * @return See above.
264 */
265 size_t n_serialization_segments() const;
266
267private:
268 // Types.
269
270 /// Alias for the capnp `MallocMessageBuilder`-like serialization engine of ours.
272
273 // Data. ATTN: If messing with this area: don't forget to check move-assignment or any other such list-of-members.
274
275 /**
276 * See ctor. We only store it (as opposed to merely passing it to #m_engine) due to the need to
277 * potentially split segments per `split_segs` in emit_serialization(). So if a certain segment had to
278 * be sized by #m_engine larger than the request cap here, the split post-processing will still follow the cap,
279 * albeit at an additional efficiency cost on the receiving side (the joining procedure will require copying).
280 *
281 * The `= 0` matters only in the default-cted case (which contractually allows only destruction or move-to);
282 * it makes that state determinate on the cheap (the main ctor's member-init overrides it). This might also
283 * prevent sanitizer false warnings.
284 */
286
287 /**
288 * The capnp builder engine which really does the work including owning the needed allocated segments so far.
289 * payload_msg_builder() simply returns (up-cast of) `&m_engine`.
290 *
291 * ### Why the `unique_ptr` wrapper? ###
292 * We need to be move-ctible and move-assignable.
293 *
294 * @note Somewhere after capnp-0.10 and before capnp-1.0.0 that became the simple situation. However before that
295 * point #Capnp_msg_builder_interface was actually movable and therefore so was
296 * our #Capnp_heap_engine a/k/a Heap_fixed_builder_capnp_message_builder. Yet we still used a
297 * wrapper here, because even then Heap_fixed_builder_capnp_message_builder doc header had a note
298 * that said it was movable, but the movability (due to `capnp::MessageBuilder` impl details) was somewhat
299 * slow.
300 *
301 * As a side benefit: it's an easy way to detect a moved-from `*this` and `assert()` in some situations.
302 */
303 boost::movelib::unique_ptr<Capnp_heap_engine> m_engine;
304}; // class Heap_fixed_builder
305
306/**
307 * Implements Struct_reader concept by straightforwardly interpreting a serialization by Heap_fixed_builder
308 * or any other builder that produces segments directly readable via `SegmentArrayMessageReader`.
309 *
310 * While written originally specifically as the counterpart to Heap_fixed_builder, it would work with any
311 * builder that produces an equivalent serialization modulo using potentially different segment sizes.
312 * That is why it is called `Heap_reader` -- not `Heap_fixed_reader` or some-such.
313 *
314 * @see Heap_fixed_builder
315 * A counterpart Struct_builder implementation that can create compatible serializations.
316 * @see Struct_reader: implemented concept.
317 */
319 public flow::log::Log_context,
320 private boost::noncopyable
321{
322public:
323 // Types.
324
325 /// Implements Struct_reader::Config sub-concept.
326 struct Config
327 {
328 // Types.
329
330 /// Implements concept API.
332
333 // Data.
334
335 /// Logger to use for logging subsequently.
336 flow::log::Logger* m_logger_ptr;
337
338 /**
339 * Best guess for how many times you expect to call add_serialization_segment(); or 0 if you have no guess.
340 * This is a (fairly minor as of this writing) optimization opportunity; an internal container may be
341 * `reserve()`d preventing future reallocs.
342 */
344
345 /**
346 * See Struct_reader::Config concept. Pointee (null OK = no stats kept) for cumulative
347 * rcv-stats; must outlive each Heap_reader built with `*this` (the gauge decrements
348 * happen in Heap_reader dtor).
349 */
351 }; // class Config
352
353 // Constructors/destructor.
354
355 /**
356 * Implements concept API. See Config doc header for information on how `*this` behavior is controlled by `config`.
357 *
358 * @param config
359 * See above.
360 */
361 explicit Heap_reader(const Config& config);
362
363 /// Implements concept API. In this impl: frees all segments allocated via add_serialization_segment() so far.
364 ~Heap_reader();
365
366 // Methods.
367
368 /**
369 * Implements concept API.
370 *
371 * @param blob
372 * See above.
373 */
375
376 /**
377 * Implements concept API.
378 *
379 * @tparam Struct
380 * See above.
381 * @param split_segs_or_null
382 * See above.
383 * @param err_code
384 * See above. #Error_code generated:
385 * error::Code::S_DESERIALIZE_FAILED_INSUFFICIENT_SEGMENTS (add_serialization_segment() was never called),
386 * error::Code::S_DESERIALIZE_FAILED_SEGMENT_MISALIGNED (add_serialization_segment()-given segment
387 * was not properly aligned -- start address or size -- at this time),
388 * error::Code::S_DESERIALIZE_FAILED_REASSEMBLY_FAILED (something wrong with the contents of
389 * `*split_segs_or_null` including but not limited to it being `.empty()`).
390 * @return See above.
391 */
392 template<typename Struct>
393 typename Struct::Reader deserialization(const Split_segments* split_segs_or_null, Error_code* err_code = nullptr);
394
395private:
396 // Types.
397
398 /// Alias for the capnp `SegmentArrayMessageReader` deserialization engine.
399 using Capnp_heap_engine = ::capnp::SegmentArrayMessageReader;
400
401 /// Alias to pointer to #Capnp_heap_engine.
402 using Capnp_heap_engine_ptr = boost::movelib::unique_ptr<Capnp_heap_engine>;
403
404 /**
405 * Alias to capnp type that's similar to util::Blob_const but stores `word*` and `word` count
406 * instead of `uint8_t*` and byte count.
407 */
408 using Capnp_word_array_ptr = kj::ArrayPtr<const ::capnp::word>;
409
410 // Data.
411
412 /**
413 * Before deserialization(): the serialization payloads supplied so far, as divided up into (sub-)segments, each via
414 * an add_serialization_segment() call; after it: the actual serialization consisting of the segments (after any
415 * re-joining as needed).
416 */
417 std::vector<Segment_blob_in> m_serialization_segments;
418
419 /**
420 * Null/meaningless until deserialization() returns; once it does: if needed by the re-joining procedure
421 * possibly performed by that method, this stores the moved contents of `m_serialization_segments[0]` which is
422 * replaced with the finalized segment 1, as joined from 2+ sub-segments. If `m_serialization_segments[0]` need not
423 * be replaced (such as if it was a full-segment already, and/or no splitting had been done at all to any segments),
424 * then this remains null.
425 *
426 * When non-null, the value itself is never checked/used by `*this` per se; it is just there to satisfy the
427 * contract of add_serialization_segment() and deserialization(), which say that regardless of any re-joining
428 * done by the latter, the first (sub-)segment supplied via add_serialization_segment() must remain in memory
429 * until destruction or move-from.
430 */
432
433 /**
434 * After deserialization(), capnp-targeted data structure which is essentially `vector<>` of each
435 * `Blob::const_buffer()` from #m_serialization_segments, in order, except instead of a given `const_buffer`
436 * it's the capnp-style data structure that stores a `word*` and `word` count (instead of
437 * `uint8_t*` and byte count). This must be alive while #m_engine is non-null, so from deserialization() on;
438 * capnp's #m_engine saves the pointer `&m_capnp_segments[0]` and `m_capnp_segments.size()`.
439 */
440 std::vector<Capnp_word_array_ptr> m_capnp_segments;
441
442 /**
443 * The capnp reader engine, as set in the deserialization() call and corresponding to the
444 * `Struct::Reader` it returned; or null if deserialization() has not yet been called. In terms of data this, really,
445 * just stores pointers into the backing serialization -- the blobs #m_serialization_segments.
446 * deserialization()-returned value then adds the capnp structured-data accessors which hop around those segments,
447 * obtaining individual values directly from the segment blobs #m_serialization_segments.
448 */
450
451 /**
452 * See Config::m_stats: cumulative stat::Serializer_stats::Rcv pointee `*this` shall update; null OK
453 * (no stats kept). The pointee, if any, outlives `*this`.
454 */
456
457 /**
458 * Cache: sum of `m_serialization_segments[].capacity()` (post-reassembly-if-needed inside
459 * deserialization()); zero until then. Used in dtor for outstanding-bytes gauge decrement. Frozen
460 * once deserialization() succeeds. (Context: `*this`-storing Msg_in -- if any -- is read-only post-creation and
461 * certainly on delivery to user from `Channel`).
462 *
463 * Ignored if #m_stats null (may or may not still accumulate; tactical decision).
464 */
465 uint64_t m_alloc_sz;
466
467 /// Cache: analogously to #m_alloc_sz: sum of `m_serialization_segments[].size()`.
468 uint64_t m_used_sz;
469}; // class Heap_reader
470
471/**
472 * Convenience alias: Use this when constructing a struc::Channel with tag
473 * Channel_base::S_SERIALIZE_VIA_HEAP.
474 *
475 * See Channel_base::Serialize_via_heap doc header for when/how to use.
476 *
477 * Tip: `Sync_io_obj` member alias will get you the `sync_io`-pattern counterpart.
478 *
479 * @internal
480 * Unable to put it in ..._fwd.hpp, because it relies on nested class inside incomplete type.
481 */
482template<typename Channel_obj, typename Msg_body_t>
484 = Channel<Channel_obj, Msg_body_t,
486
487// Free functions: in *_fwd.hpp.
488
489// Template implementations.
490
491template<typename Struct>
492typename Struct::Reader Heap_reader::deserialization(const Split_segments* split_segs_or_null, Error_code* err_code)
493{
494 using flow::error::Runtime_error;
495 using flow::util::buffers_dump_string;
496 using flow::util::stat::fetch_add;
497 using flow::util::stat::update_hi_wmark;
498 using boost::movelib::make_unique;
499 using std::vector;
500 using std::swap; // Required for correct ADL-semantics.
501 using ::capnp::word;
502 using Capnp_word_array_array_ptr = kj::ArrayPtr<const Capnp_word_array_ptr>;
503 using Capnp_heap_engine_opts = ::capnp::ReaderOptions;
504
505 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(typename Struct::Reader, deserialization<Struct>, split_segs_or_null, _1);
506 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
507
508 // See explanation and associated to-do in shm::Reader::deserialization(); applies equally here.
509 constexpr Capnp_heap_engine_opts RDR_OPTIONS = { std::numeric_limits<uint64_t>::max() / sizeof(word),
510 Capnp_heap_engine_opts{}.nestingLimit };
511
512 if (m_serialization_segments.empty())
513 {
515 return {};
516 }
517 // else: OK so far.
518
519 assert((!m_engine) && "Do not call deserialization() more than once.");
520
521 if (split_segs_or_null) // Note: This being true is rare, or should be; which is good, as reassembly is costly.
522 {
523 const auto& split_segs = *split_segs_or_null;
524 if (split_segs.empty())
525 {
526 FLOW_LOG_WARNING("Heap_reader [" << *this << "]: "
527 "Split-segment records sequence is supplied but empty, against internal protocol; "
528 "it should have not been supplied (null).");
530 return {};
531 }
532 // else
533
534 /* Time to reassemble the segments, as needed, according to split_segs if not null. split_segs semantics are
535 * simple, but please refer to the doc header of Split_segments for a recap. Per the algorithm explained there,
536 * scan through split_segs in backwards order (so as to avoid making later-scanned indices in split_segs become
537 * wrong), and join each sequence of 2+ elements of m_serialization_segments (as described in that split_segs[])
538 * into 1, in-place. A couple of subtleties to point out ahead of time:
539 * - It is possible (we won't discuss here how likely; it depends on stuff not in our purview, but trust me that
540 * it is at least conceivably possible) that when we must join 2+ segs, in order, [s1, ...], the area
541 * [s1.begin() + s1.size(), s1.begin() - s1.start() + s1.capacity()) (the unused, but allocated, area in the
542 * first of the to-be-joined sub-segments) is large enough to hold the combined `.size()`s of the remaining ...
543 * sub-segments. In that case, for perf, we reuse s1 as the ultimately-joined segment Blob, copying each of ...
544 * onto it and adjusting its .size(). Then remove the ... from the vector. (It's an opportunistic optimization
545 * that eliminates one allocation and one seg copy.) Otherwise we must create a new large-enough seg and
546 * simply copy s1 and ... onto it; then replace s1 with the resulting segment and remove ... from the vector.
547 * - By our contract, we *are* allowed to invalidate m_serialization_segments elements -- both the `Blob`s
548 * themselves (essentially "handles" to data areas in memory) and the referred-to-thereby data areas themselves.
549 * However we are *not* allowed to invalidate the data area known as the header, which is
550 * the byte range [seg1.begin() - seg1.start(), seg1.begin()), where seg1 is m_serialization_segments[0].
551 * - Therefore: If seg1 is mentioned in split_segs, and its unused-capacity area is not large enough to
552 * enable the optimization in previous bullet point, then we must save seg1 somewhere -- to avoid invalidating
553 * the header area -- even as we replace it in m_serialization_segments with the new post-join segment Blob.
554 * For this purpose we use m_seg1_hdr_backup which shall simply keep the header (and remaining stuff, no
555 * longer used but cannot be deallocated due to being part of the header-containing buffer) until dtor
556 * destroys everything. */
557
558 const auto split_segs_it_rend = split_segs.rend();
559 for (auto split_segs_it = split_segs.rbegin();
560 split_segs_it != split_segs_it_rend;
561 ++split_segs_it)
562 {
563 const auto& split_seg = *split_segs_it;
564 const size_t start_idx = split_seg.m_start_idx;
565 const size_t n_cont_subsegs = split_seg.m_n_cont_subsegs;
566
567 /* m_s_s.size() changes on-the-fly, but we go in decreasing order of start_idx values (no, we don't check it;
568 * we trust the opposing side and check for correctness when we feel like it/decent effort), so the range
569 * check here is correct. The 1st check validates that a record is only included if a segment actually was
570 * split; plus we need not think about degenerate cases below as a result. Note the subtraction-form
571 * bounds checks: the naive `start_idx + n_cont_subsegs + 1 > .size()` could wrap around given garbage
572 * input values and pass. */
573 if ((n_cont_subsegs == 0) || (start_idx >= m_serialization_segments.size())
574 || (n_cont_subsegs > (m_serialization_segments.size() - 1 - start_idx)))
575 {
576 FLOW_LOG_WARNING("Heap_reader [" << *this << "]: "
577 "Split-segment record (start-index [" << start_idx << "] "
578 "(0-based, of [" << split_segs.size() << "], 1-based), "
579 "# continuation sub-segs [" << n_cont_subsegs << "]): "
580 "The latter value must be 1+ and not exceed (+ start_idx) # of input buffers "
581 "[" << m_serialization_segments.size() << "].");
583 return {};
584 }
585 // else
586
587 const size_t end_idx = start_idx + n_cont_subsegs + 1; // n_cont_subsegs does *not* include start_idx-th one.
588 // (Cannot overflow: the range check above ensured end_idx <= m_serialization_segments.size().)
589
590 size_t cont_total_sz = 0;
591 for (size_t idx = start_idx + 1; idx != end_idx; ++idx)
592 {
593 cont_total_sz += m_serialization_segments[idx].size();
594 }
595
596 auto& subseg1 = m_serialization_segments[start_idx];
597 const bool first_seg = start_idx == 0; // I.e., whether `&subseg1 == &m_serialization_segments.front()`.
598
599 const auto unused_cap = static_cast<size_t>(subseg1.capacity() - subseg1.size() - subseg1.start());
600 if (unused_cap >= cont_total_sz)
601 {
602 // This is the case where subseg1's unused capacity can fit the remaining sub-segs' total bytes.
603 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: "
604 "Split-segment record (start-index [" << start_idx << "] "
605 "(0-based, of [" << split_segs.size() << "], 1-based), "
606 "# continuation sub-segs [" << n_cont_subsegs << "]): "
607 "All [" << cont_total_sz << "] bytes across continuation sub-segs can fit into "
608 "unused capacity [" << unused_cap << "] of start-index-th sub-seg; copying their contents there "
609 "accordingly and deleting them.");
610
611 auto dest_it = subseg1.end();
612 subseg1.resize(subseg1.size() + cont_total_sz); // .start() left unchanged.
613 const auto cont_subseg_begin_it = m_serialization_segments.begin() + start_idx + 1;
614 const auto cont_subseg_end_it = m_serialization_segments.begin() + end_idx;
615 for (auto cont_subseg_it = cont_subseg_begin_it;
616 cont_subseg_it != cont_subseg_end_it;
617 (dest_it += cont_subseg_it->size()), ++cont_subseg_it)
618 {
619 subseg1.emplace_copy(dest_it, cont_subseg_it->const_buffer());
620 }
621 assert(dest_it == subseg1.end());
622
623 m_serialization_segments.erase(cont_subseg_begin_it, cont_subseg_end_it);
624
625 /* Per algorithm above, reminder: subseg1's original contents sit undisturbed; no need to worry about
626 * having to use m_seg1_hdr_backup, even if subseg1 is indeed m_serialization_segments.front(). It is
627 * still there (and always will be), just more bytes are tacked on at the end. */
628 } // if (unused_cap >= cont_total_sz) // subseg1 can fit all of subseg1 + continuation sub-segs.
629 else // if (subseg1 cannot fit all of subseg1 + continuation sub-segs)
630 {
631 /* This is the (probably likelier) case where subseg1 is no different than the others.
632 * Per algorithm above, reminders:
633 * - Mainly just make an all-new Blob combining subseg1 and the n_cont_subsegs other ones in that order.
634 * Then replace subseg1 with that resulting new Blob.
635 * - If subseg1 is .front() (<=> first_seg is true), then it must be saved in m_seg1_hdr_backup, so as to keep
636 * the header bytes alive where they are (and since buffer cannot be part-freed... all of it).
637 * So tactically we just use m_seg1_hdr_backup as bullet 1 result Blob. Then swap that guy with subseg1, and
638 * .erase() the other sub-segs from m_serialization_segments. If first_seg, we're done. Otherwise, also
639 * m_seg1_hdr_backup.make_zero(), since no need to keep ex-subseg1 bytes alive. That completes bullet 2.
640 *
641 * Note that we're going backwards; so first_seg is true (if ever) only in our very last iteration.
642 * Hence m_seg1_hdr_backup -- if it were ever non-.zero() in a previous iteration -- would have been
643 * de-alloc-ed before we got here. Hence these assert(). */
644 assert(m_seg1_hdr_backup.zero() && "Original state should be unallocated.");
645
646 m_seg1_hdr_backup.resize(subseg1.size() + cont_total_sz);
647 auto dest_it = m_seg1_hdr_backup.begin();
648
649 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: "
650 "Split-segment record (start-index [" << start_idx << "] "
651 "(0-based, of [" << split_segs.size() << "], 1-based), "
652 "# continuation sub-segs [" << n_cont_subsegs << "]): "
653 "All [" << cont_total_sz << "] bytes across continuation sub-segs cannot fit into "
654 "unused capacity [" << unused_cap << "] of start-index-th sub-seg; "
655 "therefore creating new segment-blob of size [" << m_seg1_hdr_backup.size() << "], "
656 "copying sub-segs' contents there, and replacing sub-segments with the resulting blob. "
657 "Start-index-th sub-seg shall be saved elsewhere so as to keep frame-header alive? = "
658 "[" << first_seg << "].");
659
660 auto subseg_begin_it = m_serialization_segments.begin() + start_idx;
661 const auto subseg_end_it = m_serialization_segments.begin() + end_idx;
662 for (auto subseg_it = subseg_begin_it;
663 subseg_it != subseg_end_it;
664 (dest_it += subseg_it->size()), ++subseg_it)
665 {
666 m_seg1_hdr_backup.emplace_copy(dest_it, subseg_it->const_buffer());
667 }
668 assert(dest_it == m_seg1_hdr_backup.end());
669
670 swap(m_seg1_hdr_backup, subseg1);
671 if (!first_seg)
672 {
673 m_seg1_hdr_backup.make_zero();
674 }
675
676 m_serialization_segments.erase(++subseg_begin_it, subseg_end_it);
677 } // else // if (unused_cap < cont_total_sz) // subseg1 cannot fit all of subseg1 + continuation sub-segs.
678 } // for (split_seg in split_segs, backwards)
679 } // if (split_segs_or_null) // Usually false.
680
681 // m_serialization_segments won't change past this point for the lifetime of *this.
682
683 // Set up capnp ArrayPtr<ArrayPtr>. ArrayPtr is a ptr (to word) + size (in `word`s). The backing vector:
684 auto& capnp_segs = m_capnp_segments;
685 assert(capnp_segs.empty() && "m_capnp_segments should have been empty so far, pre-deserialization().");
686 capnp_segs.reserve(m_serialization_segments.size());
687
688 size_t idx = 0;
689 for (const auto& serialization_segment : m_serialization_segments)
690 {
691 const auto data_ptr = serialization_segment.const_data();
692 const auto seg_sz = serialization_segment.size();
693
694 if (((reinterpret_cast<uintptr_t>(data_ptr) % sizeof(word)) != 0) || ((seg_sz % sizeof(word)) != 0))
695 {
696 FLOW_LOG_WARNING("Heap_reader [" << *this << "]: "
697 "Serialization segment [" << idx << "] "
698 "(0-based, of [" << m_serialization_segments.size() << "], 1-based): "
699 "Heap buffer @[" << static_cast<const void*>(data_ptr) << "] sized [" << seg_sz << "]: "
700 "Starting pointer is not capnp-word-aligned, and/or size is not a capnp-word-multiple. "
701 "Bug? Misuse of Heap_reader? "
702 "Either is against the API use requirements; capnp would complain and fail.");
704 return {};
705 }
706 // else
707
708 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: "
709 "Serialization segment [" << idx << "] "
710 "(0-based, of [" << m_serialization_segments.size() << "], 1-based): "
711 "Heap buffer @[" << static_cast<const void*>(data_ptr) << "] sized [" << seg_sz << "]: "
712 "Feeding into capnp deserialization engine.");
713 FLOW_LOG_DATA("Segment contents: "
714 "[\n" << buffers_dump_string(serialization_segment.const_buffer(), " ") << "].");
715
716 capnp_segs.emplace_back(reinterpret_cast<const word*>(data_ptr),
717 /* ^-- OK cast. We prefer dealing with bytes; capnp wants to deal with `word`s;
718 * we're giving it the right address, and it is aligned. The rest -- formal safety
719 * of further access in the segment -- is on capnp. */
720 seg_sz / sizeof(word)); // (An exact multiple: the size check above ensured it.)
721
722 /* Stats: To avoid another loop, we can accumulate these opportunistically. Could check `bool(m_stats)`, but
723 * that's slower overall, so just do it. */
724 m_alloc_sz += serialization_segment.capacity();
725 m_used_sz += seg_sz;
726
727 ++idx;
728 } // for (const auto& serialization_segment : m_serialization_segments)
729
730 /* Initialize the deserialization engine by giving it the pointers to/size of the backing segment blobs.
731 * It doesn't copy this array of pointers/sizes, so that array must stay alive, hence why capnp_segs is
732 * really m_capnp_segments. To be clear: not only must the blobs stay alive but so must the array referring
733 * to them. */
734 const Capnp_word_array_array_ptr capnp_segs_ptr{&(capnp_segs.front()), capnp_segs.size()};
735 m_engine = make_unique<Capnp_heap_engine>(capnp_segs_ptr, RDR_OPTIONS);
736
737 err_code->clear();
738
739 /* Stats. Past all early-exit error returns; m_engine is set (which indicates, in the stats context, that
740 * deserialization() happened and succeeded).
741 * (Context: If we are used by a Msg_in, that Msg_in can be emitted to the user by Channel.)
742 *
743 * Sample/increment everything here; the dtor decrements the gauges, partially based on
744 * m_alloc_sz and m_used_sz. */
745 if (m_stats)
746 {
747 update_hi_wmark(&m_stats->m_msgs_outstanding_hi_wmark,
748 fetch_add(&m_stats->m_msgs_outstanding, 1) + 1);
752 update_hi_wmark(&m_stats->m_used_outstanding_sz_hi_wmark,
756 }
757
758 // And lastly set up the structured-accessor API object that'll traverse those blobs via that engine.
759 return m_engine->getRoot<Struct>();
760} // Heap_reader::deserialization()
761
762} // namespace ipc::transport::struc
Owning and wrapping a pre-connected transport::Channel peer (an endpoint of an established channel ov...
Definition: channel.hpp:645
A capnp::MessageBuilder used by Heap_fixed_builder: similar to a capnp::MallocMessageBuilder (albeit ...
~Simplest possible impl of Struct_builder concept that represents the capnp payload directly in heap ...
Capnp_msg_builder_interface * payload_msg_builder()
Implements concept API.
size_t n_serialization_segments() const
Implements concept API.
Heap_fixed_builder(const Heap_fixed_builder &)=delete
Disallow copy construction.
Heap_fixed_builder()
Implements concept API.
boost::movelib::unique_ptr< Capnp_heap_engine > m_engine
The capnp builder engine which really does the work including owning the needed allocated segments so...
~Heap_fixed_builder()
Implements concept API. In this impl: frees all segments allocated on-demand so far.
void emit_serialization(Segment_bufs *target_blobs, util::Blob_mutable *hdr_blob, Split_segments *split_segs, const Session &session, Error_code *err_code=nullptr) const
Implements concept API.
Heap_fixed_builder & operator=(const Heap_fixed_builder &)=delete
Disallow copy assignment.
Heap_fixed_builder(Heap_fixed_builder &&src)
Implements concept API.
Implements Struct_reader concept by straightforwardly interpreting a serialization by Heap_fixed_buil...
Heap_reader(const Config &config)
Implements concept API.
Struct::Reader deserialization(const Split_segments *split_segs_or_null, Error_code *err_code=nullptr)
Implements concept API.
uint64_t m_alloc_sz
Cache: sum of m_serialization_segments[].capacity() (post-reassembly-if-needed inside deserialization...
boost::movelib::unique_ptr< Capnp_heap_engine > Capnp_heap_engine_ptr
Alias to pointer to Capnp_heap_engine.
uint64_t m_used_sz
Cache: analogously to m_alloc_sz: sum of m_serialization_segments[].size().
~Heap_reader()
Implements concept API. In this impl: frees all segments allocated via add_serialization_segment() so...
::capnp::SegmentArrayMessageReader Capnp_heap_engine
Alias for the capnp SegmentArrayMessageReader deserialization engine.
stat::Serializer_stats::Rcv *const m_stats
See Config::m_stats: cumulative stat::Serializer_stats::Rcv pointee *this shall update; null OK (no s...
std::vector< Segment_blob_in > m_serialization_segments
Before deserialization(): the serialization payloads supplied so far, as divided up into (sub-)segmen...
Capnp_heap_engine_ptr m_engine
The capnp reader engine, as set in the deserialization() call and corresponding to the Struct::Reader...
Segment_blob_in m_seg1_hdr_backup
Null/meaningless until deserialization() returns; once it does: if needed by the re-joining procedure...
void add_serialization_segment(Segment_blob_in &&blob)
Implements concept API.
std::vector< Capnp_word_array_ptr > m_capnp_segments
After deserialization(), capnp-targeted data structure which is essentially vector<> of each Blob::co...
kj::ArrayPtr< const ::capnp::word > Capnp_word_array_ptr
Alias to capnp type that's similar to util::Blob_const but stores word* and word count instead of uin...
@ S_DESERIALIZE_FAILED_INSUFFICIENT_SEGMENTS
Structured message deserialization: Tried to deserialize without enough input segments (e....
@ S_DESERIALIZE_FAILED_REASSEMBLY_FAILED
Structured message deserialization: Cannot reassemble large capnp-segment; internal protocol error.
@ S_DESERIALIZE_FAILED_SEGMENT_MISALIGNED
Structured message deserialization: An input segment is not aligned as required.
Reader< ipc::shm::classic::Pool_arena > Reader
Convenience alias: transport::struc::shm::Reader that works with boost.ipc.shm pools from ipc::shm::c...
Definition: classic_fwd.hpp:39
Sub-module of Flow-IPC module ipc::transport providing transmission of structured messages specifical...
Definition: channel.cpp:31
std::vector< Split_segment > Split_segments
A simple data structure describing the segments (if any) in a Segment_bufs (or similar) structure whi...
Definition: struc_fwd.hpp:185
flow::util::Blob_sans_log_context Segment_blob_in
Short-hand for a high-performance blob structure that represents an IPC-incoming (sub-)segment 1+ of ...
Definition: struc_fwd.hpp:192
std::vector< util::Blob_const > Segment_bufs
Simpler cousin of Segment_ptrs, this represents the IPC-transmissible representation of a capnp seria...
Definition: struc_fwd.hpp:171
void swap(Bipc_mq_handle &val1, Bipc_mq_handle &val2)
Implements Persistent_mq_handle related concept: Swaps two objects.
boost::asio::mutable_buffer Blob_mutable
Short-hand for a mutable blob somewhere in memory, stored as exactly a void* and a size_t.
Definition: util_fwd.hpp:137
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Implements Struct_builder::Config sub-concept.
size_t m_seg_and_frame_sz_cap
Cap for how large a buffer size to choose for any header+segment buffer.
size_t m_segment_sz_init
Seg 1 size; a value exceeding m_seg_and_frame_sz_cap - m_frame_prefix_sz is clamped to that.
flow::log::Logger * m_logger_ptr
Logger to use for logging subsequently.
size_t m_frame_prefix_sz
Size of header to precede seg 1 in the first buffer allocated by the Heap_fixed_builder.
stat::Serializer_stats::Snd * m_stats
See Struct_builder::Config concept.
Implements Struct_reader::Config sub-concept.
stat::Serializer_stats::Rcv * m_stats
See Struct_reader::Config concept.
flow::log::Logger * m_logger_ptr
Logger to use for logging subsequently.
size_t m_n_segments_est
Best guess for how many times you expect to call add_serialization_segment(); or 0 if you have no gue...
Value for Struct_builder::Session when no extra information is needed when serializing Struct_builder...
Simple structure describing how a pre-split single segment was turned into 2+ post-split component su...
size_t m_start_idx
The index into a fully-post-split Segment_bufs (or similar) containing the first component of the spl...
size_t m_n_cont_subsegs
How many elements immediately following the one at m_start_idx (but not including it!...
Receive-side (during-deserialization) stats, captured at hooks within the Struct_reader impl.
std::atomic< size_t > m_used_outstanding_sz
Gauge: Subset of m_alloc_outstanding_sz that contains actual capnp-serialization payload.
std::atomic< size_t > m_alloc_outstanding_sz_hi_wmark
High water-mark of m_alloc_outstanding_sz observed so far.
std::atomic< size_t > m_msgs_outstanding_hi_wmark
High water-mark of m_msgs_outstanding observed so far.
std::atomic< size_t > m_msgs_outstanding
Gauge: Currently-alive Struct_reader (usually <=> Msg_in) instances.
std::atomic< uint64_t > m_alloc_lifetime_sz
Lifetime sum of segment-blob capacity across all Struct_reader (usually <=> Msg_in) instances ever.
std::atomic< size_t > m_alloc_outstanding_sz
Gauge: Of bytes tracked by m_alloc_lifetime_sz, how many are currently allocated as opposed to now-fr...
std::atomic< size_t > m_used_outstanding_sz_hi_wmark
High water-mark of m_used_outstanding_sz observed so far.
flow::util::stat::Histogram_counter m_histo_msg_alloc_sz
Histogram tracking per-Struct_reader (usually <=> Msg_in) contribution to m_alloc_lifetime_sz.
flow::util::stat::Histogram_counter m_histo_msg_used_sz
Subset of bytes tracked in m_histo_msg_alloc_sz that contains actual capnp-serialization payload.
Send-side (during-serialization) stats, captured at hooks within the Struct_builder impl: typically e...