Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
native_socket_stream_impl_rcv.cpp
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
21#include <boost/make_shared.hpp>
22#include <boost/array.hpp>
23#include <cassert>
24#include <cstdint>
25#include <utility>
26
28{
29
30// Native_socket_stream_impl implementations (::rcv_*() and receive-API methods only).
31
33{
34 return start_ops<Op::S_RCV>(std::move(ev_wait_func));
35 /* We are a public API impl; but we don't try any I/O; so m_rcv_pending_err_code cannot become truthy.
36 * So no need to check for that / log_stats() (see its doc header for background as to what we mean here). */
37}
38
40{
41 return start_receive_native_handle_ops(std::move(ev_wait_func));
42}
43
45 const util::Blob_mutable& target_meta_blob,
46 Error_code* sync_err_code, size_t* sync_sz,
47 flow::async::Task_asio_err_sz&& on_done_func)
48{
49 assert(!on_done_func.empty());
50
51 /* Subtlety: async_receive_blob() will pass target_hndl==nullptr to _impl(); but we do not allow this.
52 * When using us as a Native_handle_receiver they must be ready to receive a Native_handle, even if the other
53 * side chooses to not send one (then *target_hndl shall be set to equal Native_handle{}, a null handle).
54 *
55 * So now internally our code can tell whether this is from a Blob_receiver or a Native_handle_receiver role
56 * and emit error::Code::S_BLOB_RECEIVER_GOT_NON_BLOB if target_hndl==0, yet the other side sent an actual handle. */
57 assert(target_hndl && "Native_socket_stream::async_receive_native_handle() must take non-null Native_handle ptr.");
58
59 return async_receive_impl(target_hndl, target_meta_blob, sync_err_code, sync_sz,
60 std::move(on_done_func));
61}
62
64 Error_code* sync_err_code, size_t* sync_sz,
65 flow::async::Task_asio_err_sz&& on_done_func)
66{
67 assert(!on_done_func.empty());
68 return async_receive_impl(nullptr, target_blob, sync_err_code, sync_sz, std::move(on_done_func));
69}
70
72 const util::Blob_mutable& target_meta_blob,
73 Error_code* err_code, size_t* sync_sz,
74 flow::async::Task_asio_err_sz&& on_done_func)
75{
76 FLOW_ERROR_EXEC_AND_THROW_ON_ERROR(bool,
78 target_hndl_or_null, target_meta_blob, _1, sync_sz, std::move(on_done_func));
79 // ^-- Call ourselves and return if err_code is null. If got to present line, err_code is not null.
80 auto& sync_err_code = *err_code;
81
82 if ((!op_started<Op::S_RCV>("async_receive_impl()"))
83 || (!state_peer("async_receive_impl()")))
84 {
85 return false;
86 }
87 // else
88
89 // We comment liberally, but tactically, inline; but please read the strategy in the class doc header's impl section.
90
92 {
93 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Async-receive requested, but the preceding such request "
94 "is still in progress; the message has not arrived yet. "
95 "Likely a user error, but who are we to judge? Ignoring.");
96 return false;
97 }
98 // else
99
100 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User async-receive request for "
101 "possible native handle and meta-blob (located @ [" << target_meta_blob.data() << "] of "
102 "max size [" << target_meta_blob.size() << "]).");
103
105 {
106 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request for "
107 "possible native handle and meta-blob (located @ [" << target_meta_blob.data() << "] of "
108 "max size [" << target_meta_blob.size() << "]): Error already encountered earlier. Emitting "
109 "via sync-args.");
110
111 sync_err_code = m_rcv_pending_err_code;
112 *sync_sz = 0;
113 }
114 else
115 {
116 async_receive_core(target_hndl_or_null, false, target_meta_blob, &sync_err_code, sync_sz, std::move(on_done_func));
117 // ^-- on_done_func() won't be called in there; that would be later (if ever) and only if we hit would-block now.
118
119 // See log_stats() doc header for basic background behind the logic here.
120 if (m_rcv_pending_err_code) // Note we wouldn't be in this branch had it been already truthy at the start.
121 {
122 rcv_log_stats("async_receive_impl(): while sync-processing rcv-pipe hosed");
123 }
124 }
125
126 return true;
127} // Native_socket_stream_impl::async_receive_impl()
128
130 bool assume_would_block,
131 const util::Blob_mutable& target_meta_blob,
132 Error_code* sync_err_code, size_t* sync_sz,
133 flow::async::Task_asio_err_sz&& on_done_func_or_none)
134{
135 assert(sync_err_code);
136 // Might be redundant for some code paths, but avoiding the pre-clear is really not worth the maintenance-bug danger.
137 sync_err_code->clear();
138
139 /* Background can be found by following the comment on this concept constant (S_BLOB_UNDERFLOW_ALLOWED).
140 * In this context what is interesting for us, though, is that its value is `true`. Let's explore:
141 * If we operate with SOCK_SEQPACKET semantics then:
142 * - When we make the OS-read call that shall read into m_rcv_user_request->m_target_meta_blob, we do *not*
143 * know the length of what will be returned; but the OS will be able to detect if a particular in-dgram
144 * is too long, and our API(s) wrapping this will emit MESSAGE_SIZE_EXCEEDS_USER_STORAGE directly in
145 * asio_local_stream_socket-land (not here in *this). (It is detectable in Linux via the MSG_TRUNC out-flag.
146 * See nb_read_some_with_native_handle() doc header for details.)
147 * - So we rely on nb_read_some_with_native_handle(), etc., to throw the aforementioned civilized error
148 * as needed, upon the OS-call failing due to having to truncate an in-dgram.
149 *
150 * However if we operate with SOCK_STREAM (which is more typical/portable but has inferiorities in other ways):
151 * - When we make that OS-read call, we do know the length and read just that many bytes (we have the length
152 * from a preceding OS-read call that obtained the length frame-header).
153 * - So we can throw a civilized error right then, before attempting any OS-read call into the buffer; as
154 * by then we know the length in the in-message *and* the buffer's length m_target_meta_blob.size().
155 *
156 * That's the background, but the practical result is simply that we don't do any pre-check here;
157 * if an in-dgram or in-message is too long, we'll throw the appropriate error at that time, first detecting
158 * the situation via OS or ourselves respectively.
159 *
160 * Note that in some cases, as of this writing, higher layers (namely struc::Channel) might rely on the ability
161 * to intentionally provide smaller-than-MAX_META_BLOB_LENGTH in-buffers (so as to not need to allocate larger
162 * buffers which can be costlier in some cases depending on the allocator used), when both sides have agreed on
163 * a smaller-than-MAX_META_BLOB_LENGTH size limit. So through the protocol above ours, they guarantee no overflow;
164 * but if they (or someone else using us) messes it up, we will throw the necessary error at that time. */
165
167 "See large-ish comment above this place in the code.");
168
169 m_rcv_user_request.emplace();
170 m_rcv_user_request->m_target_hndl_ptr = target_hndl_or_null;
171 m_rcv_user_request->m_target_meta_blob = target_meta_blob;
172 if (!on_done_func_or_none.empty())
173 {
174 m_rcv_user_request->m_on_done_func = std::move(on_done_func_or_none);
175 }
176 // else { We must be part of the async_receive_batch_emulation() code path. }
177
179 {
180 rcv_read_msg_from_pkt_stream(assume_would_block, sync_err_code, sync_sz);
181 }
182 else // if constexpr(!USE_OS_DGRAM_SUPPORT)
183 {
185 {
186 // This is the mainstream path.
187 rcv_read_msg_from_byte_stream(assume_would_block, sync_err_code, sync_sz);
188 }
189 else
190 {
191 // Not an error but rare and interesting: INFO log-level.
192 FLOW_LOG_INFO("Socket stream [" << *this << "]: User async-receive request for "
193 "possible native handle and meta-blob (located @ [" << target_meta_blob.data() << "] of "
194 "max size [" << target_meta_blob.size() << "]): Encountered corner-case situation wherein "
195 "a batch-receive-emulation op earlier encountered a would-block in the middle of "
196 "reading a message having already received 1+ complete in-messages; which meant it had to "
197 "finish the batch-receive op and leave the state machine to resume at that point in the next "
198 "async-receive op (that is we). Executing the steps to place the state machine in that "
199 "state again and resume.");
200
201 /* Pre-nullify m_rcv_resume_incomplete_msg_processing_func just in case the saved function will itself need to set
202 * m_rcv_resume_incomplete_msg_processing_func again. */
203 const auto resume_func = std::move(m_rcv_resume_incomplete_msg_processing_func);
204 m_rcv_resume_incomplete_msg_processing_func.clear(); // Just in case the move-assign didn't do it.
205
206 /* This can be thought of as rcv_read_msg_from_byte_stream()... but it "fast-forwards" through part of the
207 * first in-message and resumes from after that point.
208 * @todo In this corner case we just ignore assume_would_block being true, if it's true. The idea is to do it
209 * for perf when user knows there's would-block; so this would at worst waste some cycles/whatever but get the
210 * right result. It is OK in this corner case of a corner case, but for full correctness it shouldn't be ignored;
211 * it's just fairly painful (as is everything to do with this resume-func corner case really) for
212 * m_rcv_resume_incomplete_msg_processing_func to take and propagate assume_would_block to its innards. */
213 resume_func(sync_err_code, sync_sz);
214 }
215 } // else // if constexpr(!USE_OS_DGRAM_SUPPORT)
216
217 /* Finalize result, w/r/t *m_target_hndl_ptr (user expects the Native_handle -- null or otherwise -- to be there
218 * on success) and m_own_hndl (the auto-closing Native_handle storage we use until this moment). Namely:
219 * - Success (no error): Release from m_own_hndl; place raw handle value into *m_target_hndl_ptr; it's the
220 * user's problem now.
221 * - Error: Just keep it in m_own_hndl. If m_rcv_user_request is nullified (op finished), handle gets auto-closed
222 * instead of leaking. If op continues (would-block), then m_own_hndl should be null anyway; but in any
223 * case it's still stored and won't be leaked.
224 * Attn! Result can also be emitted through on_done_func(), so this has to be done there too. */
225 if ((!*sync_err_code) && m_rcv_user_request->m_target_hndl_ptr)
226 {
227 *m_rcv_user_request->m_target_hndl_ptr = util::disowned_native_handle(std::move(m_rcv_user_request->m_own_hndl));
228 assert(m_rcv_user_request->m_own_hndl.get().null() && "Move-from Owned_nh=>Nh should have nullified the former.");
229 }
230
231 if (m_rcv_user_request->m_on_done_func.empty() // Would-block ends op, if no on-done func provided (as advertised).
232 || (*sync_err_code != error::Code::S_SYNC_IO_WOULD_BLOCK)) // Success or any other error always ends op.
233 {
234 FLOW_LOG_TRACE("Async-request completed synchronously (result "
235 "[" << *sync_err_code << "] [" << sync_err_code->message() << "]); emitting synchronously and "
236 "disregarding handler.");
237 m_rcv_user_request.reset();
238 }
239 // else { Other stuff logged enough. }
240
241} // Native_socket_stream_impl::async_receive_core()
242
244{
246 using util::Task;
247 using boost::chrono::round;
248 using boost::chrono::milliseconds;
249
250 // We comment liberally, but tactically, inline; but please read the strategy in the class doc header's impl section.
251
252 assert(timeout.count() > 0);
253
254 if ((!op_started<Op::S_RCV>("idle_timer_run()")) || (!state_peer("idle_timer_run()")))
255 {
256 return false;
257 }
258 // else
259
260 // According to concept requirements we shall no-op/return false if duplicately called.
261 if (m_rcv_idle_timeout != Fine_duration::zero())
262 {
263 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User wants to start idle timer, but they have already "
264 "started it before. Therefore ignoring.");
265 return false;
266 }
267 // else
268
269 m_rcv_idle_timeout = timeout; // Remember this, both as flag (non-zero()) and to know to when to schedule it.
270 // Now we will definitely return true (even if an error is already pending). This matches concept requirements.
271
273 {
274 FLOW_LOG_INFO("Socket stream [" << *this << "]: User wants to start idle timer, but an error has already "
275 "been found and emitted earlier. It's moot; ignoring.");
276 return true;
277 }
278 // else
279
280 FLOW_LOG_INFO("Socket stream [" << *this << "]: User wants to start idle-timer with timeout "
281 "[" << round<milliseconds>(m_rcv_idle_timeout) << "]. Scheduling (will be rescheduled as needed).");
282
283 /* Per requirements in concept, start it now; reschedule similarly each time there is activity.
284 *
285 * The mechanics of timer-scheduling are identical to those in auto_ping() and are explained there;
286 * keeping comments light here. */
287
289 false, // Wait for read.
290 boost::make_shared<Task>
291 ([this]() { rcv_on_ev_idle_timer_fired(); }));
292
295
296 /* We are a public API impl; but we don't try any I/O; so m_rcv_pending_err_code cannot become truthy.
297 * So no need to check for that / log_stats() (see its doc header for background as to what we mean here). */
298
299 return true;
300} // Native_socket_stream_impl::idle_timer_run()
301
303{
304 /* This is an event handler! Specifically for the *m_rcv_idle_timer_fired_peer pipe reader being
305 * readable. To avoid infinite-loopiness, we'd best pop the thing that was written there. */
307
309 {
310 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Idle timer fired: There's been 0 traffic past idle timeout. "
311 "However an error has already been found and emitted earlier. Therefore ignoring.");
312 return;
313 }
314 // else
315
318
319 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Idle timer fired: There's been 0 traffic past idle timeout. "
320 "Will not proceed with any further low-level receiving. If a user async-receive request is "
321 "pending (is it? = [" << (m_rcv_user_request || m_rcv_user_batch_request) << "]) "
322 "will emit to completion handler.");
323
324 /* See log_stats() doc header for basic background behind the logic here.
325 * Note we put this ahead of any handler-call to avoid reentrant hellishness. */
326 rcv_log_stats("rcv_on_ev_idle_timer_fired(): idle timeout fired, rcv-pipe hosed");
327
329 {
331 // Prevent stepping on our own toes: move/clear it first / invoke handler second.
332 const auto on_done_func = std::move(m_rcv_user_request->m_on_done_func);
333 m_rcv_user_request.reset();
334
335 assert((!on_done_func.empty())
336 && "Empty m_on_done_func => that m_rcv_user_request should have been nullified before returning "
337 "from the async-receive API, hence before the timer fired and we were called. Bug.");
338
339 on_done_func(m_rcv_pending_err_code, 0);
340 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Handler completed.");
341 }
343 {
344 assert(!m_rcv_user_request);
345 const auto on_done_func = std::move(m_rcv_user_batch_request->m_on_done_func);
347 assert((!on_done_func.empty())
348 && "Empty m_on_done_func => that m_rcv_user_batch_request should have been nullified before returning "
349 "from the async-receive API, hence before the timer fired and we were called. Bug.");
350 on_done_func(m_rcv_pending_err_code);
351 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Handler completed.");
352 }
353
354 /* That's it. If m_rcv_user_request (we've just nullified it) then async read chain will be finished forever as
355 * soon as the user informs us of readability (if it ever does) -- we will detect there's an error
356 * in m_rcv_pending_err_code already (and hence no m_rcv_user_request).
357 * Or same deal with m_rcv_user_batch_request. */
358} // Native_socket_stream_impl::rcv_on_ev_idle_timer_fired()
359
361{
363
364 if (m_rcv_idle_timeout == Fine_duration::zero())
365 {
366 return;
367 }
368 // else
369
370 /* idle_timer_run() has enabled the idle timer feature, and we've been called indicating we just read something,
371 * and therefore it is time to reschedule the idle timer. */
372
373 const auto n_canceled = m_rcv_idle_timer.expires_after(m_rcv_idle_timeout);
374
375 if (n_canceled == 0)
376 {
377 // This is a fun, rare coincidence that is worth an INFO message.
378 FLOW_LOG_INFO("Socket stream [" << *this << "]: Finished reading a message, which means "
379 "we just received traffic, which means the idle timer should be rescheduled. However "
380 "when trying to reschedule it, we found we were too late: it was very recently queued to "
381 "be invoked in the near future. An idle timeout error shall be emitted very soon.");
382 }
383 else // if (n_canceled >= 1)
384 {
385 assert((n_canceled == 1) && "We only issue 1 timer async_wait() at a time.");
386
387 /* m_timer_worker will m_rcv_idle_timer.async_wait(F), where F() will signal through pipe,
388 * making *m_rcv_idle_timer_fired_peer readable. We've already used m_rcv_ev_wait_func() to start
389 * wait on it being readable and invoke rcv_on_ev_idle_timer_fired() in that case; but we've
390 * canceled the previous .async_wait() that would make it readable; so just redo that part. */
392 }
393} // Native_socket_stream_impl::rcv_not_idle()
394
396 Error_code* sync_err_code, size_t* sync_sz)
397{
398 using util::Task;
399 using util::Blob_mutable;
401 using flow::util::Lock_guard;
402
404 && "@todo Really this function should be made to not compile in this case, and this then should be "
405 "static_assert() rather than run-time.");
406 assert(!m_rcv_pending_err_code);
407
408 if (assume_would_block)
409 {
411 return;
412 }
413 // else:
414
415 const bool proto_negotiating
417
418 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Async-receive: "
419 "Trying nb-read of in-dgram synchronously; proto-negotiating? = [" << proto_negotiating << "].");
420
421 /* Target this even if target_hndl_or_null is null (to check for a certain error).
422 * Prevent leaks by wrapping the raw handle in this auto-closer. */
423 Own_native_handle target_hndl;
424 const auto n_rcvd_or_zero
426 (&target_hndl,
428 proto_negotiating ? Blob_mutable{}
429 : m_rcv_user_request->m_target_meta_blob,
431
433 {
434 if (n_rcvd_or_zero != 0)
435 {
436 FLOW_LOG_TRACE("Got in-dgram.");
437
438 rcv_on_dgram(std::move(target_hndl), n_rcvd_or_zero, sync_err_code, sync_sz);
439 return;
440 }
441 // else
442
443 if (m_rcv_user_request->m_on_done_func.empty())
444 {
445 FLOW_LOG_TRACE("Got nothing but would-block. This is part of a sub-op (as of this writing when emulating "
446 "batch-receive using single-receives) in which would-block means we end the op and do *not* "
447 "await readability; so we emit would-block and stop the overall receive op.");
448 // This is like the below case minus the considerably more complex steps of continuing the overall op.
449 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
450 *sync_sz = 0;
451 return;
452 }
453 // else
454
455 FLOW_LOG_TRACE("Got nothing but would-block. Awaiting readability.");
456
457 /* Conceptually we'd like to do m_peer_socket->async_wait(readable, F), where F() would perform
458 * rcv_nb_read_low_lvl_payload_from_pkt_stream() (nb-receive over m_peer_socket). However this is the sync_io
459 * pattern, so the user will be performing the conceptual async_wait() for us. We must ask them to do so
460 * via m_rcv_ev_wait_func(), giving them m_peer_socket's FD -- m_ev_wait_hndl_peer_socket -- to wait-on. */
461
462 // m_*peer_socket = (the only) shared data between our- and opposite-direction code. Must lock (see their docs).
463 {
464 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
465
466 if (m_peer_socket)
467 {
469 false, // Wait for read.
470 // Once readable do this:
471 boost::make_shared<Task>([this]()
472 {
474 }));
475
476 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
477 *sync_sz = 0;
478 return;
479 }
480 // else:
481 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
482
483 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request: "
484 "was about to await readability but discovered opposite-direction socket-hosing error; "
485 "emitting error via completion handler (or via sync-args).");
486
488 } // if (!m_rcv_pending_err_code) (but may have become truthy inside, and has unless we `return`ed inside)
489
491
492 FLOW_LOG_TRACE("Nb-read, or async-read following nb-read encountering would-block, detected error (details "
493 "above); will emit via completion handler (or via sync-args). Most errors hose the pipe "
494 "in both directions; however as of this writing MESSAGE_SIZE_EXCEEDS_USER_STORAGE "
495 "hoses only in in-direction while the out-direction pipe is usable.");
496
497 *sync_err_code = m_rcv_pending_err_code;
498 *sync_sz = 0;
499} // Native_socket_stream_impl::rcv_read_msg_from_pkt_stream()
500
502 Error_code* sync_err_code, size_t* sync_sz)
503{
504 using util::Blob_mutable;
505 using util::Task;
506
508 && "@todo Really this function should be made to not compile in this case, and this then should be "
509 "static_assert() rather than run-time. Anyway... bug; we should never be called because "
510 "!USE_OS_DGRAM_SUPPORT.");
511 assert(m_rcv_user_request);
512 assert(!m_rcv_pending_err_code);
513 assert(n_rcvd != 0);
514
515 bool proto_negotiating
517 const bool hndl_is_null = hndl_or_null.get().null();
518
519 if (proto_negotiating && (!hndl_is_null))
520 {
521 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Expecting protocol-negotiation (first) in-dgram "
522 "to contain *only* a meta-blob: but received Native_handle is non-null which is "
523 "unexpected; emitting error via completion handler (or via sync-args).");
524#ifndef NDEBUG
525 const bool ok =
526#endif
528 assert(ok && "Protocol_negotiator breaking contract? Bug?");
530 && "Protocol_negotiator should have emitted error given intentionally bad version.");
531 }
532 else if ((!hndl_is_null) && (!m_rcv_user_request->m_target_hndl_ptr))
533 // && (!proto_negotiating)
534 {
535 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request for "
536 "*only* a meta-blob: but received Native_handle is non-null which is "
537 "unexpected; emitting error via completion handler (or via sync-args).");
539 /* @todo Consider nullifying m_peer_socket (hosing out-pipe) here per to-do in rcv_read_batch_from_pkt_stream().
540 * Or comment the reverse and explain why (all part of that to-do). */
541 } // if (!hndl_is_null && (!m_rcv_user_request->m_target_hndl_ptr))
542 else // if (no prob with hndl_or_null or m_target_hndl_ptr)
543 {
544 // Finalize the user's Native_handle target variable if applicable.
545 if (m_rcv_user_request->m_target_hndl_ptr
546 // If proto_negotiating, hndl_or_null is null; and anyway m_target_hndl_ptr is not yet in play.
547 && (!proto_negotiating))
548 {
549 m_rcv_user_request->m_own_hndl = std::move(hndl_or_null);
550 }
551
552 /* Decode the situation, which mainly flows from m_rcv_target_meta_length, though despite the name in
553 * our (dgram-based) case it is actually:
554 * - (if proto_negotiating) Protocol_negotiator-consumed value; or
555 * - (otherwise) basically an enum indicating message type.
556 * (Re-recommend here to read ### Protocol with `Protocol_pkt_stream` (OS maintains message boundaries) ###
557 * in class doc header.)
558 *
559 * In any case m_rcv_target_meta_length must have been fully received; so check that first. */
560
561 if (n_rcvd < sizeof(m_rcv_target_meta_length))
562 {
563 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Received in-dgram contains invalid header (opposing "
564 "side misbehaved/bug?): header length is [" << sizeof(m_rcv_target_meta_length) << "]; "
565 "but the entire in-dgram has size only [" << n_rcvd << "]; "
566 "emitting error via completion handler (or via sync-args).");
568 /* @todo Consider nullifying m_peer_socket (hosing out-pipe) here per to-do in rcv_read_batch_from_pkt_stream().
569 * Same for all below instances of emitting INVALID_HEADER. */
570 }
571 else // if (n_rcvd >= sizeof(m_rcv_target_meta_length))
572 {
573 const bool no_data = n_rcvd == sizeof(m_rcv_target_meta_length);
574
575 if (proto_negotiating)
576 {
577 if (!no_data)
578 {
579 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Expecting protocol-negotiation (first) in-dgram "
580 "to contain *only* the header: but received more bytes "
581 "([" << (n_rcvd - sizeof(m_rcv_target_meta_length)) << "] on top of header); "
582 "unexpected; emitting error via completion handler (or via sync-args).");
584 }
585 else // if (no_data) // && proto_negotiating
586 {
587 /* Protocol_negotiator handles everything (invalid value, incompatible range...); we just know
588 * the encoding is to shove the version number into what is normally the length field. */
589#ifndef NDEBUG
590 const bool ok =
591#endif
595 assert(ok && "Protocol_negotiator breaking contract? Bug?");
596 proto_negotiating = false; // Just in case (maintainability).
597
599 {
600 // Succeeded; do what we'd do due to, say, receiving auto-ping below.
601
602 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of negotiation payload; passed. "
603 "Ignoring other than registering non-idle activity. Proceeding with the next dgram read.");
604
605 rcv_not_idle(); // Register activity <= end of complete message, no error.
606
608 rcv_read_msg_from_pkt_stream(false, sync_err_code, sync_sz);
609 return;
610 }
611 // else if (m_rcv_pending_err_code) { Fall through as for various other error cases above and below. }
612 } // else if (!no_data) // && proto_negotiating
613 } // if (proto_negotiating)
614 else // if (!proto_negotiating) // Typical path.
615 {
617 {
618 if (!no_data)
619 {
620 sync_err_code->clear();
621 *sync_sz = (n_rcvd - sizeof(m_rcv_target_meta_length));
622
623 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received in-dgram with user message sized "
624 "[" << *sync_sz << "]. Will register non-idle activity; "
625 "and invoke handler (or report via sync-args).");
626
627 rcv_not_idle(); // Register activity <= end of complete message, no error.
628
630 m_rcv_stats.m_total_bytes += *sync_sz;
632 m_rcv_stats.m_histo_payload_sz.record_value(*sync_sz);
633 if (!hndl_is_null) { ++m_rcv_stats.m_msgs_with_hndls; }
634 return;
635 }
636 // else
637 if (no_data && (!hndl_is_null))
638 {
639 sync_err_code->clear();
640 *sync_sz = 0;
641
642 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received in-dgram; header=0 + non-null handle + "
643 "no data => handle received with no meta-blob. Will register non-idle activity; "
644 "and invoke handler (success).");
645
646 rcv_not_idle(); // Register activity <= end of complete message, no error.
647
650 m_rcv_stats.m_histo_payload_sz.record_value(0); // Handle-only: 0-byte user payload.
651 ++m_rcv_stats.m_msgs_with_hndls; // Always a handle here (that's the point).
652 return;
653 }
654 // else if (no_data && hndl_is_null):
655
656 // Once per connection at most, so INFO log level is OK.
657 FLOW_LOG_INFO("Socket stream [" << *this << "]: Received in-dgram: Graceful-close-of-incoming-pipe "
658 "message. Will not proceed with any further low-level receiving. "
659 "Will invoke handler (graceful-close error).");
661
662 /* Discussion/sanity-checks: RECEIVES_FINISHED_CANNOT_RECEIVE is not like other errors emitted in the
663 * present function, as it is (even in the concept docs) more like an in-message: if user send*()s A, B, C
664 * and then async_end_sending(), then we will receive A, B, C, error R_F_C_R. So are we doing the right
665 * thing? What we do is emit it (hence in-pipe is hosed) but leave m_peer_socket alone; out-pipe
666 * remains healthy; they can send*() and/or *end_sending() just fine.
667 *
668 * Why do we choose this? Answer: See comment in similar spot in rcv_read_batch_from_pkt_stream(), where
669 * it discusses graceful-close RECEIVES_FINISHED_CANNOT_RECEIVE but also `eof` and
670 * MESSAGE_SIZE_EXCEEDS_USER_STORAGE. (That comment covers more situations than we have
671 * here, so that's why the "pointee" comment is there, while the "pointer" is here.) In our case,
672 * that comment covers the situation relevant to the present function: receiving
673 * RECEIVES_FINISHED_CANNOT_RECEIVE.
674 *
675 * The MESSAGE_SIZE_EXCEEDS_USER_STORAGE and `eof` cases are handled one level down, in
676 * rcv_nb_read_low_lvl_payload_from_pkt_stream() (as it's emitted by low-level-transmit APIs as an error
677 * rather than encoded in the dgram as here in rcv_on_dgram()). */
678
680 } // if (m_rcv_target_meta_length == 0)
682 {
683 if (no_data && hndl_is_null)
684 {
685 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received in-dgram; header "
686 "contains special value indicating a ping. Ignoring other than registering non-idle "
687 "activity. Proceeding with the next dgram read.");
688
689 rcv_not_idle(); // Register activity <= end of complete message, no error.
690
693 rcv_read_msg_from_pkt_stream(false, sync_err_code, sync_sz);
694 return;
695 }
696 // else if (!no_data) || (!hndl_is_null):
697 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Received in-dgram contains invalid header (opposing "
698 "side misbehaved/bug?): it contains PING_SENTINEL, but a ping shall contain no "
699 "further data and no native handle, yet we received either more bytes "
700 "([" << (n_rcvd - sizeof(m_rcv_target_meta_length)) << "] on top of header) or "
701 "a handle (got one? = [" << (!hndl_is_null) << "]) or both; "
702 "emitting error via completion handler (or via sync-args).");
704 } // else if (m_rcv_target_meta_length == PING)
705 else // if (m_rcv_target_meta_length != 0 or PING)
706 {
707 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Received in-dgram contains invalid header (opposing "
708 "side misbehaved/bug?): it contains the value [" << m_rcv_target_meta_length << " "
709 "(decimal)] but only the values 0 and [" << Native_socket_stream_cfg::S_PING_SENTINEL << "] "
710 "are allowed; we also possibly received more bytes "
711 "([" << (n_rcvd - sizeof(m_rcv_target_meta_length)) << "] on top of header); "
712 "emitting error via completion handler (or via sync-args).");
714 } // else if (m_rcv_target_meta_length != 0 or PING)
715 } // else if (!proto_negotiating)
716 } // else if (n_rcvd >= sizeof(m_rcv_target_meta_length))
717 } // else if (no prob with hndl_or_null or m_target_hndl_ptr)
718
720
721 // WARNINGs above are enough; no TRACE here.
722
723 *sync_err_code = m_rcv_pending_err_code;
724 *sync_sz = 0;
725} // Native_socket_stream_impl::rcv_on_dgram()
726
728{
729 /* Our task is the USE_OS_DGRAM_SUPPORT version of rcv_on_ev_peer_socket_byte_stream_readable_or_error()
730 * but much simpler: The would-block (which has now been resolved) can only by definition occur between
731 * in-dgrams and not partway through one. So basically we just do
732 * rcv_read_msg_from_pkt_stream()
733 * again, with some pre-checks (same as in that other guy) as-to something having happened during the wait +
734 * invoking the on-done handler on successful in-message read. */
735
737 && "@todo Really this function should be made to not compile in this case, and this then should be "
738 "static_assert() rather than run-time.");
739
741 {
742 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User's wait-for-readable finished (readable or error, "
743 "we do not know which yet); would resume processing depending on what we were doing before; "
744 "however an error was detected in the meantime (as of this writing: idle timeout). "
745 "Stopping read chain.");
746 assert((!m_rcv_user_request)
747 && "If rcv-error emitted during low-level async-wait, we should have fed it to any pending async-receive.");
748 return;
749 }
750 // else
751
752 assert(m_rcv_user_request);
753
754 // Will potentially emit these (if and only if message-read completes due to this successful async-wait).
755 Error_code sync_err_code;
756 size_t sync_sz;
757
758 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User-performed wait-for-readable finished (readable or error, "
759 "we do not know which yet). Retrying to resume read chain.");
760
761 rcv_read_msg_from_pkt_stream(false, &sync_err_code, &sync_sz);
762
763 if (sync_err_code == error::Code::S_SYNC_IO_WOULD_BLOCK)
764 {
765 // Another async-wait is pending now. We've logged enough. Live to fight another day.
766 return;
767 }
768 // else
769
770 /* See log_stats() doc header for basic background behind the logic here.
771 * Note we put this ahead of any handler-call to avoid reentrant hellishness. */
772 if (m_rcv_pending_err_code) // Note we would've returned already had it been already truthy at the start.
773 {
774 rcv_log_stats("rcv_on_ev_peer_socket_pkt_stream_readable_or_error(): while processing ev-ready rcv-pipe hosed");
775 }
776
777 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Async-op result ready after successful async-wait. "
778 "Executing handler now.");
779
780 // Prevent stepping on our own toes: move/clear it first / invoke handler second.
781 const auto on_done_func = std::move(m_rcv_user_request->m_on_done_func);
782
783 /* Finalize result, w/r/t *m_target_hndl_ptr and m_own_hndl. See async_receive_core() near the end; we are
784 * doing the same thing but for the asynchronous path (where the original async_receive_*() did not get
785 * an in-message synchronously). */
786 if ((!sync_err_code) && m_rcv_user_request->m_target_hndl_ptr)
787 {
788 *m_rcv_user_request->m_target_hndl_ptr = util::disowned_native_handle(std::move(m_rcv_user_request->m_own_hndl));
789 assert(m_rcv_user_request->m_own_hndl.get().null() && "Move-from Owned_nh=>Nh should have nullified the former.");
790 }
791
792 // Op finished.
793 m_rcv_user_request.reset();
794
795 assert((!on_done_func.empty())
796 && "Empty m_on_done_func => that m_rcv_user_request should have been nullified without issuing async-wait, "
797 "so we should not have been called. Bug.");
798
799 on_done_func(sync_err_code, sync_sz);
800 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Handler completed.");
801} // Native_socket_stream_impl::rcv_on_ev_peer_socket_pkt_stream_readable_or_error()
802
804 Error_code* sync_err_code, size_t* sync_sz)
805{
807 using util::Task;
808 using util::Blob_mutable;
809 using flow::util::Lock_guard;
810
812 && "@todo Really this function should be made to not compile in this case, and this then should be "
813 "static_assert() rather than run-time.");
814
815 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Async-receive: Start of payload 1: "
816 "Trying nb-read of payload 1 (handle if any, meta-blob-length) synchronously.");
817 assert(!m_rcv_pending_err_code);
818
819 if (assume_would_block)
820 {
822 return;
823 }
824 // else:
825
826 /* Target this even if target_hndl_or_null is null (to check for a certain error).
827 * Prevent leaks by wrapping the raw handle in this auto-closer. */
828 Own_native_handle target_hndl;
829 const auto n_rcvd_or_zero
835 {
836 if (n_rcvd_or_zero != 0)
837 {
838 FLOW_LOG_TRACE("Got some or all of payload 1.");
839 rcv_on_handle_finalized(std::move(target_hndl), n_rcvd_or_zero, sync_err_code, sync_sz);
840 return;
841 }
842 // else
843
844 if (m_rcv_user_request->m_on_done_func.empty())
845 {
846 FLOW_LOG_TRACE("Got nothing but would-block. This is part of a sub-op (as of this writing when emulating "
847 "batch-receive using single-receives) in which would-block means we end the op and do *not* "
848 "await readability; so we emit would-block and stop the overall receive op.");
849 // This is like the below case minus the considerably more complex steps of continuing the overall op.
850 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
851 *sync_sz = 0;
852 return;
853 }
854 // else
855
856 FLOW_LOG_TRACE("Got nothing but would-block. Awaiting readability.");
857
858 /* Conceptually we'd like to do m_peer_socket->async_wait(readable, F), where F() would perform
859 * rcv_nb_read_low_lvl_payload_from_byte_stream() (nb-receive over m_peer_socket). However this is the sync_io
860 * pattern, so the user will be performing the conceptual async_wait() for us. We must ask them to do so
861 * via m_rcv_ev_wait_func(), giving them m_peer_socket's FD -- m_ev_wait_hndl_peer_socket -- to wait-on. */
862
863 // m_*peer_socket = (the only) shared data between our- and opposite-direction code. Must lock (see their docs).
864 {
865 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
866
867 if (m_peer_socket)
868 {
870 false, // Wait for read.
871 // Once readable do this:
872 boost::make_shared<Task>([this]()
873 {
875 0 /* ignored for S_MSG_START */);
876 }));
877
878 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
879 *sync_sz = 0;
880 return;
881 }
882 // else:
883 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
884
885 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request: "
886 "was about to await readability but discovered opposite-direction socket-hosing error; "
887 "emitting error via completion handler (or via sync-args).");
888
890 } // if (!m_rcv_pending_err_code) (but may have become truthy inside, and has unless we `return`ed inside)
891
893
894 FLOW_LOG_TRACE("Nb-read, or async-read following nb-read encountering would-block, detected error (details "
895 "above); will emit via completion handler (or via sync-args).");
896
897 *sync_err_code = m_rcv_pending_err_code;
898 *sync_sz = 0;
899} // Native_socket_stream_impl::rcv_read_msg_from_byte_stream()
900
902 Error_code* sync_err_code, size_t* sync_sz)
903{
905 using util::Blob_mutable;
906 using util::Task;
907 using flow::util::Lock_guard;
908
910 && "@todo Really this function should be made to not compile in this case, and this then should be "
911 "static_assert() rather than run-time.");
912 assert(m_rcv_user_request);
913 assert(!m_rcv_pending_err_code);
914 assert(n_rcvd != 0);
915
916 const bool proto_negotiating
918 const bool hndl_is_null = hndl_or_null.get().null();
919
920 if (proto_negotiating && (!hndl_is_null))
921 {
922 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Expecting protocol-negotiation (first) in-message "
923 "to contain *only* a meta-blob: but received Native_handle is non-null which is "
924 "unexpected; emitting error via completion handler (or via sync-args).");
925#ifndef NDEBUG
926 const bool ok =
927#endif
929 assert(ok && "Protocol_negotiator breaking contract? Bug?");
931 && "Protocol_negotiator should have emitted error given intentionally bad version.");
932 }
933 else if ((!hndl_is_null) && (!m_rcv_user_request->m_target_hndl_ptr))
934 // && (!proto_negotiating)
935 {
936 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request for "
937 "*only* a meta-blob: but received Native_handle is non-null which is "
938 "unexpected; emitting error via completion handler (or via sync-args).");
940 /* @todo Consider nullifying m_peer_socket (hosing out-pipe) here per to-do in rcv_read_batch_from_pkt_stream().
941 * Or comment the reverse and explain why (all part of that to-do). */
942 } // if (!hndl_is_null && (!m_rcv_user_request->m_target_hndl_ptr))
943 else // if (no prob with hndl_or_null or m_target_hndl_ptr)
944 {
945 // Finalize the user's Native_handle target variable if applicable.
946 if (m_rcv_user_request->m_target_hndl_ptr
947 // If proto_negotiating, hndl_or_null is null; and anyway m_target_hndl_ptr is not yet in play.
948 && (!proto_negotiating))
949 {
950 m_rcv_user_request->m_own_hndl = std::move(hndl_or_null);
951 }
952
953 if (n_rcvd == sizeof(m_rcv_target_meta_length))
954 {
955 // Got the entire payload 1, not just some of it including handle-if-any.
956 rcv_on_head_payload(sync_err_code, sync_sz);
957 return;
958 }
959 // else
960
961 // Still have to finish reading into m_rcv_target_meta_length. We've already hit would-block though.
962 if (m_rcv_user_request->m_on_done_func.empty())
963 {
964 // Corner case.
965 FLOW_LOG_TRACE("Got would-block after only partially reading the meta-length/msg-type from the byte stream. "
966 "This is part of a sub-op (as of this writing when emulating "
967 "batch-receive using single-receives) in which would-block means we end the op and do *not* "
968 "await readability; so we emit would-block and stop the overall receive op. However saving "
969 "the partial-message info, and any subsequent receive-op will resume from here.");
970
971 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
972 *sync_sz = 0;
973
974 /* To resume the state machine below, we have to in a sense "replay" what has happened so far this time around
975 * (which we shall abandon this time around). To do this sufficiently succinctly/buglessly it is hugely helpful
976 * to avoid various branches/corner cases w/r/t what may have happened for this incomplete in-message so far.
977 * This is helpful at least: */
978 assert((!proto_negotiating) && "This corner case should only happen after already banking 1+ user in-message, "
979 "so protocol negotiation should have already succeeded.");
981 && "This should have been nullified at the start of the async-receive.");
982
983 /* hndl_or_null's at-entry value may or may not have been moved from hndl_or_null arg into m_own_hndl.
984 * We are just trying to save the what's-been-read-so-far state here -- nothing more complex than that -- for
985 * later replaying. W/r/t hndl_or_null *inside* the lambda, that state is: we had successfully received
986 * <at-entry value of hndl_or_null in-arg>. Easy enough, except we may have moved it into m_own_hndl above.
987 * Point is, this is Own_native_handle (a unique_resource), and m_own_hndl is always null to start;
988 * thus the value we want is either null (from either place), or non-null in hndl_or_null still, or non-null
989 * in m_own_hndl (if moved there). So despite this annoying paragraph, it's just a simple ternary.
990 *
991 * The other complication is one cannot capture uncopyable objects (even when, as here, they're not going to
992 * be copied), as commonly seen with `unique_ptr`s. So we'll have to do the temp-shared_ptr-capture dance.
993 * (Don't try to store .release() in the captures, as that can leak again.) */
994
995 if (hndl_or_null.get().null())
996 {
997 hndl_or_null = std::move(m_rcv_user_request->m_own_hndl); // As noted: may well still be null.
998 }
1000 = [this,
1001 owned_hndl_or_null = boost::make_shared<Own_native_handle>(std::move(hndl_or_null)),
1002 target_meta_length_incomplete = m_rcv_target_meta_length,
1003 n_rcvd]
1004 (Error_code* err_code, size_t* sz)
1005 {
1006 rcv_resume_incomplete_msg_processing(err_code, sz, std::move(*owned_hndl_or_null),
1007 target_meta_length_incomplete, n_rcvd, {});
1008 };
1009 assert(hndl_or_null.get().null() && "The move-from should have nullified the local hndl_or_null.");
1010
1011 return;
1012 }
1013 // else if (!m_rcv_user_request->m_on_done_func.empty()):
1014
1015 /* Mainstream case: Await readability; then resume once we think socket readable. So
1016 * much like in rcv_read_msg_from_byte_stream() (keeping comments light): */
1017
1018 {
1019 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
1020
1021 if (m_peer_socket)
1022 {
1024 false, // Wait for read.
1025 // Once readable do this:
1026 boost::make_shared<Task>([this, n_rcvd]()
1027 {
1029 sizeof(m_rcv_target_meta_length) - n_rcvd);
1030 }));
1031
1032 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
1033 *sync_sz = 0;
1034 return;
1035 }
1036 // else:
1037 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
1038
1039 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request: "
1040 "was about to await readability but discovered opposite-direction socket-hosing error; "
1041 "emitting error via completion handler (or via sync-args).");
1042
1044 } // if (no prob with hndl_or_null or m_target_hndl_ptr) (but another problem may have occurred inside)
1045
1046 assert(m_rcv_pending_err_code);
1047
1048 // WARNINGs above are enough; no TRACE here.
1049
1050 *sync_err_code = m_rcv_pending_err_code;
1051 *sync_sz = 0;
1052} // Native_socket_stream_impl::rcv_on_handle_finalized()
1053
1055{
1056 using util::Blob_mutable;
1057
1059 && "@todo Really this function should be made to not compile in this case, and this then should be "
1060 "static_assert() rather than run-time.");
1061 assert(m_rcv_user_request);
1062 assert(!m_rcv_pending_err_code);
1063
1064 bool proto_negotiating
1066
1067 if (proto_negotiating)
1068 {
1069 /* Protocol_negotiator handles everything (invalid value, incompatible range...); we just know
1070 * the encoding is to shove the version number into what is normally the length field.
1071 *
1072 * (rcv_on_handle_finalized() validated, already, that there is no extraneous native-handle in the
1073 * payload; it did so, and we don't have to, because in proto_negotiating-mode getting 1 byte is sufficient
1074 * to execute that validation; so we greedily do so up there. Cf.: !proto_negotiating => we have to wait
1075 * for the whole payload; hence hndl_is_null is computed/verified below as appropriate.) */
1076#ifndef NDEBUG
1077 const bool ok =
1078#endif
1082 assert(ok && "Protocol_negotiator breaking contract? Bug?");
1083 proto_negotiating = false; // Just in case (maintainability).
1084
1086 {
1087 // Protocol negotiation failed. Do what we'd do due to, say, graceful-close below.
1088 *sync_err_code = m_rcv_pending_err_code;
1089 *sync_sz = 0;
1090 return;
1091 }
1092 // else: Succeeded; do what we'd do due to, say, receiving auto-ping below.
1093
1094 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of negotiation payload; passed. "
1095 "Ignoring other than registering non-idle activity. Proceeding with the next message read.");
1096
1097 rcv_not_idle(); // Register activity <= end of complete message, no error.
1098
1100 rcv_read_msg_from_byte_stream(false, sync_err_code, sync_sz);
1101 return;
1102 }
1103 // else if (!proto_negotiating): Normal payload 1 handling.
1104
1106 {
1107 if (m_rcv_user_request->m_own_hndl.get().null())
1108 {
1109 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of payload 1; length prefix "
1110 "contains special value indicating a ping. Ignoring other than registering non-idle "
1111 "activity. Proceeding with the next message read.");
1112
1113 rcv_not_idle(); // Register activity <= end of complete message, no error.
1114
1117 rcv_read_msg_from_byte_stream(false, sync_err_code, sync_sz);
1118 return;
1119 }
1120 // else if (!hndl_is_null) + PING:
1121
1122 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Received all of payload 1; length prefix "
1123 "contains special value indicating a ping; "
1124 "however it also contains a native handle which is a protocol violation. "
1125 "Treating similarly to a graceful-close but with a bad error code and "
1126 "this warning. Will not proceed with any further low-level receiving; will invoke "
1127 "handler (failure).");
1129 // @todo Consider nullifying m_peer_socket (hosing out-pipe) here per to-do in rcv_read_batch_from_pkt_stream().
1130 }
1131 else // if (m_rcv_target_meta_length != PING)
1132 {
1133 const auto user_target_size = m_rcv_user_request->m_target_meta_blob.size();
1134 if (m_rcv_target_meta_length != 0) // && (not ping)
1135 {
1136 if (m_rcv_target_meta_length <= user_target_size)
1137 {
1138 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of payload 1; length prefix "
1139 "[" << m_rcv_target_meta_length <<"] is positive (and not indicative of ping). "
1140 "Reading payload 2.");
1142 Blob_mutable{m_rcv_user_request->m_target_meta_blob.data(),
1143 size_t{m_rcv_target_meta_length}},
1144 sync_err_code, sync_sz);
1145 return;
1146 }
1147 // else if (m_rcv_target_meta_length > user_target_size):
1148
1149 FLOW_LOG_WARNING("Socket stream [" << *this << "]: Received all of payload 1; length prefix "
1150 "[" << m_rcv_target_meta_length <<"] is positive (and not indicative of ping); "
1151 "however it exceeds user target blob size [" << user_target_size << "] and would "
1152 "overflow. Treating similarly to a graceful-close but with a bad error code and "
1153 "this warning. Will not proceed with any further low-level receiving; will invoke "
1154 "handler (failure).");
1156
1157 /* Note: We don't nullify m_peer_socket in response to this user error; so they could still send*()
1158 * and/or *end_sending() in the out-direction. This is consistent with the Protocol_pkt_stream-path
1159 * behavior. See comment in rcv_nb_read_low_lvl_payload_from_pkt_stream()'s handling of EXCEEDS_USER_STORAGE;
1160 * it explains why we do this. */
1161 } // else if (m_rcv_target_meta_length != 0) (also not ping)
1162 else // if (m_rcv_target_meta_length == 0)
1163 {
1164 if (!m_rcv_user_request->m_own_hndl.get().null())
1165 {
1166 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of payload 1; length 0 + non-null handle => "
1167 "handle received with no meta-blob. Will register non-idle activity; "
1168 "and invoke handler (success).");
1169 // m_rcv_pending_err_code remains falsy.
1170
1171 rcv_not_idle(); // Register activity <= end of complete message, no error.
1172
1175 m_rcv_stats.m_histo_payload_sz.record_value(0); // Handle-only: 0-byte user payload.
1176 ++m_rcv_stats.m_msgs_with_hndls; // Always a handle here (that's the point).
1177 }
1178 else
1179 {
1180 // Once per connection at most, so INFO log level is OK.
1181 FLOW_LOG_INFO("Socket stream [" << *this << "]: User message received: Graceful-close-of-incoming-pipe "
1182 "message. Will not proceed with any further low-level receiving. "
1183 "Will invoke handler (graceful-close error).");
1185
1186 /* Note: We don't nullify m_peer_socket in response to this event which is not really a true-blue error but
1187 * rather indication that opposing user did *end_sending() after the message we'd last received; so they could
1188 * still send*() and/or *end_sending() in the out-direction. This is consistent with the
1189 * Protocol_pkt_stream-path behavior. See comment in rcv_on_dgram()'s handling of
1190 * RECEIVES_FINISHED_CANNOT_RECEIVE. */
1191
1193 }
1194 } // else // if (m_rcv_target_meta_length == 0)
1195 } // else // if (m_rcv_target_meta_length != PING)
1196
1197 *sync_err_code = m_rcv_pending_err_code; // Truthy (graceful-close/bad-ping/overflow) or falsy (handle sans blob).
1198 *sync_sz = 0;
1199} // Native_socket_stream_impl::rcv_on_head_payload()
1200
1202 size_t n_left)
1203{
1204 using util::Blob_mutable;
1205
1207 && "@todo Really this function should be made to not compile in this case, and this then should be "
1208 "static_assert() rather than run-time.");
1209
1211 {
1212 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User's wait-for-readable finished (readable or error, "
1213 "we do not know which yet); would resume processing depending on what we were doing before; "
1214 "however an error was detected in the meantime (as of this writing: idle timeout). "
1215 "Stopping read chain.");
1216 assert((!m_rcv_user_request)
1217 && "If rcv-error emitted during low-level async-wait, we should have fed it to any pending async-receive.");
1218 return;
1219 }
1220 // else
1221
1222 assert(m_rcv_user_request);
1223
1224 // Will potentially emit these (if and only if message-read completes due to this successful async-wait).
1225 Error_code sync_err_code;
1226 size_t sync_sz;
1227
1228 FLOW_LOG_TRACE("Socket stream [" << *this << "]: User-performed wait-for-readable finished (readable or error, "
1229 "we do not know which yet). Resuming processing depending on what we were doing before.");
1230
1231 switch (msg_state)
1232 {
1234 FLOW_LOG_TRACE("Socket stream [" << *this << "]: In state MSG_START: Reading from byte 0/handle if any.");
1235 rcv_read_msg_from_byte_stream(false, &sync_err_code, &sync_sz);
1236 break;
1237
1239 FLOW_LOG_TRACE("Socket stream [" << *this << "]: In state HEAD_PAYLOAD: "
1240 "Reading meta-length/ping/graceful-close specifier: [" << n_left << "] bytes left.");
1242 Blob_mutable{static_cast<uint8_t*>(static_cast<void*>(&m_rcv_target_meta_length))
1243 + sizeof(m_rcv_target_meta_length) - n_left,
1244 n_left},
1245 &sync_err_code, &sync_sz);
1246 break;
1247
1249 FLOW_LOG_TRACE("Socket stream [" << *this << "]: In state META_BLOB_PAYLOAD: "
1250 "Reading meta-blob: [" << n_left << "] bytes left.");
1252 Blob_mutable{static_cast<uint8_t*>(m_rcv_user_request->m_target_meta_blob.data())
1253 + size_t(m_rcv_target_meta_length) - n_left,
1254 n_left},
1255 &sync_err_code, &sync_sz);
1256 } // switch (msg_state) (Compiler should catch any missed enum value.)
1257
1258 if (sync_err_code == error::Code::S_SYNC_IO_WOULD_BLOCK)
1259 {
1260 // Another async-wait is pending now. We've logged enough. Live to fight another day.
1261 return;
1262 }
1263 // else
1264
1265 /* See log_stats() doc header for basic background behind the logic here.
1266 * Note we put this ahead of any handler-call to avoid reentrant hellishness. */
1267 if (m_rcv_pending_err_code) // Note we would've returned already had it been already truthy at the start.
1268 {
1269 rcv_log_stats("rcv_on_ev_peer_socket_byte_stream_readable_or_error(): while processing ev-ready rcv-pipe hosed");
1270 }
1271
1272 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Async-op result ready after successful async-wait. "
1273 "Executing handler now.");
1274
1275 // Prevent stepping on our own toes: move/clear it first / invoke handler second.
1276 const auto on_done_func = std::move(m_rcv_user_request->m_on_done_func);
1277
1278 /* Finalize result, w/r/t *m_target_hndl_ptr and m_own_hndl. See async_receive_core() near the end; we are
1279 * doing the same thing but for the asynchronous path (where the original async_receive_*() did not get
1280 * an in-message synchronously). */
1281 if ((!sync_err_code) && m_rcv_user_request->m_target_hndl_ptr)
1282 {
1283 *m_rcv_user_request->m_target_hndl_ptr = util::disowned_native_handle(std::move(m_rcv_user_request->m_own_hndl));
1284 assert(m_rcv_user_request->m_own_hndl.get().null() && "Move-from Owned_nh=>Nh should have nullified the former.");
1285 }
1286
1287 // Op finished.
1288 m_rcv_user_request.reset();
1289
1290 assert((!on_done_func.empty())
1291 && "Empty m_on_done_func => that m_rcv_user_request should have been nullified without issuing async-wait, "
1292 "so we should not have been called. Bug.");
1293
1294 on_done_func(sync_err_code, sync_sz);
1295 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Handler completed.");
1296} // Native_socket_stream_impl::rcv_on_ev_peer_socket_byte_stream_readable_or_error()
1297
1299 Error_code* sync_err_code, size_t* sync_sz)
1300{
1302 using util::Task;
1303 using util::Blob_const;
1304 using flow::util::Lock_guard;
1305 using flow::util::Blob;
1306
1308 && "@todo Really this function should be made to not compile in this case, and this then should be "
1309 "static_assert() rather than run-time.");
1310 assert(!m_rcv_pending_err_code);
1311 assert(m_rcv_user_request);
1312
1313 const auto n_rcvd_or_zero = rcv_nb_read_low_lvl_payload_from_byte_stream(nullptr, target_blob,
1316 {
1317 if (n_rcvd_or_zero == target_blob.size())
1318 {
1319 switch (msg_state)
1320 {
1322 rcv_on_head_payload(sync_err_code, sync_sz);
1323 return;
1325 {
1326 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Received all of payload 2 (meta-blob of length "
1327 "[" << m_rcv_target_meta_length << "]). Will register non-idle activity; "
1328 "and invoke handler (or report via sync-args).");
1329
1330 rcv_not_idle(); // Register activity <= end of complete message, no error.
1331
1332 const auto len = size_t(m_rcv_target_meta_length);
1333
1334 assert(!*sync_err_code);
1335 *sync_sz = len;
1336
1340 m_rcv_stats.m_histo_payload_sz.record_value(len);
1341 if (!m_rcv_user_request->m_own_hndl.get().null())
1342 {
1344 }
1345 return;
1346 }
1348 assert(false && "rcv_read_blob() shall be used only for S_*_PAYLOAD phases.");
1349 }
1350 assert(false && "Should not get here.");
1351 } // if (n_rcvd_or_zero == target_blob.size())
1352 // else if (n_rcvd_or_zero != target_blob.size()):
1353
1354 // Still have to finish reading into target_blob. We've already hit would-block though.
1355 if (m_rcv_user_request->m_on_done_func.empty())
1356 {
1357 /* Corner case. They want us to report the would-block synchronously (as normal) but abandon the
1358 * async-read (async_receive_batch_emulation() code-path only). So any *following* async-read will
1359 * need to start the state machine already mid-message. */
1360
1361 FLOW_LOG_TRACE("Got would-block after only partially reading the requested blob. "
1362 "This is part of a sub-op (as of this writing when emulating "
1363 "batch-receive using single-receives) in which would-block means we end the op and do *not* "
1364 "await readability; so we emit would-block and stop the overall receive op. However saving "
1365 "the partial-message info, and any subsequent receive-op will resume from here.");
1366
1367 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
1368 *sync_sz = 0;
1369
1370 /* To resume the state machine later, we have to in a sense "replay" what has happened so far this time around
1371 * (which we shall abandon this time around). It might help here to look at rcv_on_handle_finalized(), in a
1372 * similar spot -- but in our case depending on (at least) msg_state we possibly have more to replay than
1373 * just that. Anyway rcv_resume_incomplete_msg_processing() will do the right thing; we just have to tell
1374 * it where we had to stop mid-message. */
1375
1377 && "This corner case should only happen after already banking 1+ user in-message, "
1378 "so protocol negotiation should have already succeeded.");
1380 && "This should have been nullified at the start of the async-receive.");
1381
1382 if (msg_state == Rcv_msg_state::S_HEAD_PAYLOAD)
1383 {
1384 // We stopped in the middle of reading into m_rcv_target_meta_length.
1386 = [this,
1387 // As in rcv_on_handle_finalized(): Can't capture uncopyable thing; gotta do this dance.
1388 owned_hndl_or_null
1389 = boost::make_shared<Own_native_handle>(std::move(m_rcv_user_request->m_own_hndl)),
1390 target_meta_length_incomplete = m_rcv_target_meta_length,
1391 target_meta_length_incomplete_n_rcvd = sizeof(m_rcv_target_meta_length)
1392 - target_blob.size() + n_rcvd_or_zero]
1393 (Error_code* err_code, size_t* sz)
1394 {
1395 rcv_resume_incomplete_msg_processing(err_code, sz, std::move(*owned_hndl_or_null),
1396 target_meta_length_incomplete,
1397 target_meta_length_incomplete_n_rcvd, {});
1398 };
1399 assert(m_rcv_user_request->m_own_hndl.get().null()
1400 && "The move-from should have nullified m_own_hndl.");
1401
1402 return;
1403 } // if (msg_state == HEAD_PAYLOAD)
1404 // else if (msg_state == META_BLOB_PAYLOAD):
1405
1406 /* We got all of m_rcv_target_meta_length, but only N (not all m_rcv_target_meta_length) of the following
1407 * meta-blob. N may be 0. */
1408
1409 Blob target_blob_incomplete{get_logger()};
1410 // This is N.
1411 const auto target_blob_incomplete_sz = size_t(m_rcv_target_meta_length)
1412 - target_blob.size() + n_rcvd_or_zero;
1413 if (target_blob_incomplete_sz != 0)
1414 {
1415 /* Rare (in fact only, as of this writing) receive-direction meta-blob copy.
1416 * (Well, also, m_rcv_resume_incomplete_msg_processing_func() will copy again -- out of it into the next
1417 * async-receive's user target buffer.)
1418 * We expect this entire situation to be quite rare, so the perf impact is likely negligible.
1419 * There really is no choice; this is the user's buffer, and we're about to report we received no data
1420 * into it, and the user is allowed to destroy/repurpose that memory area. */
1421 target_blob_incomplete.assign_copy(Blob_const{m_rcv_user_request->m_target_meta_blob.data(),
1422 target_blob_incomplete_sz});
1423 }
1425 = [this,
1426 // As in rcv_on_handle_finalized(): Can't capture uncopyable thing; gotta do this dance.
1427 owned_hndl_or_null
1428 = boost::make_shared<Own_native_handle>(std::move(m_rcv_user_request->m_own_hndl)),
1429 target_meta_length = m_rcv_target_meta_length,
1430 target_blob_incomplete = std::move(target_blob_incomplete)] // Move-capture it (don't copy again).
1431 (Error_code* err_code, size_t* sz)
1432 {
1433 rcv_resume_incomplete_msg_processing(err_code, sz, std::move(*owned_hndl_or_null),
1434 target_meta_length, 0, target_blob_incomplete);
1435 };
1436 assert(m_rcv_user_request->m_own_hndl.get().null()
1437 && "The move-from should have nullified m_own_hndl.");
1438
1439 return;
1440 } // if (m_rcv_user_request->m_on_done_func.empty())
1441 /* else if (!m_rcv_user_request->m_on_done_func.empty()):
1442 *
1443 * Mainstream case: Report would-block synchronously (as normal) but don't abandon the async-receive.
1444 * Await readability; then resume once we think socket readable. So,
1445 * much like in rcv_read_msg_from_byte_stream() (keeping comments light): */
1446
1447 FLOW_LOG_TRACE("Do not have all of requested payload; got would-block. Awaiting readability.");
1448
1449 {
1450 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
1451
1452 if (m_peer_socket)
1453 {
1455 false, // Wait for read.
1456 // Once readable do this:
1457 boost::make_shared<Task>([this, msg_state,
1458 n_left = target_blob.size() - n_rcvd_or_zero]()
1459 {
1460 rcv_on_ev_peer_socket_byte_stream_readable_or_error(msg_state, n_left);
1461 }));
1462
1463 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
1464 *sync_sz = 0;
1465 return;
1466 }
1467 // else:
1468 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
1469
1471 } // if (!m_rcv_pending_err_code) (but may have become truthy inside, and has unless we `return`ed inside)
1472
1473 assert(m_rcv_pending_err_code);
1474
1475 *sync_err_code = m_rcv_pending_err_code;
1476 *sync_sz = 0;
1477} // Native_socket_stream_impl::rcv_read_blob()
1478
1480 (Error_code* err_code, size_t* sz, util::Own_native_handle&& hndl_or_null,
1481 Native_socket_stream_cfg::low_lvl_payload_blob_length_t target_meta_length_possibly_incomplete,
1482 size_t target_meta_length_incomplete_n_rcvd_or_zero_if_complete,
1483 const flow::util::Blob& target_blob_incomplete)
1484{
1485 using util::Blob_mutable;
1486
1487 assert(m_rcv_user_request && "We should be called from top-level-ish async-receive....");
1488 assert((!m_rcv_pending_err_code) && "Any cached error should have been emitted already.");
1489 assert(m_rcv_resume_incomplete_msg_processing_func.empty() && "It should be a new day!");
1490
1491 // Replay the fact we may have gotten a handle. Same deal as in rcv_on_handle_finalized(); keeping comments light.
1492 if ((!hndl_or_null.get().null()) && (!m_rcv_user_request->m_target_hndl_ptr))
1493 {
1494 FLOW_LOG_WARNING("Socket stream [" << *this << "]: While replaying: User async-receive request for "
1495 "*only* a meta-blob: but earlier-received Native_handle is non-null which is "
1496 "unexpected; emitting error.");
1498 // @todo See to-do near GOT_NON_BLOB handling in rcv_on_handle_finalized().
1499 *sz = 0;
1500 return;
1501 } // if (hndl_or_null && (!m_rcv_user_request->m_target_hndl_ptr))
1502 // else
1503 if (m_rcv_user_request->m_target_hndl_ptr)
1504 {
1505 m_rcv_user_request->m_own_hndl = std::move(hndl_or_null);
1506 }
1507
1508 /* In target_meta_length_possibly_incomplete, we have either all of m_rcv_target_meta_length; or just 1+ but not
1509 * all of the bytes of it. Either way copy it over, including the possibly-garbage trailing bytes. */
1510 m_rcv_target_meta_length = target_meta_length_possibly_incomplete;
1511
1512 if (target_meta_length_incomplete_n_rcvd_or_zero_if_complete != 0)
1513 {
1514 /* We have only some of it. Replayed. Now resume from there.
1515 * This is the core of what rcv_on_ev_peer_socket_byte_stream_readable_or_error(HEAD_PAYLOAD) would do.
1516 * @todo Some code reuse might be nice... though it might also just increase code verbosity. */
1518 Blob_mutable{static_cast<uint8_t*>(static_cast<void*>(&m_rcv_target_meta_length))
1519 + target_meta_length_incomplete_n_rcvd_or_zero_if_complete,
1521 - target_meta_length_incomplete_n_rcvd_or_zero_if_complete},
1522 err_code, sz);
1523 return;
1524 }
1525 /* else if (target_meta_length_incomplete_n_rcvd_or_zero_if_complete == 0): We have all of it. Continue replaying.
1526 *
1527 * We have saved a copy of the completed part of the meta-blob. (It might be empty; that is fine.) */
1528
1529 assert((m_rcv_target_meta_length != 0)
1530 && "Got all of m_rcv_target_meta_length, and it is zero, yet instructed to finish reading meta-blob... "
1531 "but there should have been none; the message should have been completed; and we should not have "
1532 "been invoked at all. Bug on someone's part?");
1533
1534 if (!target_blob_incomplete.empty())
1535 {
1536 target_blob_incomplete.sub_copy(target_blob_incomplete.begin(),
1537 Blob_mutable{m_rcv_user_request->m_target_meta_blob.data(),
1538 target_blob_incomplete.size()});
1539 }
1540
1541 /* Replayed. Now resume from there.
1542 * This is the core of what rcv_on_ev_peer_socket_byte_stream_readable_or_error(META_BLOB_PAYLOAD) would do.
1543 * @todo Some code reuse might be nice... though it might also just increase code verbosity. */
1545 Blob_mutable{static_cast<uint8_t*>(m_rcv_user_request->m_target_meta_blob.data())
1546 + target_blob_incomplete.size(),
1548 - target_blob_incomplete.size()},
1549 err_code, sz);
1550} // Native_socket_stream_impl::rcv_resume_incomplete_msg_processing()
1551
1553 size_t* sync_sz)
1554{
1555 using flow::util::Lock_guard;
1556 using util::Task;
1557
1558 /* Special mode; it's invoked only for perf reasons so fast-track through the various possibilities of what would
1559 * happen if !assume_would_block => actual nb-read => would-block. This decreases maintainability but is nice for
1560 * perf. Keeping comments light; see the !assume_would_block paths for those. @todo Revisit.
1561 *
1562 * A nice thing is that particular path is nearly identical whether we're using Protocol_pkt_stream or the
1563 * generally more annoying Protocol_byte_stream. So we can code this common-to-both helper which just
1564 * diverges in one `if constexpr()` below. */
1565
1566 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
1567
1568 if (m_peer_socket)
1569 {
1570 if (!m_rcv_user_request->m_on_done_func.empty())
1571 {
1572 FLOW_LOG_TRACE("Got nothing but would-block (pre-assumed). Awaiting readability.");
1573
1574 m_rcv_ev_wait_func(&m_ev_wait_hndl_peer_socket, false, boost::make_shared<Task>([this]()
1575 {
1577 {
1579 }
1580 else
1581 {
1583 0 /* ignored for S_MSG_START */);
1584 }
1585 }));
1586 }
1587 // else { Corner case. }
1588
1589 *sync_err_code = error::Code::S_SYNC_IO_WOULD_BLOCK;
1590 }
1591 else
1592 {
1593 FLOW_LOG_WARNING("Socket stream [" << *this << "]: User async-receive request (pre-assumed would-block): "
1594 "was about to await readability but discovered opposite-direction socket-hosing error; "
1595 "emitting error via completion handler (or via sync-args).");
1597 }
1598 *sync_sz = 0;
1599} // Native_socket_stream_impl::rcv_read_msg_from_stream_having_assumed_would_block()
1600
1602 (util::Own_native_handle* target_payload_hndl,
1603 const util::Blob_mutable& target_payload_blob1, const util::Blob_mutable& target_payload_blob2_or_none,
1604 Error_code* err_code)
1605{
1607 using util::Blob_mutable;
1608 using flow::util::Lock_guard;
1609 using boost::array;
1610
1611 /* @todo This is somewhat similar to rcv_nb_read_low_lvl_payload_from_byte_stream(), but only in little pieces.
1612 * Try to code-reuse without sacrificing any perf; or maybe avoid duplicating comments (though, the dupe comments
1613 * are not long). */
1614
1616 && "@todo Really this function should be made to not compile in this case, and this then should be "
1617 "static_assert() rather than run-time.");
1618 assert(err_code);
1619 assert(target_payload_hndl && "Our contract is we must have a target Native_handle; rationale is in our doc header.");
1620
1621 // We comment liberally, but tactically, inline; but please read the strategy in the class doc header's impl section.
1622
1623 /* Result semantics (reminder of what we promised in contract):
1624 * Success in non-blockingly receiving blob(s) and handle-or-none: > 0; falsy *err_code.
1625 * USE_OS_DGRAM_SUPPORT => Either we got that, or we got nothing; no "partial" success possible.
1626 * No success in receiving blob(s)+handle, because it would-block (not fatal): == 0; falsy *err_code.
1627 * No success in receiving blob(s)+handle, because fatal error: == 0, truthy *err_code. */
1628 size_t n_rcvd_or_zero = 0;
1629
1630 // m_*peer_socket = (the only) shared data between our- and opposite-direction code. Must lock (see class docs).
1631 {
1632 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
1633
1634 if (m_peer_socket)
1635 {
1636 /* Per contract, nb_read_some_with_native_handle() is identical to setting non_blocking(true) and attempting
1637 * .receive(<buffer sequence below>) -- except if it's able to receive an in-dgram, it'll also have set the
1638 * target handle to either null (none present) or non-null; and it detects having to truncate the in-dgram
1639 * due to target_payload_blob1 + target_payload_blob2_or_none being in sum too small (it'll emit
1640 * S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE in that case, as we advertised).
1641 *
1642 * Perf subtlety: If target_payload_blob2_or_none is "none" (empty), then specifying target_payload_blob1
1643 * alone (as opposed to a 2-container with an empty 2nd element) subtly chooses a compile-time-faster template
1644 * impl of nb_read_some_with_native_handle(). So do that if relevant, even though the code is less
1645 * elegant-looking here. Also the best choice (for another compile-time-decided optimization) for the
1646 * 2-container (if relevant) is {std|boost}::array<2>. So use that if relevant. */
1647 {
1648 Native_handle hndl;
1649 if (target_payload_blob2_or_none.size() == 0)
1650 {
1651 n_rcvd_or_zero = nb_read_some_with_native_handle<Native_socket_stream_cfg::Protocol>
1652 (get_logger(), m_peer_socket.get(), &hndl, target_payload_blob1, err_code);
1653 }
1654 else
1655 {
1656 array<Blob_mutable, 2> target_buf_seq = { target_payload_blob1, target_payload_blob2_or_none };
1657 n_rcvd_or_zero = nb_read_some_with_native_handle<Native_socket_stream_cfg::Protocol>
1658 (get_logger(), m_peer_socket.get(), &hndl, target_buf_seq, err_code);
1659 }
1660 // That should have TRACE-logged stuff, so we won't (it's our function).
1661
1662 target_payload_hndl->reset(std::move(hndl));
1663 }
1664
1665 /* Almost home free; but our result semantics are a little different from the low-level-read functions'.
1666 *
1667 * Plus, if we just discovered the connection is hosed, do whatever's needed with that. */
1668 assert(((!*err_code) && (n_rcvd_or_zero != 0))
1669 || (*err_code && (n_rcvd_or_zero == 0)));
1670 if (*err_code == boost::asio::error::would_block)
1671 {
1672 err_code->clear();
1673 // *err_code is falsy; n_rcvd_or_zero == 0; cool.
1674 }
1675 else if (*err_code && (*err_code != error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE))
1676 {
1677 /* True-blue system error. Kill off *m_peer_socket (connection hosed). We could simply nullify it, which would
1678 * give it back to the system (it's a resource), but see m_peer_socket_hosed doc header for explanation as
1679 * to why we cannot, and why instead we transfer it to "death row" until dtor executes, at which
1680 * point ::close(m_peer_socket_hosed->native_handle()) will actually happen. */
1681
1682 assert((!m_peer_socket_hosed) && "m_peer_socket_hosed must start as null and only become non-null once. Bug?");
1684 assert((!m_peer_socket) && "Shocking unique_ptr misbehavior!");
1685
1686 // *err_code is truthy; n_rcvd_or_zero == 0; cool.
1687 }
1688 /* else if (!*err_code) { *err_code is falsy; n_rcvd_or_zero >= 1; cool. }
1689 *
1690 * else if (*err_code == MESSAGE_SIZE_EXCEEDS_USER_STORAGE)
1691 * { *err_code is truthy; n_rcvd_or_zero == 0; and to our user the *in*-direction pipe is likely hosed --
1692 * our caller shall set m_rcv_pending_err_code accordingly, and that's that. However, we choose *not*
1693 * to hose m_peer_socket, and therefore the *out*-direction pipe continues to operate if desired. }
1694 *
1695 * Why do we choose that? Answer: See comment in similar spot in rcv_read_batch_from_pkt_stream(), where
1696 * it discusses MESSAGE_SIZE_EXCEEDS_USER_STORAGE but also graceful-close RECEIVES_FINISHED_CANNOT_RECEIVE
1697 * and `eof`. (That comment covers more situations than we have here, so that's why the "pointee" comment
1698 * is there, while the "pointer" is here.) In our case, that comment covers these situations relevant to
1699 * the present function:
1700 * - Why we don't hose m_peer_socket/`*this` here on MESSAGE_SIZE_EXCEEDS_USER_STORAGE.
1701 * - Why we *do* hose it here on native `eof`.
1702 * The RECEIVES_FINISHED_CANNOT_RECEIVE case is handled one level up, in rcv_on_dgram() (as it's not
1703 * emitted by low-level-transmit APIs as an error but rather encoded in the dgram by opposing *end_sending()). */
1704 } // if (m_peer_socket)
1705 else // if (!m_peer_socket)
1706 {
1708 }
1709 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
1710
1711 assert((!*err_code)
1712 || (n_rcvd_or_zero == 0)); // && *err_code
1713
1714 if (*err_code)
1715 {
1716 FLOW_LOG_TRACE("Received nothing due to error [" << *err_code << "] [" << err_code->message() << "].");
1717 }
1718 else
1719 {
1720 FLOW_LOG_TRACE("Receive: no error. Was able to receive [" << n_rcvd_or_zero << "] of "
1721 "[" << (target_payload_blob1.size() + target_payload_blob2_or_none.size()) << "] bytes; "
1722 "interest in native handle: got [" << target_payload_hndl->get() << "].");
1723 } // else if (!*err_code)
1724
1725 return n_rcvd_or_zero;
1726} // Native_socket_stream_impl::rcv_nb_read_low_lvl_payload_from_pkt_stream()
1727
1728template<typename Ignored> // See below. Technicalities therein aside -- feel free to ignore this line.
1730 (util::Own_native_handle* target_payload_hndl_or_null,
1731 const util::Blob_mutable& target_payload_blob, Error_code* err_code)
1732{
1734 using flow::util::Lock_guard;
1735
1736 /* @todo This is somewhat similar to rcv_nb_read_low_lvl_payload_from_pkt_stream(), but only in little pieces.
1737 * Try to code-reuse without sacrificing any perf; or maybe avoid duplicating comments (though, the dupe comments
1738 * are not long). */
1739
1741 && "Really this function should be made to not compile in this case, and this then should be "
1742 "static_assert() rather than run-time. There is a @todo lower-down in this function to that effect.");
1743 assert(err_code);
1744
1745 // We comment liberally, but tactically, inline; but please read the strategy in the class doc header's impl section.
1746
1747 /* Result semantics (reminder of what we promised in contract):
1748 * Partial or total success in non-blockingly receiving blob and handle-or-none: > 0; falsy *err_code.
1749 * No success in receiving blob+handle, because it would-block (not fatal): == 0; falsy *err_code.
1750 * No success in receiving blob+handle, because fatal error: == 0, truthy *err_code. */
1751 size_t n_rcvd_or_zero = 0;
1752
1753 // m_*peer_socket = (the only) shared data between our- and opposite-direction code. Must lock (see class docs).
1754 {
1755 Lock_guard<decltype(m_peer_socket_mutex)> peer_socket_lock{m_peer_socket_mutex};
1756
1757 if (m_peer_socket)
1758 {
1759 if (target_payload_hndl_or_null)
1760 {
1761 /* Per contract, nb_read_some_with_native_handle() is identical to setting non_blocking(true) and attempting
1762 * m_peer_socket->receive(target_payload_blob) -- except if it's able to receive even 1 byte it'll also
1763 * have set the target handle to either null (none present) or non-null.
1764 * When we say identical we mean identical result semantics along with everything else. */
1765 Native_handle hndl;
1766 n_rcvd_or_zero = nb_read_some_with_native_handle<Native_socket_stream_cfg::Protocol>
1767 (get_logger(), m_peer_socket.get(), &hndl, target_payload_blob, err_code);
1768 // That should have TRACE-logged stuff, so we won't (it's our function).
1769
1770 target_payload_hndl_or_null->reset(std::move(hndl));
1771 } // if (target_payload_hndl_or_null)
1772 else // if (!target_payload_hndl_or_null)
1773 {
1774 /* No interest in receiving a handle, so we can just use boost.asio's normal non-blocking read
1775 * (non_blocking(true), receive()). */
1776
1777 // First set non-blocking mode... same deal as in snd_nb_write_low_lvl_payload(); keeping comments light.
1778 if (!m_peer_socket->non_blocking())
1779 {
1780 FLOW_LOG_TRACE("Socket stream [" << *this << "]: Setting boost.asio peer socket non-blocking mode.");
1781 m_peer_socket->non_blocking(true, *err_code); // Sets *err_code to success or the triggering error.
1782 }
1783 else // if (already non-blocking)
1784 {
1785 err_code->clear();
1786 }
1787
1788 if (!*err_code)
1789 {
1790 assert(m_peer_socket->non_blocking());
1791
1792 FLOW_LOG_TRACE("Reading low-level blob directly via boost.asio (blob details logged above hopefully).");
1793
1794 /* (Without the `if constexpr()` it would try to compile a call to
1795 * Protocol_pkt_stream::socket::receive
1796 * (<signature that exists only for Protocol_byte_stream::socket::receive()>) => compile error.
1797 * even though the present method would never be called.) */
1799 {
1800 /* Here we want to just:
1801 * n_rcvd_or_zero = m_peer_socket->receive(target_payload_blob, 0, *err_code);
1802 * Unfortunately due to extremely difficult rules about when a discarded branch of `if constexpr()`
1803 * is still semantically-checked, and to what extent, that would still lead to the compile error.
1804 * By placing the present code in a fake template (parameterized on `typename Ignored = void`) *and*
1805 * wrapping the call in a template-specialization-via-auto-taking-lambda, we can get the compiler
1806 * (clang, gcc, various versions each) to forego instantiating the "problem" code when semantically
1807 * checking it. Note that any decent optimizer will in the end still generate the equivalent of
1808 * the above desired statement, inlined.
1809 *
1810 * The following would be probably more "direct," but at least some compilers will "see through it"
1811 * and give a compile error still:
1812 * n_rcvd_zero = invoke(&Protocol_byte_stream::sock::receive, m_peer_socket.get(),
1813 * target_payload_blob, 0, *err_code);
1814 * So we'll settle for the funky lambda-with-auto-arg thing. */
1815 n_rcvd_or_zero =
1816 ([&](auto& sock) -> auto { return sock->receive(target_payload_blob, 0, *err_code); })
1817 (m_peer_socket);
1818 }
1819 else
1820 {
1821 assert(false && "We are not a template so would have been compiled even though USE_OS_DGRAM_SUPPORT; "
1822 "but we should not have been *called* ever.");
1823 /* @todo There is surely a way to structure our compile-time-diverged code-paths using certain C++
1824 * techniques (templates would be surely involved) so that the code in this method is never checked
1825 * by the compiler (beyond syntax), unless !USE_OS_DGRAM_SUPPORT. The same applies to a few other
1826 * places in Flow-IPC with similar properties. It would not be an algorithmic improvement, but the
1827 * code would be perhaps cleaner; with unnecessary code paths not being instantiated at all.
1828 * Warning: Getting that to work requires a keen understanding of some hairiness such as
1829 * [ https://en.cppreference.com/w/cpp/language/if.html#Constexpr_if ]
1830 * which I (ygoldfel) assure you is even tougher than it looks. */
1831 }
1832 }
1833 // else if (*err_code) { *err_code is truthy; n_rcvd_or_zero == 0; cool. }
1834 } // else if (!target_payload_hndl_or_null)
1835
1836 /* Almost home free; but our result semantics are a little different from the low-level-read functions'.
1837 *
1838 * Plus, if we just discovered the connection is hosed, do whatever's needed with that. */
1839 assert(((!*err_code) && (n_rcvd_or_zero != 0))
1840 || (*err_code && (n_rcvd_or_zero == 0)));
1841 if (*err_code == boost::asio::error::would_block)
1842 {
1843 err_code->clear();
1844 // *err_code is falsy; n_rcvd_or_zero == 0; cool.
1845 }
1846 else if (*err_code)
1847 {
1848 /* True-blue system error. Kill off *m_peer_socket (connection hosed). We could simply nullify it, which would
1849 * give it back to the system (it's a resource), but see m_peer_socket_hosed doc header for explanation as
1850 * to why we cannot, and why instead we transfer it to "death row" until dtor executes, at which
1851 * point ::close(m_peer_socket_hosed->native_handle()) will actually happen. */
1852
1853 assert((!m_peer_socket_hosed) && "m_peer_socket_hosed must start as null and only become non-null once. Bug?");
1855 assert((!m_peer_socket) && "Shocking unique_ptr misbehavior!");
1856
1857 // *err_code is truthy; n_rcvd_or_zero == 0; cool.
1858 }
1859 // else if (!*err_code) { *err_code is falsy; n_rcvd_or_zero >= 1; cool. }
1860 } // if (m_peer_socket)
1861 else // if (!m_peer_socket)
1862 {
1864 }
1865 } // Lock_guard peer_socket_lock{m_peer_socket_mutex}
1866
1867 assert((!*err_code)
1868 || (n_rcvd_or_zero == 0)); // && *err_code
1869
1870 if (*err_code)
1871 {
1872 FLOW_LOG_TRACE("Received nothing due to error [" << *err_code << "] [" << err_code->message() << "].");
1873 }
1874 else
1875 {
1876 FLOW_LOG_TRACE("Receive: no error. Was able to receive [" << n_rcvd_or_zero << "] of "
1877 "[" << target_payload_blob.size() << "] bytes.");
1878 if (target_payload_hndl_or_null)
1879 {
1880 if (n_rcvd_or_zero != 0)
1881 {
1882 FLOW_LOG_TRACE("Interest in native handle; got [" << target_payload_hndl_or_null->get() << "].");
1883 }
1884 } // if (target_payload_hndl_or_null)
1885 else
1886 {
1887 FLOW_LOG_TRACE("No interest in native handle.");
1888 }
1889 } // else if (!*err_code)
1890
1891 return n_rcvd_or_zero;
1892} // Native_socket_stream_impl::rcv_nb_read_low_lvl_payload_from_byte_stream()
1893
1895{
1896 using flow::util::stat::print;
1897
1898 if (m_state == State::S_PEER)
1899 {
1900 FLOW_LOG_INFO("Socket stream [" << *this << "]: In context [" << context << "]: Stats: "
1901 "rcv[" << print(m_rcv_stats) << "].");
1902 }
1903 // else { It'd all be zeroes anyway. }
1904}
1905
1907{
1908 return state_peer("receive_meta_blob_max_size()") ? Native_socket_stream_cfg::S_MAX_META_BLOB_LENGTH : 0;
1909}
1910
1912{
1914}
1915
1917{
1918 return m_rcv_stats;
1919}
1920
1922{
1924}
1925
1927{
1928 return blob_receive_stats();
1929}
1930
1932{
1934}
1935
1936} // namespace ipc::transport::sync_io
uint16_t low_lvl_payload_blob_length_t
The type used to encode the internal meta-blob length/type-indicator for sync_io::Native_socket_strea...
static constexpr low_lvl_payload_blob_length_t S_PING_SENTINEL
For sync_io::Native_socket_stream and derivative(s): Value for the length/type-indicator field in eac...
static constexpr size_t S_MAX_META_BLOB_LENGTH
The maximum length of a blob that can be sent by the protocol internally used by sync_io::Native_sock...
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),...
int16_t proto_ver_t
Type sufficient to store a protocol version; positive values identify newer versions of a protocol; w...
bool compute_negotiated_proto_ver(proto_ver_t opposing_max_proto_ver, Error_code *err_code=nullptr)
Based on the presumably-just-received-from-opposing-side value of their local_max_proto_ver,...
proto_ver_t negotiated_proto_ver() const
Returns S_VER_UNKNOWN before compute_negotiated_proto_ver(); then either the positive version of the ...
static constexpr proto_ver_t S_VER_UNKNOWN
A proto_ver_t value, namely a negative one, which is a reserved value indicating "unknown version"; i...
void blob_receive_stats_reset()
See Native_socket_stream counterpart.
util::sync_io::Timer_event_emitter m_timer_worker
As typical in timer-needing sync_io-pattern-implementing objects, maintains a thread exclusively for ...
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...
util::Fine_duration m_rcv_idle_timeout
timeout from idle_timer_run() args; or zero() if not yet called.
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...
util::sync_io::Asio_waitable_native_handle m_rcv_ev_wait_hndl_idle_timer_fired_peer
Descriptor waitable by outside event loop async-waits – storing the same Native_handle as (and thus b...
void rcv_read_msg_from_byte_stream(bool assume_would_block, Error_code *sync_err_code, size_t *sync_sz)
Begins read chain (completing it as synchronously as possible, async-completing the rest) for the nex...
void rcv_on_ev_peer_socket_byte_stream_readable_or_error(Rcv_msg_state msg_state, size_t n_left)
Completion handler, from outside event loop via sync_io pattern, for the async-wait initiated by vari...
Blob_rcv_stats blob_receive_stats() const
See Native_socket_stream counterpart.
size_t rcv_nb_read_low_lvl_payload_from_pkt_stream(util::Own_native_handle *target_payload_hndl, const util::Blob_mutable &target_payload_blob1, const util::Blob_mutable &target_payload_blob2_or_none, Error_code *err_code)
Similar to rcv_nb_read_low_lvl_payload_from_byte_stream() but for Protocol_pkt_stream instead of Prot...
Function< void(Error_code *, size_t *)> m_rcv_resume_incomplete_msg_processing_func
Relevant only if Protocol_byte_stream was chosen (at compile-time) for m_peer_socket,...
flow::util::Timer m_rcv_idle_timer
Timer that fires rcv_on_ev_idle_timer_fired() (which hoses the in-pipe with idle timeout error) and i...
bool async_receive_impl(Native_handle *target_hndl_or_null, 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)
Body of both async_receive_native_handle() and async_receive_blob().
void rcv_on_ev_idle_timer_fired()
Handler for the async-wait, via util::sync_io::Timer_event_emitter, of the idle timer firing; if stil...
bool start_receive_native_handle_ops(util::sync_io::Event_wait_func &&ev_wait_func)
See Native_socket_stream counterpart.
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...
bool async_receive_blob(const util::Blob_mutable &target_blob, Error_code *sync_err_code, size_t *sync_sz, flow::async::Task_asio_err_sz &&on_done_func)
See Native_socket_stream counterpart.
size_t rcv_nb_read_low_lvl_payload_from_byte_stream(util::Own_native_handle *target_payload_hndl_or_null, const util::Blob_mutable &target_payload_blob, Error_code *err_code)
Utility that synchronously, non-blockingly attempts to read over m_peer_socket into the target blob a...
void rcv_on_ev_peer_socket_pkt_stream_readable_or_error()
Completion handler, from outside event loop via sync_io pattern, for the async-wait initiated by the ...
void rcv_read_blob(Rcv_msg_state msg_state, const util::Blob_mutable &target_blob, Error_code *sync_err_code, size_t *sync_sz)
A somewhat-general utility that continues read chain with the aim to complete the present in-message,...
size_t receive_meta_blob_max_size() const
See Native_socket_stream counterpart.
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...
Rcv_msg_state
Used to organize the incoming-direction state machine tactically, this indicates what part of payload...
@ S_HEAD_PAYLOAD
Reading payload 1, but at least byte 2: already have at least byte 1 and either the Native_handle or ...
@ S_MSG_START
Reading start of payload 1: already have nothing – no Native_handle; no 1st byte.
@ S_META_BLOB_PAYLOAD
Reading payload 2; M bytes remain, where M is in [1, N], and N is the meta-length encoded in precedin...
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...
void rcv_on_handle_finalized(util::Own_native_handle &&hndl_or_null, size_t n_rcvd, Error_code *sync_err_code, size_t *sync_sz)
Helper of rcv_read_msg_from_byte_stream() – it could have been inlined instead of a method but for re...
size_t receive_blob_max_size() const
See Native_socket_stream counterpart.
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.
void rcv_on_dgram(util::Own_native_handle &&hndl_or_null, size_t n_rcvd, Error_code *sync_err_code, size_t *sync_sz)
Reacts to an in-datagram (hence possible user in-message) having been received.
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 native_handle_receive_stats_reset()
See Native_socket_stream counterpart.
void rcv_read_msg_from_stream_having_assumed_would_block(Error_code *sync_err_code, size_t *sync_sz)
Helper for rcv_read_msg_from_{pkt|byte}_stream() that handles the assume_would_block == true case,...
Native_socket_stream_cfg::low_lvl_payload_blob_length_t m_rcv_target_meta_length
Direct-write target, encoding the message type of the message currently being received; and possibly ...
void rcv_on_head_payload(Error_code *sync_err_code, size_t *sync_sz)
Reacts to payload 1 having been completely received.
Blob_rcv_stats native_handle_receive_stats() const
See Native_socket_stream counterpart.
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...
util::sync_io::Timer_event_emitter::Timer_fired_read_end * m_rcv_idle_timer_fired_peer
Read-end of IPC-mechanism used by m_timer_worker to ferry timer-fired events from m_rcv_idle_timer to...
Error_code m_rcv_pending_err_code
The first and only connection-hosing error condition detected when attempting to low-level-read on m_...
bool idle_timer_run(util::Fine_duration timeout)
See Native_socket_stream counterpart.
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 start_receive_blob_ops(util::sync_io::Event_wait_func &&ev_wait_func)
See Native_socket_stream counterpart.
void rcv_read_msg_from_pkt_stream(bool assume_would_block, Error_code *sync_err_code, size_t *sync_sz)
Analogous to rcv_read_msg_from_byte_stream() but used if m_peer_socket is of socket-type Protocol_pkt...
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...
void rcv_resume_incomplete_msg_processing(Error_code *err_code, size_t *sz, util::Own_native_handle &&hndl_or_null, Native_socket_stream_cfg::low_lvl_payload_blob_length_t target_meta_length_possibly_incomplete, size_t target_meta_length_incomplete_n_rcvd_or_zero_if_complete, const flow::util::Blob &target_blob_incomplete)
Core of what is assigned, when necessary, to m_rcv_resume_incomplete_msg_processing_func; at start of...
stat::Blob_rcv_stats m_rcv_stats
Incoming-direction (receive-pipe) stats.
bool async_receive_native_handle(Native_handle *target_hndl, 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)
See Native_socket_stream counterpart.
static constexpr bool S_BLOB_UNDERFLOW_ALLOWED
Implements concept API; namely it is true.
void consume_timer_firing_signal(Timer_fired_read_end *read_end)
Must be called after a timer_async_wait()-triggered timer fired, before invoking that method for the ...
void timer_async_wait(flow::util::Timer *timer, Timer_fired_read_end *read_end)
To be used on a timer T returned by create_timer(), this is the replacement for the usual T....
size_t nb_read_some_with_native_handle(flow::log::Logger *logger_ptr, Peer_socket< Protocol > *peer_socket_ptr, Native_handle *target_payload_hndl_ptr, const Mutable_buffer_sequence &target_payload_blob, Error_code *err_code, int message_flags)
boost.asio extension similar to peer_socket->non_blocking(true); auto n = peer_socket->receive(target...
@ 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_LOW_LVL_INTERNAL_PROTOCOL_INVALID_HEADER
Unable to receive incoming traffic: Opposing side sent invalid internal per-message header.
@ 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_RECEIVER_IDLE_TIMEOUT
No messages (optional auto-pings or otherwise) have been received; optionally configured timeout exce...
@ S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE
User protocol-code mismatch: local user-provided storage cannot fit entire message received from oppo...
@ S_BLOB_RECEIVER_GOT_NON_BLOB
User protocol-code mismatch: local user expected blob only and no native handle; received at least th...
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::transport.
Function< void(Asio_waitable_native_handle *hndl_of_interest, bool ev_of_interest_snd_else_rcv, Task_ptr &&on_active_ev_func)> Event_wait_func
In sync_io pattern, concrete type storing user-supplied function invoked by pattern-implementing ipc:...
Native_handle disowned_native_handle(Own_native_handle &&src) noexcept
Returns Native_handle constructed by relieving an existing auto-closing Own_native_handle of its owne...
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::Fine_duration Fine_duration
Short-hand for Flow's Fine_duration.
Definition: util_fwd.hpp:114
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
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:112
boost::scope::unique_resource< Native_handle, Native_handle::Closer, Native_handle > Own_native_handle
A unique_resource wrapper for Native_handle useful to avoid descriptor leaks and double-closing; can ...
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Cumulative stats from the incoming-direction (receive) pipe of a Blob_receiver or Native_handle_recei...
uint64_t m_total_bytes
Total user blob bytes received (sum of in-message blob sizes).
uint64_t m_total_msgs
Total user messages fully received and delivered.
uint64_t m_idle_timeouts
Number of idle-timeout events. For a {Blob|Native}_handle_receiver as of this writing this cannot exc...
uint64_t m_total_low_lvl_bytes
Total low-level bytes received (user blob bytes plus protocol headers and framing).
uint64_t m_msgs_with_hndls
Number of messages received that carried at least one native handle. Currently at most 1 handle per m...
uint64_t m_auto_pings
Number of auto-pings received from the opposing sender.
flow::util::stat::Histogram_counter m_histo_payload_sz
Payload-size histogram for received user messages.
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.