Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
heap_serializer.cpp
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
28#include <boost/move/make_unique.hpp>
29#include <type_traits>
30
32{
33
34// Static initializers.
35
37
38// Heap_fixed_builder implementations.
39
41
43 flow::log::Log_context(config.m_logger_ptr, Log_component::S_TRANSPORT),
44 // (The rounding-down-multiple thing is specified in Heap_fixed_builder_capnp_message_builder ctor doc header.)
45 m_seg_and_frame_sz_cap((config.m_seg_and_frame_sz_cap / sizeof(::capnp::word)) * sizeof(::capnp::word)),
46 // The builder engine launched here. In the future mutators will cause it to allocate in heap on-demand.
47 m_engine(boost::movelib::make_unique<Capnp_heap_engine>
48 (m_seg_and_frame_sz_cap,
49 config.m_frame_prefix_sz, // Do possibly reserve header space before seg 1.
50 0, // Don't reserve headers before each of seg 2, 3, ....
51 true, // Always use the exponential seg size growth, technically...
52 config.m_segment_sz_init, // ...but if this ~equals m_seg_and_frame_sz_cap then effectively don't.
53 config.m_stats)) // Snd-stats target chosen by config (typically by factory); null = no stats.
54{
55 FLOW_LOG_TRACE("Heap_fixed_builder [" << *this << "]: Fixed-size builder started: "
56 "segment (plus framing) size limit [" << m_seg_and_frame_sz_cap << "], "
57 "seg1 min size [" << config.m_segment_sz_init << "]; seg1 frame header "
58 "[" << config.m_frame_prefix_sz << "].");
59 assert(m_seg_and_frame_sz_cap != 0);
60 assert(config.m_segment_sz_init != 0);
61 /* Note: config.m_segment_sz_init may exceed m_seg_and_frame_sz_cap - config.m_frame_prefix_sz; per Config
62 * contract the engine clamps it to that. (E.g., struc::Channel::heap_fixed_builder_config() counts on this:
63 * it forwards the user's segment1_sz estimate as-is, with the cap set to the channel's max-blob-size.) */
64}
65
67
69{
70 FLOW_LOG_TRACE("Heap_fixed_builder [" << *this << "]: Fixed-size builder being destroyed.");
71 /* @todo Maybe have Heap_fixed_builder_capnp_message_builder log something useful in its dtor?
72 * It was more or less intentional for that capnp::MessageBuilder to not log/even take a log-context, so
73 * this is not a slam-dunk. Revisit.
74 *
75 * @todo Possibly TRACE-log final stats set by m_engine (either here + move-assign -- if preceding to-do not done;
76 * or else in aforementioned dtor). Also not a slam-dunk, as in the mainstream the same stats are being
77 * touched by others concurrently (typically -- definitely not necessarily -- via some globals). Still it could
78 * help debugging/testing.
79 *
80 * @todo The preceding to-do (with not-a-slam-dunk caveat) would also apply at least to shm::Builder's
81 * m_btm_engine type's dtor.
82 *
83 * @todo The preceding to-do (with not-a-slam-dunk caveat) would also apply to Heap_reader dtor. */
84}
85
87{
88 /* This exists only to log; otherwise it would be `= default`. Replacing #m_engine destroys the
89 * underlying capnp `MessageBuilder`, where snd-stats finalize -- so mirror dtor's TRACE here so
90 * the audit trail is honest on this path too. */
91 if (&src != this)
92 {
93 if (m_engine)
94 {
95 FLOW_LOG_TRACE("Heap_fixed_builder [" << *this << "]: Fixed-size builder being destroyed "
96 "(move-assigned-over).");
97 }
98
99 // The rest is what `= default` would have done.
100 static_cast<flow::log::Log_context&>(*this) = std::move(src);
101 m_seg_and_frame_sz_cap = src.m_seg_and_frame_sz_cap;
102 m_engine = std::move(src.m_engine);
103 }
104
105 return *this;
106}
107
109{
110 assert(m_engine && "Are you operating on a moved-from `*this`?");
111
112 return m_engine.get();
113}
114
116 Split_segments* split_segs,
117 const Session& session, Error_code* err_code) const
118{
119 using util::Blob_const;
120 using util::Blob_mutable;
121
122 if (flow::error::exec_void_and_throw_on_error
123 ([&](Error_code* actual_err_code)
124 { emit_serialization(target_blobs, hdr_blob, split_segs, session, actual_err_code); },
125 err_code, "Heap_fixed_builder::emit_serialization()"))
126 {
127 return;
128 }
129 // If got here: err_code is not null.
130
131 assert(m_engine && "Are you operating on a moved-from `*this`?");
132
133 /* Reminder: concept API says emit_serialization() can be called >1 times. We do allow that in the below.
134 * Keep in mind that mutations can be executed in-between, so whatever caching for perf one imagines has
135 * to not mess up in the face of that. */
136
137 const auto& seg_and_hdr_sz_cap = m_seg_and_frame_sz_cap;
138 target_blobs->clear();
139 if (split_segs)
140 {
141 split_segs->clear();
142 }
143
144 Segment_ptrs blob_ptrs;
145 m_engine->emit_segment_blobs(&blob_ptrs);
146
147 const auto n_blobs = blob_ptrs.size();
148 assert((n_blobs != 0) && "Empty serialization? What went wrong?");
149 auto n_blobs_with_subsegs = n_blobs; // Small optimization involves this....
150 target_blobs->reserve(n_blobs); // No splits (which is typical) means this optimization is sufficient.
151
152 for (size_t idx = 0; idx != n_blobs; ++idx)
153 {
154 auto& blob = *(blob_ptrs[idx]);
155 const auto buf = blob.mutable_buffer();
156 const auto seg_sz = buf.size();
157
158 /* Header area precedes seg 1 (which must exist); no other headers. Reminder: our workhorse m_engine is
159 * required to allocate the seg and also enough header bytes preceding it. So we just recognize the fruits of
160 * its labor now; and do the post-processing splitting if needed/requested. */
161
162 size_t hdr_sz_or_0;
163 if (idx == 0)
164 {
165 hdr_sz_or_0 = blob.start();
166 *hdr_blob = Blob_mutable{static_cast<void*>(blob.begin() - hdr_sz_or_0), hdr_sz_or_0};
167 }
168 else
169 {
170 hdr_sz_or_0 = 0;
171 }
172
173 FLOW_LOG_TRACE("Heap_fixed_builder [" << *this << "]: "
174 "Serialization segment [" << idx << "] (0-based, of [" << n_blobs << "], 1-based): "
175 "Heap buffer @[" << buf.data() << "] sized [" << seg_sz << "]; "
176 "with header/prefix (if any) sized [" << hdr_sz_or_0 << "].");
177
178 target_blobs->emplace_back(buf); // In case of split below its .size() shall be "corrected."
179
180 // Check for the one error (or special) condition: a too-large segment.
181 if ((seg_sz + hdr_sz_or_0) > seg_and_hdr_sz_cap)
182 {
183 if (!split_segs)
184 {
185 FLOW_LOG_WARNING("Heap_fixed_builder [" << *this << "]: "
186 "Serialization segment [" << idx << "] (0-based, of [" << n_blobs << "], 1-based): "
187 "Heap buffer @[" << buf.data() << "] "
188 "sized [" << seg_sz << "] plus frame header [" << hdr_sz_or_0 << "]: exceeded limit "
189 "[" << seg_and_hdr_sz_cap << "]. Seg-split not requested (why not? bug?) so: "
190 "Emitting error; will not return serialization.");
192 return;
193 }
194 // else if (split_segs):
195
196 /* If seg is too big, split it up into the following, in order:
197 * - seg itself: Set its size to <cap minus hdr_sz_or_0> instead of the too-big size.
198 * - [n_full_cont_subsegs times]: "Whole" (cap-sized) continuation sub-segments.
199 * - [Only if a "partial" (<cap-sized) # of bytes is left over after that]: Final sub-segment.
200 * n_cont_subsegs = n_full_cont_subsegs from bullet two, plus 1 if bullet three applies.
201 *
202 * Note we aren't actually copying any of the contents of the segment, just figuring out the locations
203 * and sizes of the sub-segments within it. (After IPC-transmission, reassembly will involve copying, as
204 * the sub-segments shall be scattered in receiver's RAM.) */
205
206 // Finalize the original segment (now sub-seg 1).
207 auto last_subseg_ptr = static_cast<uint8_t*>(buf.data());
208 auto last_subseg_sz = static_cast<size_t>(seg_and_hdr_sz_cap - hdr_sz_or_0);
209 target_blobs->back() = Blob_const{last_subseg_ptr, last_subseg_sz};
210 const size_t seg_minus_subseg1_sz = seg_sz - last_subseg_sz;
211 // seg_minus_subseg1_sz = remaining # of bytes from original segment (all but sub-seg 1).
212
213 // Bullet two above: This many full sub-segments (possibly 0).
214 size_t n_full_cont_subsegs = seg_minus_subseg1_sz / seg_and_hdr_sz_cap;
215 size_t n_cont_subsegs = n_full_cont_subsegs;
216
217 // Bullet three above: Possibly 1 more sub-segment.
218 const size_t partial_cont_subseg_sz_or_0 = seg_minus_subseg1_sz % seg_and_hdr_sz_cap;
219 (partial_cont_subseg_sz_or_0 != 0) && (++n_cont_subsegs);
220
221 // Little additional optimization to try minimizing shifting/reallocs due to the "unexpected" split-related segs.
222 target_blobs->reserve(n_blobs_with_subsegs += n_cont_subsegs);
223
224 // Okay, it's planned-out/accounted; update the actual out-args target_blobs and split_segs.
225
226 for (; n_full_cont_subsegs != 0; --n_full_cont_subsegs)
227 {
228 last_subseg_ptr += last_subseg_sz;
229 last_subseg_sz = seg_and_hdr_sz_cap;
230 target_blobs->emplace_back(last_subseg_ptr, last_subseg_sz);
231 }
232
233 if (partial_cont_subseg_sz_or_0 != 0)
234 {
235 last_subseg_ptr += last_subseg_sz;
236 target_blobs->emplace_back(last_subseg_ptr, partial_cont_subseg_sz_or_0);
237 }
238
239 split_segs->emplace_back(Split_segment{ target_blobs->size() - n_cont_subsegs - 1,
240 n_cont_subsegs });
241 FLOW_LOG_TRACE("Heap_fixed_builder [" << *this << "]: "
242 "Serialization segment [" << idx << "] (0-based, of [" << n_blobs << "], 1-based): "
243 "Heap buffer @[" << buf.data() << "] "
244 "sized [" << seg_sz << "] plus frame header [" << hdr_sz_or_0 << "]: exceeded limit "
245 "[" << seg_and_hdr_sz_cap << "]. Seg-split requested; therefore split into "
246 "[" << (n_cont_subsegs + 1) << "] sub-segments sized "
247 "init[" << (seg_and_hdr_sz_cap - hdr_sz_or_0) << "], N x full[" << seg_and_hdr_sz_cap << "], "
248 "partial[" << partial_cont_subseg_sz_or_0 << "].");
249 } // if ((seg_sz + hdr_sz_or_0) > seg_and_hdr_sz_cap)
250 // else if ((seg_sz + hdr_sz_or_0) <= seg_and_hdr_sz_cap) { Segment need not be split. }
251 } // for (blob : blob_ptrs)
252 // Got here: no error. Done and done.
253 err_code->clear();
254} // Heap_fixed_builder::emit_serialization()
255
257{
258 assert(m_engine && "Are you operating on a moved-from `*this`?");
259
260 return m_engine->n_segments();
261}
262
263std::ostream& operator<<(std::ostream& os, const Heap_fixed_builder& val)
264{
265 return os << '@' << &val;
266}
267
268// Heap_reader implementations.
269
271 flow::log::Log_context(config.m_logger_ptr, Log_component::S_TRANSPORT),
272 m_stats(config.m_stats), // Rcv-stats target chosen by config (typically by factory); null = no stats.
273 m_alloc_sz(0),
274 m_used_sz(0)
275{
276 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: Heap reader started: "
277 "expecting [" << config.m_n_segments_est << "] segments (0 if unknown).");
279}
280
282{
283 using flow::util::stat::fetch_sub;
284
285 if ((!m_stats) || (!m_engine)) // I.e., if (nothing to do except log):
286 {
287 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: Reader being destroyed including: "
288 "serialization containing [" << m_serialization_segments.size() << "] segments; "
289 << (m_engine ? "no stats kept." : "no deserialization yet attempted."));
290 /* If !m_engine: deserialization() never succeeded; nothing was incremented; nothing to decrement.
291 * If !m_stats: stats not kept at all. Either way, skip the decrements. */
292 return;
293 }
294 // else
295
296 // This *might* be perf-pricey, to some extent, so definitely be careful with the log level.
297 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: Reader being destroyed including: "
298 "serialization containing [" << m_serialization_segments.size() << "] segments; "
299 "last deserialization reports [" << (m_engine->sizeInWords() * sizeof(::capnp::word)) << "] "
300 "input heap space total.");
301
302 /* Decrement gauges that deserialization() incremented. Underflow assertions ride on the RMW's
303 * pre-change return value. */
304#ifndef NDEBUG
305 const auto prev_msgs_outstanding =
306#endif
307 fetch_sub(&m_stats->m_msgs_outstanding, 1);
308 assert((prev_msgs_outstanding >= 1)
309 && "Bug: rcv-stats msgs_outstanding gauge underflow at Heap_reader dtor.");
310
311#ifndef NDEBUG
312 const auto prev_alloc_outstanding_sz =
313#endif
315 assert((prev_alloc_outstanding_sz >= m_alloc_sz)
316 && "Bug: rcv-stats alloc-bytes_outstanding gauge underflow at Heap_reader dtor.");
317
318#ifndef NDEBUG
319 const auto prev_used_outstanding_sz =
320#endif
322 assert((prev_used_outstanding_sz >= m_used_sz)
323 && "Bug: rcv-stats used-bytes_outstanding gauge underflow at Heap_reader dtor.");
324} // Heap_reader::~Heap_reader()
325
327{
328 using flow::util::Blob;
329 using Ptr = const void*;
330
331 static_assert(std::is_nothrow_move_constructible_v<Segment_blob_in>,
332 "Segment_blob_in (as of this writing Basic_blob) move-ct must be noexcept for vector to move them "
333 "instead of copying on realloc; this is critical not just for performance, but because callers "
334 "may routinely rely on a given segment not moving in memory across add_serialization_segment()s.");
335
336 FLOW_LOG_TRACE("Heap_reader [" << *this << "]: "
337 "Serialization segment [" << m_serialization_segments.size() << "] (0-based) added/owned: "
338 "Heap buffer @[" << static_cast<Ptr>(blob.const_data()) << "] "
339 "max-sized [" << blob.capacity() << "], "
340 "actual serialization sized [" << blob.size() << "].");
341
342 /* Out of paranoia ensure seg1's memory never moves; the static_assert() above should ensure it already but still....
343 * (None of the others should move either, but we'll just check it for that particular one.) */
344#ifndef NDEBUG
345 Ptr seg1_ptr = nullptr;
347 || (seg1_ptr = static_cast<Ptr>(m_serialization_segments.front().const_data()));
348#endif
349
350 m_serialization_segments.emplace_back(std::move(blob));
351 assert(((!seg1_ptr) || (seg1_ptr == static_cast<Ptr>(m_serialization_segments.front().const_data())))
352 && "Bug: vector realloc moved a segment blob's buffer, presumably via unintended Basic_blob copy; "
353 "make sure Basic_blob/Segment_blob_in is coded properly for by-value storage in containers.");
354}
355
356std::ostream& operator<<(std::ostream& os, const Heap_reader& val)
357{
358 return os << '@' << &val;
359}
360
361} // namespace ipc::transport::struc
362
363namespace capnp
364{
365
366std::ostream& operator<<(std::ostream& os, const Text::Reader& val)
367{
368 return os << val.cStr();
369}
370
371} // namespace capnp
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()
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.
Implements Struct_reader concept by straightforwardly interpreting a serialization by Heap_fixed_buil...
Heap_reader(const Config &config)
Implements concept API.
uint64_t m_alloc_sz
Cache: sum of m_serialization_segments[].capacity() (post-reassembly-if-needed inside deserialization...
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...
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...
void add_serialization_segment(Segment_blob_in &&blob)
Implements concept API.
Small group of miscellaneous utilities to ease work with capnp (Cap'n Proto), joining its capnp names...
@ S_INTERNAL_ERROR_SERIALIZE_LEAF_TOO_BIG
Structured message serialization (e.g., when sending over channel): A user-mutated datum (list/blob/s...
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
std::vector< flow::util::Blob_sans_log_context * > Segment_ptrs
Sequence of 1+ segment descriptions, each representing a live writable segment (of a capnp serializat...
Definition: struc_fwd.hpp:160
const Null_session NULL_SESSION
The only necessary value of empty-type Null_session.
std::ostream & operator<<(std::ostream &os, const Heap_fixed_builder &val)
Prints string representation of the given Heap_fixed_builder to the given ostream.
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
std::ostream & operator<<(std::ostream &os, const Generic_msg_batch_in< Msg_resource_t, NO_HNDLS > &val)
Prints string representation of the given Generic_msg_batch_in to the given ostream.
Definition: batch.hpp:431
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
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
Implements Struct_builder::Config sub-concept.
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.
size_t m_frame_prefix_sz
Size of header to precede seg 1 in the first buffer allocated by the Heap_fixed_builder.
Implements Struct_reader::Config sub-concept.
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...
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_msgs_outstanding
Gauge: Currently-alive Struct_reader (usually <=> Msg_in) instances.
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...