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
23#include <flow/log/log.hpp>
24#include <cassert>
25#include <utility>
26
28{
29
30// Template implementations.
31
32template<bool NO_HNDLS, typename Batch, typename Task_err, typename Async_rcv_impl_func>
33void async_receive_batch_emulation(flow::log::Logger* logger_ptr,
34 Batch* batch, bool assume_would_block, Error_code* sync_err_code,
35 Task_err&& on_done_func,
36 Async_rcv_impl_func&& async_rcv_impl_func)
37{
38 assert(sync_err_code);
39
40 /* We are emulating a batch-receive by expressing it in terms of, mainly, the ability to do a single-target "classic"
41 * receive op (plus some other little needed hooks). Conceptually one can think of a batch-receive as similar to
42 * a single-target receive. To recap the latter:
43 *
44 * - We try to get a message immediately (synchronously); and
45 * - we might succeed; or else
46 * - we might get would-block, effect a sync_io-pattern async-wait on certain FD(s), then try again
47 * again once informed via sync_io pattern; rinse/repeat until indeed we do succeed in getting that 1 message.
48 * (To be clear-ish for this recap -- the stuff about async-waiting and rinse/repeat, that's all handled
49 * by the single-target-receive black box. The bottom line is, either it yields the 1 message synchronously
50 * or later via on_done_func(). The async-wait machinery has all been set-up already via start_ops(), etc.)
51 * - In the first case async_rcv_impl_func() (which implements the single-target receive) reports it
52 * synchronously via *sync_sz.
53 * - In the second case it later reports it via on_done_func(..., size_t).
54 * - Either way an error might occur; then we report that via *sync_err_code or on_done_func(Error_code, ...)
55 * respectively.
56 *
57 * The difference when adding batching is, conceptually, not that big. It is simply that, upon reading that
58 * 1 in-message successfully (either path), we then read as many other messages as we synchronously can, until
59 * either would-block happens (possibly not the 1st time, depending on the earlier path to initial read success),
60 * or batch->full() (all slots have in-messages). So one can think of it as reading a message consisting of
61 * 1+ sub-messages. Oh and also there's no "overall msg count" separate analogous to sync_sz, as that is state
62 * recorded in *batch.
63 *
64 * There are various tactical caveats, generally commented-upon as needed closer to the fact; but the following
65 * are somewhat more strategic:
66 * - Would-block is not like other error conditions, even though API-wise it is reported similarly via
67 * *sync_err_code. It needs to be handled specially depending on the context.
68 * - If it occurs during that initial single-target receive (async_rcv_impl_func()) attempt, then we report it
69 * as normal via *sync_err_code but must remember that whatever Task_err_sz we give to async_rcv_impl_func()
70 * will still likely trigger later, continuing the overall operation.
71 * - If it occurs while trying to tack-on more messages having gotten the initial in-message, then it has a very
72 * different meaning. It means the end of the batch -- so at that point we do *not* report would-block
73 * via *sync_err_code or on_done_func(); rather we report a successful read of 1+ in-messages. This introduces
74 * tactical challenge: async_rcv_impl_func() yielding would-block, by contract, means it has initiated
75 * async-wait(s) and is trying to read more messages; but in fact we do *not* want that anymore; we want
76 * to be all-done. Hence async_rcv_impl_func() takes an *optional* done-handler; we *do* pass one in here
77 * but do *not* in the prev bullet.
78 * - Other error conditons, including (but not limited to; e.g. for Unix-domain-sockets there could also be
79 * ECONNRESET/etc.) graceful-close (RECEIVES_FINISHED_CANNOT_RECEIVE), might occur during the
80 * tacking-onto-initial-good-message phase. We advertise that -- much like Linux's ::recvmmsg() as of this
81 * writing -- in this case we:
82 * - report full success *this time*; but
83 * - any relevant call *next time* will yield the (deferred) truthy Error_code.
84 * Mechanically this is easy; any pipe-hosing error is by contract to be cached by async_rcv_impl_func(), so
85 * bullet point 2 is taken care of for us; and as for bullet point 1, we simply do not forward the error
86 * reported by async_rcv_impl_func() back to the user. */
87
88 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
89
90 if (!batch->initialized())
91 {
92 FLOW_LOG_WARNING("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = [" << NO_HNDLS << "]; "
93 "assume-would-block? = [" << assume_would_block << "]): "
94 "By contract emitting invalid-argument error, because the user- or library-supplied "
95 "batch object is not initialized() (not all slots have been prepare_target_payload()ed).");
96 *sync_err_code = error::Code::S_INVALID_ARGUMENT;
97 return; // Notable before reading anything off the pipe (as advertised). Pipe is not hosed by this.
98
99 /* Note on asymmetry: !initialized() => graceful INVALID_ARGUMENT here; but full()-at-entry is guarded
100 * only by an assert() (inside ->next_target_blob(); callers pre-check full() themselves). The user-facing
101 * contracts like Blob_receiver::async_receive_blob_batch() don't have this problem, so while somewhat odd
102 * in and of itself, we combine with other parts of the jigsaw puzzle to do the right thing in the end.
103 * @todo Revisit anyway sometime; maybe it can be rejiggered so as to not require such comments. */
104 }
105 // else
106
107 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = [" << NO_HNDLS << "]; "
108 "assume-would-block? = [" << assume_would_block << "]).");
109
110 size_t sync_init_sz = 0; // (Initialized just to avoid copying an indeterminate value in the error case below.)
111 if constexpr(NO_HNDLS)
112 {
113 async_rcv_impl_func(assume_would_block, batch->next_target_blob(), sync_err_code, &sync_init_sz,
114 [logger_ptr, batch, on_done_func = std::move(on_done_func),
115 async_rcv_impl_func] // No choice but to copy it. @todo Use member func ptrs instead?
116 (const Error_code& init_err_code, size_t init_sz) mutable
117 {
118 // Got 1 message, or (real) error, asynchronously.
119 Error_code err_code;
120
121 async_receive_batch_emulation_on_init_msg<NO_HNDLS>
122 (logger_ptr, batch, &err_code, async_rcv_impl_func, init_err_code, init_sz);
123
124 on_done_func(err_code);
125 });
126 }
127 else // if constexpr(!NO_HNDLS)
128 {
129 async_rcv_impl_func(batch->next_target_hndl(), assume_would_block, batch->next_target_blob(),
130 sync_err_code, &sync_init_sz,
131 [logger_ptr, batch, on_done_func = std::move(on_done_func), async_rcv_impl_func]
132 (const Error_code& init_err_code, size_t init_sz) mutable
133 { // Same exact thing. Code reuse doesn't seem worthwhile here.
134 Error_code err_code;
135 async_receive_batch_emulation_on_init_msg<NO_HNDLS>
136 (logger_ptr, batch, &err_code, async_rcv_impl_func, init_err_code, init_sz);
137 on_done_func(err_code);
138 });
139 }
140
141 if (*sync_err_code == error::Code::S_SYNC_IO_WOULD_BLOCK)
142 {
143 // We'll (likely) later continue from the handler we gave just above. Emit this synchronously and GTFO.
144 return;
145 }
146 // else: Got 1 message, or (real) error, synchronously.
147
148 async_receive_batch_emulation_on_init_msg<NO_HNDLS>
149 (logger_ptr, batch, sync_err_code, async_rcv_impl_func, *sync_err_code, sync_init_sz);
150} // async_receive_batch_emulation()
151
152} // namespace ipc::transport::sync_io
@ S_SYNC_IO_WOULD_BLOCK
A sync_io operation could not immediately complete; it will complete contingent on active async-wait ...
@ S_INVALID_ARGUMENT
User called an API with 1 or more arguments against the API spec.
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::transport.
void async_receive_batch_emulation(flow::log::Logger *logger_ptr, Batch *batch, bool assume_would_block, Error_code *sync_err_code, Task_err &&on_done_func, Async_rcv_impl_func &&async_rcv_impl_func)
Implements {Native_handle|Blob}_receiver::async_receive_*_batch() by emulating batch-receiving as a s...
Definition: batch.hpp:33
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313