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
26{
27
28// Template implementations.
29
30template<bool NO_HNDLS, typename Batch, typename Async_rcv_impl_func>
31void async_receive_batch_emulation_on_init_msg(flow::log::Logger* logger_ptr,
32 Batch* batch, Error_code* sync_err_code,
33 const Async_rcv_impl_func& async_rcv_impl_func,
34 const Error_code init_err_code, size_t init_sz)
35{
36 using flow::async::Task_asio_err_sz;
37
38 /* This is a continuation of async_receive_batch_emulation(), and it should be looked at together with that one,
39 * including the big comment therein. Really we are only a (helper) function, because we might be invoked
40 * synchronously from there, or asynchronously after a would-block and async-wait. */
41
42 if (init_err_code)
43 {
44 *sync_err_code = init_err_code; // Init read led to fatal error.
45 return;
46 }
47 // else: Got 1 message! So now read 0+ more messages until would-block or other error or all-slots-filled.
48
49 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_TRANSPORT);
50
51 /* Though first don't forget to "bank" that initial message. The meta-blob and (possibly) Native_handle are
52 * already written; so we just tell it the size of the message to record. It'll also increment batch->n_used().
53 * Note that the latter could make batch->full(). Also note that earlier we ensured the requirement that
54 * batch->initialized() -- notably, *before* reading anything off the pipe. */
55 const auto init_n_used = batch->n_used();
56 batch->emulate_result(init_sz);
57
58 if constexpr(NO_HNDLS)
59 {
60 // By the way *batch output includes key stuff like .n_used(), so things should be clear.
61 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = yes); "
62 "just recorded init-result into the last slot: n_rcvd [" << init_sz << "].");
63 }
64 else
65 {
66 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = no); "
67 "just recorded init-result into the last slot: "
68 "n_rcvd [" << init_sz << "]; hndl [" << batch->result_payload_hndl(batch->n_used() - 1) << "].");
69 }
70
71 sync_err_code->clear();
72
73 Error_code err_code;
74 while ((!err_code) && (!batch->full())) // First time err_code is falsy; but batch->full() may be true.
75 {
76 size_t sz;
77
78 // This time, there's no async continuation on would-block (hence {} for the on-done handler).
79 if constexpr(NO_HNDLS)
80 {
81 async_rcv_impl_func(false, batch->next_target_blob(), &err_code, &sz,
82 Task_asio_err_sz{});
83 }
84 else // if constexpr(!NO_HNDLS)
85 {
86 async_rcv_impl_func(batch->next_target_hndl(), false, batch->next_target_blob(), &err_code, &sz,
87 Task_asio_err_sz{});
88 }
89
90 if (err_code && (err_code != error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE)
91 && (err_code != boost::asio::error::eof)
93 {
94 /* It is an exceptional error which, by the combination of the async_receive_..._batch() concept requirements
95 * and our explicit promise to treat `boost::asio::error::eof` as the sole transport-level graceful-close
96 * error, means that we are obliged to emit the error immediately -- not ignore it (while async_rcv_impl_func()
97 * caches it for next async-receive if any) and simply emit success/the 1+ already-received-OK in-messages.
98 * In the concept doc this is called the *instant error* outcome.
99 *
100 * Mechanically for us that means two things:
101 * - Emit the truthy err_code via *sync_err_code (obviously).
102 * - As required when doing the latter, return batch->n_used() to its original value, thus "eating" the
103 * already received 1+ messages.
104 * - But! Let us not leak any handles that may have been previously banked. (This is promised
105 * formally: see async_receive_batch_emulation() doc header's *instant error* discussion; also
106 * Native_handle_receiver::async_receive_native_handle_batch() concept requires it of impls.) */
107
108 if constexpr(!NO_HNDLS)
109 {
110 for (auto idx = init_n_used; idx != batch->n_used(); ++idx)
111 {
112 /* If there's a handle in there, un-leak it.
113 * (Stored value in *batch is not touched; but it remains in [n_used(), ...) <=> meaningless.
114 * @todo For cleanliness/defensiveness it would be good to also nullify it; *batch lacks the required
115 * receiver-engine-facing API at the moment; could add it (see to-do on
116 * asio_local_stream_socket::Msg_batch_in::result_payload_hndl()).) */
117 batch->result_payload_hndl(idx).close();
118 }
119 }
120
121 batch->clear_used(init_n_used);
122 *sync_err_code = err_code;
123
124 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Due to non-would-block, non-graceful-close "
125 "error [" << err_code << "] [" << err_code.message() << "] emitting overall batch-receive "
126 "error having rewound the Msg_batch to its pre-op state.");
127 // Loop ends because err_code.
128 }
129 else if (!err_code)
130 {
131 // Again: increments batch->n_used(); may reach batch->full().
132 batch->emulate_result(sz);
133
134 if constexpr(NO_HNDLS)
135 {
136 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = yes); "
137 "just recorded tacked-on-result into the last slot: n_rcvd [" << sz << "].");
138 }
139 else
140 {
141 FLOW_LOG_TRACE("Msg_batch [" << *batch << "]: Emulating async-receive (no-hndls? = no); "
142 "just recorded tacked-on-result into the last slot: "
143 "n_rcvd [" << sz << "]; hndl [" << batch->result_payload_hndl(batch->n_used() - 1) << "].");
144 }
145 // Loop continues unless batch->full().
146 }
147 /* else // if (err_code is one of {graceful-close, native-graceful-close, would-block}):
148 *
149 * Do nothing: leave *sync_err_code at success; emit the 1+ in-messages from before. Loop ends because err_code.
150 * Now let's consider the possibilities and show this is proper behavior:
151 * - err_code is would-block: Not a "real" error, and since we have 1+ in-messages already, we certainly
152 * should not be emitting would-block. The 1+ in-messages have been emitted into *batch. All good.
153 * - err_code is one of the graceful-closes delineated by
154 * Native_handle_receiver::async_receive_native_handle_batch() and our contract as necessitating
155 * the *delayed error* outcome. In this outcome: async_rcv_impl_func() is to cache err_code, so that
156 * any further async-receive yields that error immediately (this is a requirement for our use; not up to us);
157 * while we are to emit success this time, emitting any 1+ in-messages that had been obtained to that point.
158 * Indeed: *sync_err_code is falsy, and the 1+ in-messages have been emitted into *batch. All good. */
159 } // while (!err_code && !batch->full())
160 // Reminder: err_code, even if truthy (very possible: would-block), is eaten.
161
162 /* That's it! To recap:
163 * - 1 (init) message is banked in *batch (batch->n_used() incremented 1x).
164 * - 0+ further messages are banked (batch->n_used() incremented 1x per msg).
165 * - If, while trying to get these, a (non-would-block) error was detected, it will be emitted next
166 * time user tries async-receive (but, per next bullet, not this time). It is the object's responsibility
167 * to save such deferred errors.
168 * - Emitting success *sync_err_code.
169 * - batch->full() may have been reached. If it has not, it is "implied would-block".
170 * If it has been reached, it is indeterminate; could be would-block, could be not.
171 * User can do what they wish (spoiler alert: on implied would-block, they shouldn't try a read right now;
172 * in the other case they should). */
173} // async_receive_batch_emulation_on_init_msg()
174
175} // 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_RECEIVES_FINISHED_CANNOT_RECEIVE
Will not receive message: opposing user sent graceful-close via API.
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::transport.
void async_receive_batch_emulation_on_init_msg(flow::log::Logger *logger_ptr, Batch *batch, Error_code *sync_err_code, const Async_rcv_impl_func &async_rcv_impl_func, const Error_code init_err_code, size_t init_sz)
Helper for async_receive_batch_emulation() that executes on that call's initial single-message async-...
Definition: batch.hpp:31
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313