Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
native_socket_stream_impl_rcv.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
24#include "ipc/util/util_fwd.hpp"
25#include <flow/error/error.hpp>
26#include <flow/util/util_fwd.hpp>
27#include <cassert>
28#include <utility>
29
31{
32
33// Native_socket_stream_impl template implementations (::rcv_*() and receive-API methods only).
34
35template<typename Msg_resource>
37 bool assume_would_block,
38 Error_code* sync_err_code,
39 flow::async::Task_asio_err&& on_done_func)
40{
41 return async_receive_batch_impl<Native_handle_batch_in<Msg_resource>, false>
42 (batch, assume_would_block, sync_err_code, std::move(on_done_func));
43}
44
45template<typename Msg_resource>
47 bool assume_would_block,
48 Error_code* sync_err_code,
49 flow::async::Task_asio_err&& on_done_func)
50{
51 return async_receive_batch_impl<Blob_batch_in<Msg_resource>, true>
52 (batch, assume_would_block, sync_err_code, std::move(on_done_func));
53}
54
55template<typename Batch, bool NO_HNDLS>
57 bool assume_would_block,
58 Error_code* err_code,
59 flow::async::Task_asio_err&& on_done_func)
60{
61 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool,
62 (async_receive_batch_impl<Batch, NO_HNDLS>),
63 batch, assume_would_block, _1, std::move(on_done_func));
64 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
65 auto& sync_err_code = *err_code;
66
67 assert(!on_done_func.empty());
68
69 // Do these checks one time, similarly to the user-facing single-message async_receive_*().
70 if ((!op_started<Op::S_RCV>("async_receive_batch_impl()"))
71 || (!state_peer("async_receive_batch_impl()")))
72 {
73 return false;
74 }
75 // else
77 {
78 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Async-receive-batch for in-batch [" << *batch << "] "
79 "requested, but a preceding request is still in progress; the message has not arrived yet. "
80 "Likely a user error, but who are we to judge? Ignoring.");
81 return false;
82 }
83 // else
85 {
86 FLOW_LOG_WARNING("Socket stream [" << *this << "]: "
87 "User async-receive-batch for in-batch [" << *batch << "] requested: "
88 "Error already encountered earlier. Emitting via sync-args. Note this is not necessarily "
89 "the user acting oddly; in batch-receive case it is normal to detect an error like "
90 "graceful-close just after 1+ user in-messages -- in which case we cache the error but "
91 "emit the message(s) plus success; so we might be emitting the cached error now.");
92
93 sync_err_code = m_rcv_pending_err_code;
94 return true;
95 }
96 // else
97
98 if (!batch->initialized())
99 {
100 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Async-receive-batch for in-batch [" << *batch << "] "
101 "requested, but batch object is not initialized() (not all slots have been "
102 "prepare_target_payload()ed); emitting INVALID_ARGUMENT.");
103 sync_err_code = error::Code::S_INVALID_ARGUMENT;
104 return true;
105 }
106 // else
107 if (batch->full())
108 {
109 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Async-receive-batch for in-batch [" << *batch << "] "
110 "requested, but batch object is already full; emitting INVALID_ARGUMENT.");
111 sync_err_code = error::Code::S_INVALID_ARGUMENT;
112 return true;
113 }
114 // else
115
118 {
119 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User async-receive-batch (natively) request "
120 "for in-batch [" << *batch << "]; native-handles allowed? = [" << (!NO_HNDLS) << "]; "
121 "assume initial would-block? = [" << assume_would_block << "].");
122
123 m_rcv_user_batch_request.emplace();
124 m_rcv_user_batch_request->m_no_hndls = NO_HNDLS;
125 m_rcv_user_batch_request->m_on_done_func = std::move(on_done_func);
126
127 rcv_read_batch_from_pkt_stream<Batch>(batch, assume_would_block, &sync_err_code);
128
129 if (sync_err_code != error::Code::S_SYNC_IO_WOULD_BLOCK) // Would-block continues op; success/other error ends it.
130 {
131 FLOW_LOG_TRACE("Async-request for in-batch [" << *batch << "] completed synchronously (result "
132 "[" << sync_err_code << "] [" << sync_err_code.message() << "]); emitting synchronously and "
133 "disregarding handler.");
135 }
136 // else { Other stuff logged enough. }
137 }
138 else // if constexpr(!(USE_OS_DGRAM_SUPPORT && USE_OS_DGRAM_BATCH_SUPPORT))
139 {
140 async_receive_batch_emulation<NO_HNDLS>(get_logger(), batch, assume_would_block,
141 &sync_err_code, std::move(on_done_func),
142 [this](auto&&... args)
143 {
144 if constexpr(NO_HNDLS)
145 {
146 async_receive_core(nullptr, std::forward<decltype(args)>(args)...);
147 }
148 else
149 {
150 async_receive_core(std::forward<decltype(args)>(args)...);
151 }
152 });
153 } // else // if constexpr(!(USE_OS_DGRAM_SUPPORT && USE_OS_DGRAM_BATCH_SUPPORT))
154
155 /* See log_stats() doc header for basic background behind the logic here.
156 * (Per the "subtlety" in said doc header which we should avoid: on_done_func() won't be called anywhere above;
157 * that would be later (if ever) and only if we hit would-block now.) */
158 if (m_rcv_pending_err_code) // Note we would've returned already had it been already truthy at the start.
159 {
160 rcv_log_stats("async_receive_batch_impl(): while sync-processing rcv-pipe hosed");
161 }
162
163 return true;
164} // Native_socket_stream_impl::async_receive_batch_impl()
165
166template<typename Batch>
168 bool assume_would_block,
169 Error_code* sync_err_code)
170{
171 using util::Task;
172 using util::Blob_mutable;
173 using flow::util::Lock_guard;
174
177 && "@todo Really this function should be made to not compile in this case, and this then should be "
178 "static_assert() rather than run-time.");
179 assert(!m_rcv_pending_err_code);
180
181 /* The below might look surprisingly brief and relatively simple given all the many subtleties
182 * in reading up-to-*batch-size messages essentially in one fell swoop (plus all the auto-pings and graceful-closes
183 * and ... the user has no interest in). That's really because Native_socket_stream_msg_batch_in::nb_read() does
184 * all of that for us! Well, really, it's more that that whole class is a part of our impl -- we aren't really
185 * separable -- while also giving the user a standard interface for loading target resources (buffers and such)
186 * and checking the results (including batch->n_used(), the # of messages received).
187 *
188 * So get right to it, namely batch->nb_read(..., m_peer_socket.get(), ...) which will obtain any messages,
189 * report any error including graceful-close, mark not-idleness if appropriate, even perform m_protocol_negotiator
190 * negotiation if needed -- at least! We just need to do the usual thing of locking the m_peer_socket-protecting
191 * mutex and nullify that guy (details below obv) on fatal error. */
192
193 // m_*peer_socket = (the only) shared data between our- and opposite-direction code. Must lock (see class docs).
194 {
195 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
196
197 if (m_peer_socket)
198 {
199 if (assume_would_block)
200 {
201 /* The following is identical to what would happen below if !assume_would_block, and ->nb_read() returned
202 * would-block. Note: It's easy enough, and good for correctness/maintainability, to instead only
203 * check assume_would_block nearer the ->nb_read() and "simulate" its would-block by rigging
204 * n_rcvd_or_zero, not_idle_on_would_block, etc. We choose to forego that approach in favor of little perf
205 * gains -- as assume_would_block=true exists for that reason; granted skipping the sys-call is easily the
206 * biggest gain, but let's just go all the way. @todo Reconsider.
207 *
208 * Comments light; see the assume_would_block=false clause below for comments, particularly w/r/t the
209 * m_rcv_ev_wait_func() invocation. */
210 FLOW_LOG_TRACE("Got nothing but would-block (pre-assumed) for in-batch [" << *batch << "]. "
211 "Awaiting readability.");
212
213 m_rcv_ev_wait_func(&m_ev_wait_hndl_peer_socket, false, boost::make_shared<Task>([this, batch]()
214 {
215 rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error<Batch>(batch);
216 }));
217 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
218 } // if (assume_would_block)
219 else // if (!assume_would_block)
220 {
221 bool not_idle_on_would_block;
222 auto n_rcvd_or_zero = batch->n_used(); // This is subtracted just below.
223#ifndef NDEBUG
224 const bool ok =
225#endif
227 .nb_read(get_logger(),
229 &not_idle_on_would_block, &m_rcv_pending_err_code, 0, &m_rcv_stats);
230 // That should have TRACE-logged (at least) much stuff, so we won't (it's our function).
231 assert(ok && "By contract, do not pass us an already-full batch object; we should have already emitted "
232 "INVALID_ARGUMENT without calling this.");
233 n_rcvd_or_zero = batch->n_used() - n_rcvd_or_zero;
234
236 && "By contract, do not pass us an un-initialized() batch object; we should have already emitted "
237 "INVALID_ARGUMENT without calling this.");
238 /* Caution! batch->nb_read() has unique (versus single-message-transmitting APIs) semantics:
239 * - would_block: n_rcvd_or_zero = 0 (no messages received). That's normal.
240 * - <other serious errors, including our own graceful-close S_RECEIVES_FINISHED_CANNOT_RECEIVE and
241 * low-level graceful-close `eof`>:
242 * - Possible: n_rcvd_or_zero = 0 (no messages received, only error [illegal dgram, etc.] or
243 * "error" namely one of 2 graceful-close types). That's normal.
244 * - Possible: n_rcvd_or_zero > 0 (1+ messages received, then "error," as of this writing only
245 * one of 2 graceful-close types possible).
246 * That's exotic! I.e., this can't happen with aforementioned typical APIs... but it can happen
247 * for us. Actually it can *totally* happen for us!
248 * - Reminder: Our contract says that *we* will never emit 1+ messages *and* an error.
249 * To achieve this, we below cache the error (as usual) in m_rcv_pending_err_code... but
250 * we emit success *this* time. Next time, though, they'll get the cached error. */
251
252 if (m_rcv_pending_err_code == boost::asio::error::would_block)
253 {
254 assert(n_rcvd_or_zero == 0);
255
256 FLOW_LOG_TRACE("Got nothing but would-block for in-batch [" << *batch << "]. Awaiting readability.");
257
258 /* Subtlety: We pass `batch` through a capture to rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error()
259 * which (on readability later) passes it to ourselves -- rcv_read_batch_from_pkt_stream() --
260 * to try all this again. We'd *like* to just shove it in m_rcv_user_batch_request instead, but
261 * we can't; see struct Rcv_user_batch_request doc header which explains how/why + suggests possible stylistic
262 * alternative(s). (Spoiler alert: basic reason is, `batch` has a template-parameterized type.) */
264 false, // Wait for read.
265 // Once readable do this:
266 boost::make_shared<Task>([this, batch]()
267 {
268 rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error<Batch>(batch);
269 }));
270
271 m_rcv_pending_err_code.clear(); // m_rcv_pending_err_code is falsy; n_rcvd_or_zero == 0; cool.
272 // *We*, however, emit our advertised would-block thing.
273 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
274
275 /* Subtlety possible with would-block: though we got 0 user messages, we may well have gotten auto-ping(s);
276 * at least that's what it would be as of this writing; but in any case batch->nb_read() reports a thing
277 * we can use for this specific case. */
278 if (not_idle_on_would_block)
279 {
280 rcv_not_idle(); // Register activity <= got at least 1 in-message of any kind, no (fatal) error.
281 }
282 }
283 else // if (m_rcv_pending_err_code != would_block)
284 {
286 {
287 /* @todo There is some corner-case inconsistency between the behavior of single-message receive
288 * versus us (batch-receive). Ideally these would be brought in-line with each other. Namely the
289 * difference is in some cases we nullify/hose m_peer_socket -- essentially hosing *this, so
290 * out-APIs don't work subsequently either -- while they leave it alone, so out-APIs may work fine
291 * still (allowing for send*() and *end_sending()). Details:
292 * - System errors (originating in native APIs like `::recv*()`, delivered to us through boost.asio
293 * or directly): Mutually consistent (hoses out-pipe too).
294 * - Native `eof` graceful-close: Mutually consistent (hoses out-pipe too).
295 * - RECEIVES_FINISHED_CANNOT_RECEIVE graceful-close: Mutually consistent (does not hose out-pipe).
296 * - EXCEEDS_USER_STORAGE: Mutually consistent (does not hose out-pipe).
297 * - Protocol errors like LOW_LVL_INTERNAL_PROTOCOL_INVALID_HEADER, BLOB_RECEIVER_GOT_NON_BLOB:
298 * - us (batched): hoses out-pipe too;
299 * - them (single-message): does not hose out-pipe.
300 *
301 * Pointers for the doer of to-do:
302 * - It is not urgent. As can be seen above, the discrepancy is only for the protocol-errors
303 * category. INVALID_HEADER in particular means a bug by us; GOT_NON_BLOB means misuse
304 * by user... or a bug by us. So the precondition is unlikely, and the effect of the
305 * inconsistency -- whether opposing-direction pipe remains usable in these conditions -- is
306 * pretty obscure (most users would eliminate the problem rather than worry about the other pipe).
307 * - It would still be nice, for cleanliness (and to delete this comment).
308 * - Suggest changing them (single-message rcv) to be like us (batch rcv) and not vice versa.
309 * It's easier (they already enumerate all the possible errors in rcv_on_dgram(), so just change
310 * what happens; whereas we currently let .nb_read() deal with them and handle them all the same).
311 * It's also arguably more consistent with other errors; essentially that true-blue unexpected
312 * scenario => hose it all; only allow *this to remain useful under known normal-operation "errors,"
313 * namely EXCEEDS_USER_STORAGE and RECEIVES_FINISHED_CANNOT_RECEIVE.
314 * - On the other hand: GOT_NON_BLOB is at least potentially a user error (<= we used wrong
315 * async_receive_*() API: cannot accept a Native_handle); and EXCEEDS_USER_STORAGE is definitely
316 * one; so why treat them differently? So perhaps add that specific error GOT_NON_BLOB
317 * to the list (along with RECEIVES_FINISHED_CANNOT_RECEIVE, EXCEEDS_USER_STORAGE) of
318 * non-other-pipe-hosing errors (both for us and for them consistently).
319 * - Handle the preferred code path (Protocol_pkt_stream case): rcv_on_dgram().
320 * - Handle also the other code path (Protocol_byte_stream case):
321 * rcv_on_head_payload(), rcv_on_handle_finalized(), rcv_resume_incomplete_msg_processing(). */
322
325 {
326 /* True-blue error. Kill off *m_peer_socket (connection hosed). We could simply nullify it, which'd
327 * give it back to the system (it's a resource), but see m_peer_socket_hosed doc header for explanation as
328 * to why we cannot, and why instead we transfer it to "death row" until dtor executes, at which
329 * point ::close(m_peer_socket_hosed->native_handle()) will actually happen. */
330
331 assert((!m_peer_socket_hosed)
332 && "m_peer_socket_hosed must start as null and only become non-null once. Bug?");
334 assert((!m_peer_socket) && "Shocking unique_ptr misbehavior!");
335 }
336 /* else if (m_rcv_pending_err_code == ...EXCEEDS_USER_STORAGE or graceful-close from *end_sending())
337 * { m_rcv_pending_err_code is truthy; and to our user the *in*-direction pipe is likely hosed (or
338 * will be on next receive attempt) -- see just below for all that. However, we choose *not*
339 * to hose m_peer_socket, and therefore the *out*-direction pipe continues to operate if desired. }
340 *
341 * Why do we choose this? Answer: Firstly the concept does not specify how a hypothetical
342 * other-direction pipe would be affected; the _sender and _receiver concepts are separate and don't
343 * generally interact; we just happen to implement both. So we're free to do what we want, as long
344 * as these errors both (as explicitly required) hose the in-pipe. So why not also hose the out-pipe?
345 * Answer: Leaving it alive is just useful; it is closer to full-duplex which local-sockets support,
346 * so why not?
347 *
348 * In the case of RECEIVES_FINISHED_CANNOT_RECEIVE (graceful-close) it's stronger than that actually:
349 * hosing *this upon receiving graceful-close -- which results from opposing peer doing *end_sending() --
350 * makes it impossible for us to perform our own *end_sending(); it'll fail with
351 * LOW_LVL_TRANSPORT_HOSED_CANNOT_SEND. So it would mean graceful-closing can only be fully carried
352 * out in one direction, whichever one wins the race. Not fatal for most protocols but an unnecessary
353 * concession.
354 *
355 * Related: If we treat RECEIVES_FINISHED_CANNOT_RECEIVE this way, why not do the same for
356 * `eof`? Answer: It would not be wrong, and we really refuse to do it mostly to reduce code/associated
357 * entropy/testing surface; but even if we did it, it would not bring us anything. The reason is
358 * simply that the opposing entity is a Native_socket_stream_impl like us, and we simply lack a
359 * send-API that would trigger it; we don't do m_peer_socket->shutdown(); nor ->close(); and we
360 * destroy *m_peer_socket in destructor only, at which point the conversation is moot (opposing peer
361 * is fully gone, so we don't care about our out-pipe's hypothetical health). In short: a native
362 * `eof`-as-graceful-close -- while specifically supported by nb_read() under us -- is not part of
363 * our protocol, so might as well treat it as a fully-*this-hosing event, as with broken-pipe and
364 * others. */
365
366 // m_rcv_pending_err_code is truthy; n_rcvd_or_zero >= 0; now emit the proper thing per comment higher-up.
367 if (n_rcvd_or_zero == 0)
368 {
369 *sync_err_code = m_rcv_pending_err_code;
370 }
371 else
372 {
373 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User async-receive-batch request "
374 "for in-batch [" << *batch << "]; native-handles allowed? = "
375 "[" << (!m_rcv_user_batch_request->m_no_hndls) << "]: "
376 "Batch-read encountered error "
377 "[" << m_rcv_pending_err_code << "] [" << m_rcv_pending_err_code.message() << "] after "
378 "[" << n_rcvd_or_zero << "] user in-dgrams; as advertised will emit success + "
379 "the messages but cache the error to immediately emit to the next async-receive.");
380 sync_err_code->clear();
381
382 /* Should we rcv_not_idle() here? On one hand messages did arrive; on the other the pipe is hosed
383 * anyway. Going with "don't rock the boat"; if the timer does have a chance to fire, it'll just
384 * see m_rcv_pending_err_code and no-op. We won't add entropy by canceling/restarting it. Also it's
385 * similar to the mainstream error-and-0-messages case (we don't rcv_not_idle() there). */
386 }
387 }
388 else // if (!m_rcv_pending_err_code)
389 {
390 assert(n_rcvd_or_zero != 0);
391
392 // m_rcv_pending_err_code is falsy; n_rcvd_or_zero >= 1; cool.
393 sync_err_code->clear();
394 rcv_not_idle(); // Register activity <= got at least 1 in-message of any kind, no error.
395 }
396 } // if (m_rcv_pending_err_code != would_block)
397 } // else // if (!assume_would_block)
398 } // if (m_peer_socket)
399 else // if (!m_peer_socket)
400 {
401 FLOW_LOG_WARNING("Socket stream [" << *this << "]: "
402 "User async-receive-batch request for in-batch [" << *batch << "]: "
403 "was about to batch-read but discovered opposite-direction socket-hosing error; "
404 "emitting error.");
406 }
407 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
408} // Native_socket_stream_impl::rcv_read_batch_from_pkt_stream()
409
410template<typename Batch>
412{
415 && "@todo Really this function should be made to not compile in this case, and this then should be "
416 "static_assert() rather than run-time.");
417
419 {
420 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User's wait-for-readable finished (readable or error, "
421 "we do not know which yet); would resume processing depending on what we were doing before; "
422 "however an error was detected in the meantime (as of this writing: idle timeout). "
423 "Stopping read chain (batch); in-batch [" << *batch << "].");
425 && "If rcv-error emitted during low-level async-wait, we should have fed it to any pending async-receive.");
426 return;
427 }
428 // else
429
431
432 // Will potentially emit this (if and only if message-read completes due to this successful async-wait).
433 Error_code sync_err_code;
434
435 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User-performed wait-for-readable finished (readable or error, "
436 "we do not know which yet). Retrying to resume read chain (batch); in-batch [" << *batch << "].");
437
438 rcv_read_batch_from_pkt_stream<Batch>(batch, false, &sync_err_code);
439
440 if (sync_err_code == error::Code::S_SYNC_IO_WOULD_BLOCK)
441 {
442 // Another async-wait is pending now. We've logged enough. Live to fight another day.
443 return;
444 }
445 // else
446
447 /* See log_stats() doc header for basic background behind the logic here.
448 * Note we put this ahead of any handler-call to avoid reentrant hellishness. */
449 if (m_rcv_pending_err_code) // Note we would've returned already had it been already truthy at the start.
450 {
451 rcv_log_stats("rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error(): "
452 "while processing ev-ready rcv-pipe hosed");
453 }
454
455 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Async-op (batch) result for in-batch [" << *batch << "] "
456 "ready after successful async-wait. Executing handler now.");
457
458 // Prevent stepping on our own toes: move/clear it first / invoke handler second.
459 const auto on_done_func = std::move(m_rcv_user_batch_request->m_on_done_func);
461
462 on_done_func(sync_err_code);
463 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Handler completed for in-batch [" << *batch << "].");
464} // Native_socket_stream_impl::rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error()
465
466} // namespace ipc::transport::sync_io
static constexpr bool S_USE_OS_DGRAM_BATCH_SUPPORT
Whether the OS provides, and we shall use for sync_io::Native_socket_stream and derivative(s),...
static constexpr bool S_USE_OS_DGRAM_SUPPORT
Whether the OS provides, and we shall use for sync_io::Native_socket_stream and derivative(s),...
util::sync_io::Event_wait_func m_rcv_ev_wait_func
Function (set forever in start_receive_*_ops()) through which we invoke the outside event loop's asyn...
decltype(m_peer_socket) m_peer_socket_hosed
Null to start, this takes on the value from m_peer_socket when and only when m_peer_socket is nullifi...
std::optional< Rcv_user_request > m_rcv_user_request
Null if no single-message async-receive request is currently pending; else describes the arguments to...
void rcv_on_ev_peer_socket_pkt_stream_batch_readable_or_error(Batch *batch)
Completion handler, from outside event loop via sync_io pattern, for the async-wait initiated by the ...
bool async_receive_blob_batch(Blob_batch_in< Msg_resource > *batch, bool assume_would_block, Error_code *sync_err_code, flow::async::Task_asio_err &&on_done_func)
See Native_socket_stream counterpart.
void rcv_read_batch_from_pkt_stream(Batch *batch, bool assume_would_block, Error_code *sync_err_code)
Conceptually similar to rcv_read_msg_from_pkt_stream() but tries to read not at-most-1 in-message but...
util::sync_io::Asio_waitable_native_handle m_ev_wait_hndl_peer_socket
Descriptor waitable by outside event loop async-waits – storing the same Native_handle as (and thus b...
std::conditional_t< Native_socket_stream_cfg::S_USE_OS_DGRAM_SUPPORT &&Native_socket_stream_cfg::S_USE_OS_DGRAM_BATCH_SUPPORT, Native_socket_stream_msg_batch_in< Msg_resource >, Generic_msg_batch_in< Msg_resource, true > > Blob_batch_in
See Native_socket_stream counterpart / sync_io::Blob_receiver::Blob_batch_in.
boost::movelib::unique_ptr< asio_local_stream_socket::Peer_socket< Native_socket_stream_cfg::Protocol > > m_peer_socket
The peer Unix domain socket; or null pointer if we've detected the connection has become hosed and he...
bool state_peer(util::String_view context) const
Checks whether m_state has reached State::S_PEER; if so returns true; if not logs WARNING and returns...
flow::util::Mutex_non_recursive m_peer_socket_mutex
Protects m_peer_socket and its bros m_ev_wait_hndl_peer_socket and m_peer_socket_hosed.
std::optional< Rcv_user_batch_request > m_rcv_user_batch_request
Null if no native async-batch-receive request is currently pending; else describes the arguments to t...
Protocol_negotiator m_protocol_negotiator
Handles the protocol negotiation at the start of the pipe.
void async_receive_core(Native_handle *target_hndl_or_null, bool assume_would_block, const util::Blob_mutable &target_meta_blob, Error_code *sync_err_code, size_t *sync_sz, flow::async::Task_asio_err_sz &&on_done_func_or_none)
Core of either async_receive_impl() (if user invoked single-message receiving directly) or the functi...
Error_code m_rcv_pending_err_code
The first and only connection-hosing error condition detected when attempting to low-level-read on m_...
void rcv_not_idle()
No-ops if idle_timer_run() is not engaged; otherwise reacts to non-idleness of the in-pipe by resched...
bool async_receive_batch_impl(Batch *batch, bool assume_would_block, Error_code *sync_err_code, flow::async::Task_asio_err &&on_done_func)
Body of both async_receive_native_handle_batch() and async_receive_blob_batch().
void rcv_log_stats(util::String_view context) const
INFO-logs current rcv-half stats: as log_stats() but callable from receive-op context (see "Which hal...
bool async_receive_native_handle_batch(Native_handle_batch_in< Msg_resource > *batch, bool assume_would_block, Error_code *sync_err_code, flow::async::Task_asio_err &&on_done_func)
See Native_socket_stream counterpart.
std::conditional_t< Native_socket_stream_cfg::S_USE_OS_DGRAM_SUPPORT &&Native_socket_stream_cfg::S_USE_OS_DGRAM_BATCH_SUPPORT, Native_socket_stream_msg_batch_in< Msg_resource >, Generic_msg_batch_in< Msg_resource, false > > Native_handle_batch_in
See Native_socket_stream counterpart.
stat::Blob_rcv_stats m_rcv_stats
Incoming-direction (receive-pipe) stats.
@ S_LOW_LVL_TRANSPORT_HOSED_CANNOT_RECEIVE
Unable to receive incoming traffic: an earlier-reported, or at least logged, system error had hosed t...
@ 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.
@ S_INVALID_ARGUMENT
User called an API with 1 or more arguments against the API spec.
@ S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE
User protocol-code mismatch: local user-provided storage cannot fit entire message received from oppo...
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::transport.
flow::async::Task Task
Short-hand for polymorphic function (a-la std::function<>) that takes no arguments and returns nothin...
Definition: util_fwd.hpp:119
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
This is the friend facade of Native_socket_stream_msg_batch_in that exposes private APIs hidden from ...
bool nb_read(Args &&... args)
Forwards to same-named (private) privileged-API of Base.