Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
async_adapter_snd.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
25#include <flow/log/log.hpp>
26#include <flow/async/single_thread_task_loop.hpp>
27#include <boost/noncopyable.hpp>
28#include <cassert>
29#include <string>
30#include <utility>
31
33{
34
35// Types.
36
37/**
38 * Type that adapts a given PEER-state sync_io::Native_handle_sender or sync_io::Blob_sender *core* into
39 * the async-I/O-pattern Native_handle_sender or Blob_sender. State-mutating logic of the latter is forwarded
40 * to a `*this`; while trivial `const` (in PEER state) things like `.send_blob_max_size()` are forwarded directly to the
41 * core `sync_io::X`.
42 *
43 * Flow-IPC uses a `*this` to implement each of transport::Native_socket_stream (out-direction) and
44 * transport::Blob_stream_mq_sender; but this is a public API, as one can implement any custom
45 * transport::Blob_sender (et al) in terms of the corresponding custom sync_io::Blob_sender (et al) impl.
46 * It would be an advanced task but nevertheless fully supported/intended.
47 *
48 * @internal
49 * @see transport::Native_socket_stream_impl uses this for 99% of its outgoing-direction
50 * (PEER-state by definition) logic.
51 * @see transport::Blob_stream_mq_sender_impl uses this for 99% of its logic.
52 * @endinternal
53 *
54 * @see Async_adapter_receiver for the opposite-direction thing. E.g., transport::Native_socket_stream_impl
55 * uses that for 99% of its incoming-direction logic.
56 *
57 * @internal
58 * Impl
59 * ----
60 * ### Threads and thread nomenclature; locking ###
61 * Thread U, thread W... locking... just see those sections in transport::Native_socket_stream_impl class doc header.
62 * We adopt that nomenclature and logic. However, as we are concerned with only one direction (op-type),
63 * we only deal with code in either thread U or W concerned with that. The other-direction code -- if applicable
64 * (e.g., applicable for `Native_socket_stream` which deals with both over 1 socket connection; N/A
65 * for `Blob_stream_mq_*` which uses separate objects entirely) -- simply co-exists in the same thread W and "thread"
66 * U. (If `Native_socket_stream` wanted to, it could even parallelize stuff in thread W by using separate worker
67 * threads Ws and Wr. As of this writing it does not, but it could -- nothing in `*this` would change.)
68 *
69 * Note, again, that we have our own `m_mutex`. If there is an opposing-direction counterpart Async_adapter_receiver,
70 * then it has its own `m_mutex`; hence things can proceed concurrently.
71 *
72 * ### Impl design ###
73 * This is almost entirely subsumed by our `sync_io` core, Async_adapter_sender::Core, an instance of
74 * sync_io::Native_handle_sender or sync_io::Blob_sender. It has a send op-type (possibly among others), so we invoke
75 * its `"sync_io::*_sender::start_send_blob_ops()"` during our initialization. After that:
76 *
77 * The main method, send_native_handle() (and its degenerate version send_blob())
78 * lacks a completion handler and hence can be forwarded to `Core m_sync_io`... that's it. auto_ping() -- same deal.
79 *
80 * async_end_sending() takes a completion handler, however. Simple enough -- we could just capture
81 * this `on_done_func` (from the user arg) in a lambda, then when `m_sync_io` calls our internal handler
82 * (synchronously), we `post(on_done_func)` onto thread W, and that's it. However, that's not sufficient,
83 * as if they call our dtor before that can complete, then we are to invoke `on_done_func(E)` where E =
84 * operation-aborted (by our contract). So we save `on_done_func` into `m_end_sending_on_done_func_or_empty`
85 * instead of capturing it.
86 * @endinternal
87 *
88 * @tparam Core_t
89 * The `sync_io::X` type being adapted into async-I/O-pattern `X`.
90 */
91template<typename Core_t>
93 public flow::log::Log_context,
94 private boost::noncopyable
95{
96public:
97 // Types.
98
99 /// The `sync_io::X` type being adapted into async-I/O-pattern `X`.
100 using Core = Core_t;
101
102 // Constructors/destructor.
103
104 /**
105 * Constructs the adapter around `sync_io::X` object `*sync_io`.
106 *
107 * @param logger_ptr
108 * Logger to use for logging subsequently.
109 * @param log_pfx
110 * String that shall precede ~all logged messages (e.g., `lexical_cast<string>(x)`, where `x` is an `X`.)
111 * @param worker
112 * The worker thread loop of `X`. Background work, as needed, will be posted onto this
113 * "thread W." Note that `X` may (or may not) share this thread with unrelated tasks;
114 * for example `Native_socket_stream` uses it for both a `*this` (outgoing-direction)
115 * and an Async_adapter_receiver (incoming-direction). `worker` must already be `->start()`ed.
116 * @param sync_io
117 * The core object of `X`. It should have just (irreversibly) entered state PEER.
118 */
119 Async_adapter_sender(flow::log::Logger* logger_ptr, util::String_view log_pfx,
120 flow::async::Single_thread_task_loop* worker, Core* sync_io);
121
122 /**
123 * To be invoked after `->stop()`ping `*worker` (from ctor), as well as flushing any still-queued
124 * tasks in its `Task_engine` (via `.restart()` and `.poll()`), this satisfies the customer adapter
125 * dtor's contract which is to invoke any not-yet-fired completion handlers with special
126 * operation-aborted error code. In our case that is either nothing or 1 `async_end_sending()` completion
127 * handler. If applicable the dtor returns once that handler has completed in an unspecified thread
128 * that is not the calling thread.
129 */
131
132 // Methods.
133
134 /**
135 * See transport::Native_handle_sender counterpart. However, this one is `void`, as there is no way `*this` is not
136 * in PEER state (by definition).
137 *
138 * @param hndl_or_null
139 * See above.
140 * @param meta_blob
141 * See above.
142 * @param err_code
143 * See above.
144 */
145 void send_native_handle(Native_handle hndl_or_null, const util::Blob_const& meta_blob, Error_code* err_code);
146
147 /**
148 * See transport::Blob_sender counterpart. However, this one is `void`, as there is no way `*this` is not in PEER
149 * state (by definition).
150 *
151 * @param blob
152 * See above.
153 * @param err_code
154 * See above.
155 */
156 void send_blob(const util::Blob_const& blob, Error_code* err_code);
157
158 /**
159 * See transport::Native_handle_sender counterpart; or leave `on_done_func_or_empty.empty()` for
160 * transport::Native_handle_sender::end_sending().
161 *
162 * @param on_done_func_or_empty
163 * See above.
164 * @return See above.
165 */
166 bool async_end_sending(flow::async::Task_asio_err&& on_done_func_or_empty);
167
168 /**
169 * See transport::Native_handle_sender counterpart.
170 *
171 * @param period
172 * See above.
173 * @return See above.
174 */
175 bool auto_ping(util::Fine_duration period);
176
177 /**
178 * See Native_socket_stream counterpart.
179 * @return See Native_socket_stream counterpart.
180 */
182
183 /// See Native_socket_stream counterpart.
185
186 /**
187 * See Native_socket_stream counterpart.
188 * @return See Native_socket_stream counterpart.
189 */
191
192 /// See Native_socket_stream counterpart.
194
195private:
196 // Methods.
197
198 /**
199 * Handles the completion of `m_sync_io.async_end_sending()` operation whether synchronously or asynchronously.
200 * Can be invoked from thread U or thread W, and #m_mutex must be locked. #m_end_sending_on_done_func_or_empty
201 * must not be `.empty()`. Post-condition: it has been made `.empty()`, and a `move()`d version of it has
202 * posted onto thread W.
203 *
204 * @param err_code
205 * Result to pass to user.
206 */
207 void on_sync_io_end_sending_done(const Error_code& err_code);
208
209 // Data.
210
211 /// See `log_pfx` arg of ctor.
212 const std::string m_log_pfx;
213
214 /**
215 * The `on_done_func` argument to async_end_sending(), possibly `.empty()` if originally user invoked `end_sending()`,
216 * or if neither was used, or if it has fired. Note that if async_end_sending() does not complete
217 * before dtor executes, dtor will invoke the handler (unless `.empty()`) with operation-aborted code.
218 *
219 * It would not need to be a member -- could just be captured in lambdas while async_end_sending() is outstanding --
220 * except for the need to still invoke it with operation-aborted from dtor in the aforementioned case.
221 *
222 * Protected by #m_mutex.
223 */
224 flow::async::Task_asio_err m_end_sending_on_done_func_or_empty;
225
226 /**
227 * Protects #m_end_sending_on_done_func_or_empty and, more importantly, send-ops data of #m_sync_io.
228 * For example `send_*()` engages `Core::send_*()` in thread U which might
229 * add messages to its internal pending-out-messages-during-would-block queue; while
230 * a util::sync_io::Event_wait_func `on_active_ev_func()` invocation (on writable transport) as requested by
231 * #m_sync_io will invoke logic inside the latter, which might pop items from that queue
232 * and send them off -- all in thread W. (This information is provided for context, as formally it's a black
233 * box inside `m_sync_io`.)
234 */
235 mutable flow::util::Mutex_non_recursive m_mutex;
236
237 /// Single-thread worker pool for all internal async work. Referred to as thread W in comments.
238 flow::async::Single_thread_task_loop& m_worker;
239
240 /**
241 * The core #Core engine, implementing the `sync_io` pattern (see util::sync_io doc header).
242 * See our class doc header for overview of how we use it (the aforementioned `sync_io` doc header talks about
243 * the `sync_io` pattern generally).
244 *
245 * Thus, #m_sync_io is the synchronous engine that we use to perform our work in our asynchronous boost.asio
246 * loop running in thread W (#m_worker) while collaborating with user thread(s) a/k/a thread U.
247 * (Recall that the user may choose to set up their own event loop/thread(s) --
248 * boost.asio-based or otherwise -- and use their own equivalent of an #m_sync_io instead.)
249 */
251}; // class Async_adapter_sender
252
253// Template implementations.
254
255template<typename Core_t>
257 util::String_view log_pfx,
258 flow::async::Single_thread_task_loop* worker,
259 Core* sync_io) :
260 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
261 m_log_pfx(log_pfx),
262 m_worker(*worker),
263 m_sync_io(*sync_io)
264{
267 using flow::util::Lock_guard;
268
269 // We've just entered PEER state, so set up the send-ops.
270
271 /* Hook up the m_sync_io=>*this interaction. m_sync_io will use these callbacks to ask
272 * us to ->async_wait() on `Asio_waitable_native_handle`s for it.
273 *
274 * The *this=>m_sync_io interaction shall be our APIs, like send_blob(),
275 * simply invoking the same API in m_sync_io (m_sync_io.send_blob() for that example). */
276
277 /* (.start_send_native_handle_ops() would do the same thing, if it exists. If it exists, that's because it has
278 * both to satisfy two concepts -- for when the user uses the sync_io::X directly -- but we don't care about that;
279 * we know they are the same in this case; so just use the one we know exists for any X.) */
280#ifndef NDEBUG
281 const bool ok =
282#endif
283 m_sync_io.start_send_blob_ops([this](Asio_waitable_native_handle* hndl_of_interest,
284 bool ev_of_interest_snd_else_rcv,
285 Task_ptr&& on_active_ev_func)
286 {
287 /* We are in thread U or thread W; m_sync_io.<?>() has called its m_..._ev_wait_func();
288 * it has protected access to *hndl_of_interest as needed. */
289
290 FLOW_LOG_TRACE(m_log_pfx << ": Sync-IO send-ops event-wait request: "
291 "descriptor [" << hndl_of_interest->native_handle() << "], "
292 "writable-else-readable [" << ev_of_interest_snd_else_rcv << "].");
293
294 // They want this async_wait(). Oblige.
295 assert(hndl_of_interest);
296 hndl_of_interest->async_wait(ev_of_interest_snd_else_rcv
297 ? Asio_waitable_native_handle::Base::wait_write
298 : Asio_waitable_native_handle::Base::wait_read,
299 [this, on_active_ev_func = std::move(on_active_ev_func)]
300 (const Error_code& err_code)
301 {
302 // We are in thread W. Nothing is locked.
303
304 if (err_code == boost::asio::error::operation_aborted)
305 {
306 return; // Stuff is shutting down. GTFO.
307 }
308 // else
309
310 // They want to know about completed async_wait(). Oblige.
311
312 // Protect m_sync_io and m_* against send-ops (`send_*()`, *end_sending(), ...).
313 Lock_guard<decltype(m_mutex)> lock{m_mutex};
314
315 /* Inform m_sync_io of the event. This can synchronously invoke handler we have registered via m_sync_io
316 * API. In our case -- if indeed it triggers a handler -- it will
317 * have to do with async_end_sending() completion. */
318
319 (*on_active_ev_func)();
320 // (That would have logged sufficiently inside m_sync_io; let's not spam further.)
321
322 // Lock_guard<decltype(m_mutex)> lock(m_mutex): unlocks here.
323 }); // hndl_of_interest->async_wait()
324 }); // m_sync_io.start_send_blob_ops()
325 assert(ok);
326} // Async_adapter_sender::Async_adapter_sender()
327
328template<typename Core_t>
330{
331 using flow::async::Single_thread_task_loop;
332 using flow::async::reset_thread_pinning;
333 using flow::util::ostream_op_string;
334
335 /* Pre-condition: m_worker is stop()ed, and any pending tasks on it have been executed.
336 * Our promised job is to invoke any pending handlers with operation-aborted.
337 * The decision to do it from a one-off thread is explained in transport::Native_socket_stream_impl::~dtor()
338 * and used in a few places; so see that. Let's just do it.
339 * @todo It would be cool, I guess, to do it all in one one-off thread instead of potentially starting, like,
340 * 2 for some of our customers. Well, whatever. At least we can avoid it if we know there are no handlers
341 * to invoke; speaking of which there's just the one: */
342
343 if (!m_end_sending_on_done_func_or_empty.empty())
344 {
345 Single_thread_task_loop one_thread{get_logger(),
346 ostream_op_string("ASdDeinit-", m_log_pfx)};
347 one_thread.start([&]()
348 {
349 reset_thread_pinning(get_logger()); // Don't inherit any strange core-affinity. Float free.
350
351 FLOW_LOG_INFO(m_log_pfx << ": In transient snd-finisher thread: "
352 "Shall run pending graceful-sends-close completion handler.");
353 m_end_sending_on_done_func_or_empty(error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER);
354 FLOW_LOG_INFO("Transient finisher exiting.");
355 });
356 } // if (!m_end_sending_on_done_func_or_empty.empty())
357} // Async_adapter_sender::~Async_adapter_sender()
358
359template<typename Core_t>
361{
362 using flow::util::Lock_guard;
363
364 // We are in thread U (or thread W in a completion handler, but not concurrently).
365
366 /* See comments in send_native_handle(); we are just a somewhat degraded version of that.
367 * It's just that send_native_handle() might not even compile, if m_sync_io does not have that method.
368 * Logic, as pertains to us, is the same though. */
369 Lock_guard<decltype(m_mutex)> lock{m_mutex};
370#ifndef NDEBUG
371 const bool ok =
372#endif
373 m_sync_io.send_blob(blob, err_code);
374 assert(ok && "We are by definition in PEER state, and ctor starts send-ops; so that should never "
375 "return false. Bug somewhere?");
376}
377
378template<typename Core_t>
380 Error_code* err_code)
381{
382 using flow::util::Lock_guard;
383
384 // We are in thread U (or thread W in a completion handler, but not concurrently).
385
386 /* This one is particularly simple, as there is no async completion handler to invoke. So we essentially just
387 * forward it to m_sync_io.send_native_handle() with identical signature. The only subtlety -- and it is
388 * quite important -- is that we must lock m_mutex. That might seem odd if one does not remember that
389 * m_sync_io send-ops explicit API (.send_native_handle() in this case) must never be invoked concurrently
390 * with the start_send_*_ops()-passed Event_wait_func's `on_active_ev_func()` -- which might call
391 * on_sync_io_end_sending_done(). So we lock it, and elsewhere we lock it when calling on_active_ev_func().
392 *
393 * Specifically (though formally speaking we are just following the sync_io pattern, and technically the following
394 * info is about inside the black box that is m_sync_io), m_sync_io at least stores an out-queue of messages
395 * to send, non-empty in would-block conditions; send_native_handle() pushes items onto that queue, while
396 * on_active_ev_func() (on writable socket) pops items off it upon successfully sending them off over socket. */
397
398 Lock_guard<decltype(m_mutex)> lock{m_mutex};
399#ifndef NDEBUG
400 const bool ok =
401#endif
402 m_sync_io.send_native_handle(hndl, meta_blob, err_code);
403 assert(ok && "We are by definition in PEER state, and ctor starts send-ops; so that should never "
404 "return false. Bug somewhere?");
405} // Async_adapter_sender::send_native_handle()
406
407template<typename Core_t>
408bool Async_adapter_sender<Core_t>::async_end_sending(flow::async::Task_asio_err&& on_done_func_or_empty)
409{
410 using flow::util::Lock_guard;
411
412 // We are in thread U (or thread W in a completion handler, but not concurrently).
413
414 /* While the same comments about locking m_mutex apply, in addition to that:
415 * This is somewhat more complex than the other send-ops, as there is a completion handler. */
416
417 Lock_guard<decltype(m_mutex)> lock{m_mutex};
418
419 if (on_done_func_or_empty.empty())
420 {
421 return m_sync_io.end_sending();
422 }
423 // else
424
425 if (!m_end_sending_on_done_func_or_empty.empty())
426 {
427 FLOW_LOG_WARNING(m_log_pfx << ": async_end_sending(F) invoked; but we have a saved "
428 "completion handler -- which has not yet fired -- for it already, so it must be a dupe-call. "
429 "Ignoring.");
430 return false;
431 }
432 /* else: It might also be .empty() and still be a dupe call (if it has fired already -- then they called us again).
433 * m_sync_io.async_end_sending() will catch that fine. For now though: */
434
435 /* Save it (instead of capturing it in lambda) in case dtor runs before async-end-sending completes;
436 * it would then invoke it with operation-aborted code. */
437 m_end_sending_on_done_func_or_empty = std::move(on_done_func_or_empty);
438
439 Error_code sync_err_code;
440 const bool ok = m_sync_io.async_end_sending(&sync_err_code, [this](const Error_code& err_code)
441 {
442 /* We are in thread W. m_mutex is locked (by the handler inside the function inside ctor).
443 * This'll, inside, post the handler onto thread W. */
444 on_sync_io_end_sending_done(err_code);
445 });
446
447 if (!ok)
448 {
449 // False start (dupe call). It logged enough.
450 m_end_sending_on_done_func_or_empty.clear();
451 return false;
452 }
453 // else: It either finished synchronously; or it will finish asynchronously.
454
455 if (sync_err_code == error::Code::S_SYNC_IO_WOULD_BLOCK)
456 {
457 // Async-wait needed before we can complete. Live to fight another day.
458 return true;
459 }
460 /* else: Completed synchronously. Since there can be no async-end-sending chains, it's no big deal
461 * to just use on_sync_io_end_sending_done() again. */
462
463 FLOW_LOG_INFO(m_log_pfx << ": Sync-IO async-end-sending completed immediately. "
464 "Posting handler onto async-worker thread.");
465
466 on_sync_io_end_sending_done(sync_err_code);
467
468 return true;
469 // Lock_guard<decltype(m_mutex)> lock(m_mutex): unlocks here.
470} // Async_adapter_sender::async_end_sending()
471
472template<typename Core_t>
474{
475 using flow::util::Lock_guard;
476
477 /* We are in thread U or W. m_mutex is locked (as required for m_end_sending_on_done_func_or_empty
478 * and m_sync_io access). */
479
480 assert(err_code != boost::asio::error::operation_aborted);
481
482 FLOW_LOG_TRACE(m_log_pfx << ": Earlier async-wait => event active => snd-mutex lock => "
483 "on-active-event-func => sync_io module => here (on-end-sending-done handler). Or else "
484 "snd-mutex lock => no async-wait needed => here... (ditto).");
485
486 /* Just invoke user completion handler. post() it onto thread W. Why post() onto W?
487 * - If we are in thread U: It is plainly required, as we promised to invoke completion handlers
488 * from unspecified thread that isn't U.
489 * - If we are in thread W: To avoid recursive mayhem if they choose to call some other API
490 * inside on_done_func(), we queue it onto thread W by itself. */
491 m_worker.post([this, err_code, on_done_func = std::move(m_end_sending_on_done_func_or_empty)]()
492 {
493 // We are in thread W. Nothing is locked.
494 FLOW_LOG_TRACE(m_log_pfx << ": Invoking on-end-sending-done handler.");
495 on_done_func(err_code);
496 FLOW_LOG_TRACE("Handler completed.");
497 });
498
499 m_end_sending_on_done_func_or_empty.clear(); // Just in case move() didn't do it.
500} // Async_adapter_sender::on_sync_io_end_sending_done()
501
502template<typename Core_t>
504{
505 using flow::util::Lock_guard;
506
507 // Like send_native_handle() and others (keeping comments light).
508
509 Lock_guard<decltype(m_mutex)> lock{m_mutex};
510 return m_sync_io.auto_ping(period);
511}
512
513template<typename Core_t>
515{
516 using flow::util::Lock_guard;
517
518 Lock_guard<decltype(m_mutex)> lock{m_mutex};
519 return m_sync_io.blob_send_stats();
520}
521
522template<typename Core_t>
524{
525 using flow::util::Lock_guard;
526
527 Lock_guard<decltype(m_mutex)> lock{m_mutex};
528 m_sync_io.blob_send_stats_reset();
529}
530
531template<typename Core_t>
533{
534 using flow::util::Lock_guard;
535
536 Lock_guard<decltype(m_mutex)> lock{m_mutex};
537 return m_sync_io.native_handle_send_stats();
538}
539
540template<typename Core_t>
542{
543 using flow::util::Lock_guard;
544
545 Lock_guard<decltype(m_mutex)> lock{m_mutex};
546 m_sync_io.native_handle_send_stats_reset();
547}
548
549} // namespace ipc::transport::sync_io
Type that adapts a given PEER-state sync_io::Native_handle_sender or sync_io::Blob_sender core into t...
~Async_adapter_sender()
To be invoked after ->stop()ping *worker (from ctor), as well as flushing any still-queued tasks in i...
void send_native_handle(Native_handle hndl_or_null, const util::Blob_const &meta_blob, Error_code *err_code)
See transport::Native_handle_sender counterpart.
flow::async::Task_asio_err m_end_sending_on_done_func_or_empty
The on_done_func argument to async_end_sending(), possibly .empty() if originally user invoked end_se...
stat::Blob_snd_stats blob_send_stats() const
See Native_socket_stream counterpart.
void blob_send_stats_reset()
See Native_socket_stream counterpart.
Core_t Core
The sync_io::X type being adapted into async-I/O-pattern X.
void on_sync_io_end_sending_done(const Error_code &err_code)
Handles the completion of m_sync_io.async_end_sending() operation whether synchronously or asynchrono...
void native_handle_send_stats_reset()
See Native_socket_stream counterpart.
bool async_end_sending(flow::async::Task_asio_err &&on_done_func_or_empty)
See transport::Native_handle_sender counterpart; or leave on_done_func_or_empty.empty() for transport...
void send_blob(const util::Blob_const &blob, Error_code *err_code)
See transport::Blob_sender counterpart.
flow::util::Mutex_non_recursive m_mutex
Protects m_end_sending_on_done_func_or_empty and, more importantly, send-ops data of m_sync_io.
flow::async::Single_thread_task_loop & m_worker
Single-thread worker pool for all internal async work. Referred to as thread W in comments.
Core & m_sync_io
The core Core engine, implementing the sync_io pattern (see util::sync_io doc header).
const std::string m_log_pfx
See log_pfx arg of ctor.
bool auto_ping(util::Fine_duration period)
See transport::Native_handle_sender counterpart.
Async_adapter_sender(flow::log::Logger *logger_ptr, util::String_view log_pfx, flow::async::Single_thread_task_loop *worker, Core *sync_io)
Constructs the adapter around sync_io::X object *sync_io.
stat::Blob_snd_stats native_handle_send_stats() const
See Native_socket_stream counterpart.
Useful if using the sync_io pattern within a user event loop built on boost.asio (optionally with flo...
@ S_SYNC_IO_WOULD_BLOCK
A sync_io operation could not immediately complete; it will complete contingent on active async-wait ...
@ S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER
Async completion handler is being called prematurely, because underlying object is shutting down,...
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::transport.
boost::shared_ptr< Task > Task_ptr
Short-hand for ref-counted pointer to a Function<> that takes no arguments and returns nothing; in pa...
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
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Cumulative stats from the outgoing-direction (send) pipe of a Blob_sender or Native_handle_sender con...
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.