Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
batch.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
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
21#include <vector>
23#include "ipc/util/util_fwd.hpp"
24#include <boost/noncopyable.hpp>
25#include <cassert>
26#include <ostream>
27#include <utility>
28
29namespace ipc::transport
30{
31
32// Types.
33
34/**
35 * Straightforward Msg_batch_in concept implementation (as taken by
36 * API `{Blob|Native_handle}_receiver::async_receive_*_batch()` concept) usable by any receiver
37 * engaging in *emulated batching*.
38 *
39 * @see Native_handle_receiver concept doc header for an explanation of receive-batching (including what
40 * emulated batching is; and how `Msg_resource_t` template-param relates to things).
41 *
42 * That is it features the following APIs as of this writing:
43 *
44 * - For user (including internal user struc::sync_io::Channel): `size_t max_msg_count`-taking ctor,
45 * initialized(), n_used(), full(), clear_used(), prepare_target_payload(), target_payload_size(),
46 * result_payload_blob(), result_payload_hndl() (for Native_handle_receiver only).
47 * - For sync_io::async_receive_batch_emulation() or equivalents: next_target_blob(), next_target_hndl() (for
48 * Native_handle_receiver only), emulate_result().
49 * - Subtlety: While result_payload_blob() and result_payload_hndl(), where arg `idx >= this->n_used()`,
50 * formally results in undefined behavior, Generic_msg_batch_in intentionally relaxes this restriction;
51 * values up to (not including) `max_msg_count` are allowed. The intention here is to allow a `*this`
52 * to be usable even without emulate_result(), or n_used() for that matter. (The
53 * first use-case for this was an internal scenario in struc::sync_io::Channel, when batch-size is set to 1
54 * at compile-time. Then a simplified code-path inside `Channel` stores a "batch" with `max_msg_count == 1`,
55 * then uses one-message-at-a-time receiving to target next_target_blob() and next_target_hndl() and always
56 * leaves `n_used() == 0`.) (A true-batch-handling code-path still relies on a batch being stored in that
57 * data structure but uses actual batched-receiving on it.)
58 *
59 * ### Why are `next_target_*()` and emulate_result() (and `idx >= n_used()`) public? ###
60 * Please note that we have intentionally made the receiver-engine-facing API publicly accessible. The idea is
61 * that conceivably the Flow-IPC user could make use of it, as it is fairly general. As of this writing, however,
62 * only other Flow-IPC internal code uses this sub-API; but that need not be the case. This also applies to
63 * the aforementioned case where `idx >= this->n_used()` to `result_payload_*(idx)`.
64 *
65 * ### Impl notes ###
66 * It's very straightforward. The main thing to keep in mind for context is that Generic_msg_batch_in provides
67 * value only in helping express batch-receives in terms of single-message-receives. There is little to nothing
68 * algorithmic going on; it just provides the expected batch API for the user (in terms of preparing slots and
69 * examining/consuming results) while letting receiver impls straightforwardly record single-message-receives.
70 *
71 * @tparam Msg_resource_t
72 * See Msg_batch_in concept API.
73 * @tparam NO_HNDLS
74 * See Msg_batch_in concept API.
75 */
76#ifdef IPC_DOXYGEN_ONLY // Mirror the transport_fwd.hpp fwd-declaration in the generated docs.
77
78template<typename Msg_resource_t, bool NO_HNDLS = true>
80
81#else
82
83template<typename Msg_resource_t, bool NO_HNDLS>
85
86#endif
87 : private boost::noncopyable
88{
89public:
90 // Types.
91
92 /// See Msg_batch_in concept API.
93 using Msg_resource = Msg_resource_t;
94
95 /// See Msg_batch_in concept API.
97
98 // Constants.
99
100 /// See Msg_batch_in concept API.
101 static constexpr bool S_NO_HNDLS = NO_HNDLS;
102
103 // Constructors/destructor.
104
105 /**
106 * See Msg_batch_in concept API.
107 *
108 * @param max_msg_count
109 * See above.
110 */
111 explicit Generic_msg_batch_in(size_t max_msg_count);
112
113#ifdef IPC_DOXYGEN_ONLY
114 /// See Msg_batch_in concept API.
116#endif
117
118 // Methods.
119
120 /**
121 * See Msg_batch_in concept API.
122 * @return See above.
123 */
124 bool initialized() const;
125
126 /**
127 * See Msg_batch_in concept API.
128 * @return See above.
129 */
130 size_t n_used() const;
131
132 /**
133 * See Msg_batch_in concept API.
134 * @return See above.
135 */
136 bool full() const;
137
138 /// See Msg_batch_in concept API.
139 void clear_used();
140
141 /**
142 * See Msg_batch_in concept API.
143 *
144 * @param target_blob
145 * See above.
146 * @param msg_resource
147 * See above.
148 * @param idx
149 * See above.
150 */
151 void prepare_target_payload(const Mutable_buffer_sequence& target_blob, Msg_resource&& msg_resource,
152 size_t idx = -1);
153
154 /**
155 * See Msg_batch_in concept API.
156 * @return See above.
157 */
158 size_t target_payload_size() const;
159
160 /**
161 * See Msg_batch_in concept API; plus additional behavior for arg `idx` (allowed to be `>= n_used()`).
162 *
163 * @param idx
164 * Which slot? This *may* `>= n_used()` for certain advanced use cases sans emulate_result();
165 * otherwise (informally) it should be `< n_used()` (but this is not checked). Be careful.
166 * See class doc header for brief background.
167 * @param msg_resource_ptr
168 * See above.
169 * @return See above.
170 */
171 size_t result_payload_blob(size_t idx, Msg_resource** msg_resource_ptr = nullptr);
172
173 /**
174 * See Msg_batch_in concept API; plus additional behavior for arg `idx` (allowed to be `>= n_used()`).
175 * Exists only if #S_NO_HNDLS is `false`.
176 *
177 * @param idx
178 * See result_payload_blob().
179 * @return See above.
180 */
181 Native_handle result_payload_hndl(size_t idx) const;
182
183 /**
184 * For use by receiver-engine (not receiver-user), makes it so that n_used() would return the given value. The
185 * precipitating use case for this overload was the need for sync_io::async_receive_batch_emulation() to "undo"
186 * successful receipt of 1+ messages upon encountering an exceptional error.
187 *
188 * @param new_n_used
189 * The value n_used() shall return.
190 */
191 void clear_used(size_t new_n_used);
192
193 /**
194 * For use by receiver-engine (not receiver-user), returns the location/size of the target memory area
195 * from the last prepare_target_payload() for slot with index `== this->n_used()`. full() must be `false`;
196 * else behavior undefined/assertion may trip.
197 *
198 * @return See above.
199 */
201
202 /**
203 * For use by receiver-engine (not receiver-user), returns pointer to the target `Native_handle`
204 * for slot with index `== this->n_used()`. full() must be `false`; else behavior undefined/assertion may trip.
205 * Exists only if #S_NO_HNDLS is `false`.
206 *
207 * @return See above.
208 */
210
211 /**
212 * For use by receiver-engine (not receiver-user), records the size of the successful receive at slot
213 * with index `== this->n_used()` and increments n_used(). Therefore it registers that the read into
214 * area next_target_blob() was successful and yielded `result_n_rcvd` bytes. At entry full() must be `false`;
215 * else behavior undefined/assertion may trip.
216 *
217 * @note To register a received Native_handle, simply assign to `*(next_target_hndl())` (before emulate_result()).
218 *
219 * @param result_n_rcvd
220 * How many bytes (it might be zero, depending) were successfully received.
221 */
222 void emulate_result(size_t result_n_rcvd);
223
224 /**
225 * Prints string representation to the given `ostream`.
226 *
227 * @param os
228 * Stream to which to write.
229 */
230 void to_ostream(std::ostream* os) const;
231
232private:
233 // Types.
234
235 /// Data stored per slot.
237 {
238 // Data.
239
240 /// Per-slot resource moved from user's object via `prepare_target_payload()`.
242
243 /// The target memory location for where to receive-to for this slot.
245
246 /**
247 * The target/result `Native_handle` to receive-to for this slot; null originally; null or not upon
248 * a receive op into the slot.
249 *
250 * Unused (left null) if #S_NO_HNDLS is true. (It is so small that it didn't seem worthwhile to code actually
251 * not having the member in memory in that case.)
252 */
254
255 /// The target/result containing the # of bytes successfully read into area #m_buf_seq for this slot.
257
258 // Constructors/destructor.
259
260 /**
261 * Ctor.
262 * @param buf_seq
263 * See #m_buf_seq.
264 * @param msg_resource
265 * Object to move-assign to #m_resource.
266 */
267 Mdt_per_payload(const Mutable_buffer_sequence& buf_seq, Msg_resource&& msg_resource);
268 }; // struct Mdt_per_payload
269
270 // Data.
271
272 /// The slots. `.capacity() == max_msg_count` (see ctor); `.size()` is same once initialized().
273 std::vector<Mdt_per_payload> m_batch;
274
275 /// See n_used().
276 size_t m_n_used;
277}; // class Generic_msg_batch_in
278
279// Template implementations.
280
281template<typename Msg_resource_t, bool NO_HNDLS>
283 (const Mutable_buffer_sequence& buf_seq, Msg_resource&& msg_resource) :
284 m_resource(std::move(msg_resource)),
285 m_buf_seq(buf_seq),
286 m_result_n_rcvd(0) // Not strictly necessary but to avoid entropy and [sanitizer] warnings....
287{
288 // Done.
289}
290
291template<typename Msg_resource_t, bool NO_HNDLS>
293 m_n_used(0)
294{
295 m_batch.reserve(max_msg_count);
296 // It's empty though. Let them set up each guy via prepare_target_payload(-1) x N times.
297 assert((m_batch.capacity() == max_msg_count) && "We use .capacity() to memorize `max_msg_count` for full(), etc.");
298}
299
300template<typename Msg_resource_t, bool NO_HNDLS>
302 (const util::Blob_mutable& target_blob, Msg_resource&& msg_resource, size_t idx)
303{
304 if (target_payload_size() != 0)
305 {
306 assert((target_payload_size() == target_blob.size())
307 && "Msg_batch_in concept requires the max message size be the same across all slots.");
308 }
309
310 if (idx == size_t(-1))
311 {
312 // Ensure .emplace_back() wouldn't increase .capacity().
313 assert((!initialized())
314 && "At this time the fully-initialized batch size shall be permanently set via ctor; cannot grow.");
315
316 m_batch.emplace_back(target_blob, std::move(msg_resource));
317 }
318 else if (idx < m_batch.size())
319 {
320 auto& mdt = m_batch[idx];
321 mdt.m_buf_seq = target_blob;
322 mdt.m_resource = std::move(msg_resource);
323 }
324 else // if (idx >= m_batch.size()) && not -1
325 {
326 assert(false && "prepare_target_payload(x) must either replace an existing slot x or add slot via `x = -1`.");
327 }
328}
329
330template<typename Msg_resource_t, bool NO_HNDLS>
332{
333 return m_batch.empty() ? 0
334 : m_batch.front().m_buf_seq.size();
335}
336
337template<typename Msg_resource_t, bool NO_HNDLS>
338size_t
340{
341 assert(idx < m_batch.size());
342 // Note: We intentionally allow `idx >= m_n_used` due to advanced use cases; see our doc header.
343
344 auto& mdt = m_batch[idx];
345 if (msg_resource_ptr)
346 {
347 *msg_resource_ptr = &mdt.m_resource;
348 }
349
350 return mdt.m_result_n_rcvd;
351}
352
353template<typename Msg_resource_t, bool NO_HNDLS>
355{
356 static_assert(!S_NO_HNDLS, "Do not invoke/instantiate this method if class tparam NO_HNDLS=true.");
357
358 assert(idx < m_batch.size());
359 // Note: We intentionally allow `idx >= m_n_used` due to advanced use cases; see our doc header.
360
361 return m_batch[idx].m_result_hndl_unless_no_hndls;
362}
363
364template<typename Msg_resource_t, bool NO_HNDLS>
366{
367 static_assert(!S_NO_HNDLS, "Do not invoke/instantiate this method if class tparam NO_HNDLS=true.");
368
369 assert(!full());
370 return &(m_batch[m_n_used].m_result_hndl_unless_no_hndls);
371}
372
373template<typename Msg_resource_t, bool NO_HNDLS>
375{
376 assert(!full());
377 return m_batch[m_n_used].m_buf_seq;
378}
379
380template<typename Msg_resource_t, bool NO_HNDLS>
382{
383 return m_batch.size() == m_batch.capacity();
384}
385
386template<typename Msg_resource_t, bool NO_HNDLS>
388{
389 return m_n_used;
390}
391
392template<typename Msg_resource_t, bool NO_HNDLS>
394{
395 return m_n_used == m_batch.size();
396}
397
398template<typename Msg_resource_t, bool NO_HNDLS>
400{
401 clear_used(0);
402}
403
404template<typename Msg_resource_t, bool NO_HNDLS>
406{
407 m_n_used = new_n_used;
408}
409
410template<typename Msg_resource_t, bool NO_HNDLS>
412{
413 assert(initialized() && "By contract must prepare_target_payload() to create all max_msg_count slots. "
414 "Can check initialized() to ensure that is the case before calling us.");
415 assert(!full());
416
417 m_batch[m_n_used].m_result_n_rcvd = result_n_rcvd;
418 ++m_n_used;
419}
420
421template<typename Msg_resource_t, bool NO_HNDLS>
423{
424 auto& os = *os_ptr;
425 os << "Gen[slots-total/rdy/rcvd [" << m_batch.size() << '/' << m_batch.capacity() << '/' << m_n_used << "] "
426 "blob-sz[" << target_payload_size() << "]]@" << this;
427 // We try to keep the various Msg_batch_in-ish variants to a similar output format for easy log searching.
428}
429
430template<typename Msg_resource_t, bool NO_HNDLS>
431std::ostream& operator<<(std::ostream& os,
433{
434 val.to_ostream(&os);
435 return os;
436}
437
438} // namespace ipc::transport
Straightforward Msg_batch_in concept implementation (as taken by API {Blob|Native_handle}_receiver::a...
Definition: batch.hpp:88
Msg_resource_t Msg_resource
See Msg_batch_in concept API.
Definition: batch.hpp:93
~Generic_msg_batch_in()
See Msg_batch_in concept API.
bool full() const
See Msg_batch_in concept API.
Definition: batch.hpp:393
util::Blob_mutable Mutable_buffer_sequence
See Msg_batch_in concept API.
Definition: batch.hpp:96
std::vector< Mdt_per_payload > m_batch
The slots. .capacity() == max_msg_count (see ctor); .size() is same once initialized().
Definition: batch.hpp:273
Native_handle * next_target_hndl()
For use by receiver-engine (not receiver-user), returns pointer to the target Native_handle for slot ...
Definition: batch.hpp:365
size_t m_n_used
See n_used().
Definition: batch.hpp:276
size_t target_payload_size() const
See Msg_batch_in concept API.
Definition: batch.hpp:331
void prepare_target_payload(const Mutable_buffer_sequence &target_blob, Msg_resource &&msg_resource, size_t idx=-1)
See Msg_batch_in concept API.
Definition: batch.hpp:302
static constexpr bool S_NO_HNDLS
See Msg_batch_in concept API.
Definition: batch.hpp:101
void clear_used()
See Msg_batch_in concept API.
Definition: batch.hpp:399
util::Blob_mutable next_target_blob()
For use by receiver-engine (not receiver-user), returns the location/size of the target memory area f...
Definition: batch.hpp:374
Generic_msg_batch_in(size_t max_msg_count)
See Msg_batch_in concept API.
Definition: batch.hpp:292
size_t n_used() const
See Msg_batch_in concept API.
Definition: batch.hpp:387
size_t result_payload_blob(size_t idx, Msg_resource **msg_resource_ptr=nullptr)
See Msg_batch_in concept API; plus additional behavior for arg idx (allowed to be >= n_used()).
Definition: batch.hpp:339
void to_ostream(std::ostream *os) const
Prints string representation to the given ostream.
Definition: batch.hpp:422
void emulate_result(size_t result_n_rcvd)
For use by receiver-engine (not receiver-user), records the size of the successful receive at slot wi...
Definition: batch.hpp:411
bool initialized() const
See Msg_batch_in concept API.
Definition: batch.hpp:381
Native_handle result_payload_hndl(size_t idx) const
See Msg_batch_in concept API; plus additional behavior for arg idx (allowed to be >= n_used()).
Definition: batch.hpp:354
Flow-IPC module providing transmission of structured messages and/or low-level blobs (and more) betwe...
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
Msg_resource m_resource
Per-slot resource moved from user's object via prepare_target_payload().
Definition: batch.hpp:241
Native_handle m_result_hndl_unless_no_hndls
The target/result Native_handle to receive-to for this slot; null originally; null or not upon a rece...
Definition: batch.hpp:253
Mutable_buffer_sequence m_buf_seq
The target memory location for where to receive-to for this slot.
Definition: batch.hpp:244
Mdt_per_payload(const Mutable_buffer_sequence &buf_seq, Msg_resource &&msg_resource)
Ctor.
Definition: batch.hpp:283
size_t m_result_n_rcvd
The target/result containing the # of bytes successfully read into area m_buf_seq for this slot.
Definition: batch.hpp:256
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.