Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
native_socket_stream_acceptor.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
24#include <flow/error/error.hpp>
25#include <flow/common.hpp>
26#include <boost/move/make_unique.hpp>
27#include <cassert>
28#include <ostream>
29#include <variant>
30
31namespace ipc::transport
32{
33
34// Initializers.
35
36// It's a reference due to `static` things being initialized in unknown order before main().
37const Shared_name& Native_socket_stream_acceptor::S_RESOURCE_TYPE_ID = Sync_io_obj::S_RESOURCE_TYPE_ID;
38
39// Implementations.
40
42 const Shared_name& absolute_name_arg,
43 Error_code* err_code) :
44 flow::log::Log_context(logger_ptr, Log_component::S_TRANSPORT),
45 m_absolute_name(absolute_name_arg),
46 m_worker(get_logger(), // Start the 1 thread.
47 /* (Linux) OS thread name will truncate m_absolute_name to 15-5=10 chars here; high chance that'll include
48 * something decently useful; probably not everything though. It's a decent attempt. */
49 flow::util::ostream_op_string("NSSA-", m_absolute_name.str())),
50 m_next_peer_socket(*(m_worker.task_engine())) // Steady state: start it as empty, per doc header.
51{
54#ifndef NDEBUG
56#endif
59 using flow::error::Runtime_error;
60 using flow::async::reset_thread_pinning;
61 using boost::system::system_error;
62
63 /* For simplicity we'll just do all the work in thread W we're about to start. Whether to do some initial stuff
64 * in this thread or not doesn't matter much. We have promised that,
65 * upon our return -- no later -- the Native_socket_stream_acceptor
66 * will be listening (assuming no errors). So wait for that (the startup) to finish using start() arg. */
67 Error_code sys_err_code;
68
69 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Awaiting initial setup/listening in worker thread.");
70 m_worker.start([&]() // Execute all this synchronously in the thread.
71 {
72 reset_thread_pinning(get_logger()); // Don't inherit any strange core-affinity! Worker must float free.
73
74 auto const asio_engine = m_worker.task_engine();
75
76 FLOW_LOG_INFO("Acceptor [" << *this << "]: Starting (am in worker thread).");
77
78 const auto local_endpoint = endpoint_at_shared_name<Protocol>(get_logger(), m_absolute_name, &sys_err_code);
79 assert((local_endpoint == Endpoint{}) == bool(sys_err_code));
80 if (sys_err_code) // It logged.
81 {
82 return; // Escape the start() callback, that is.
83 }
84 // else
85
86 // Start a listening acceptor socket at that endpoint!
87
88 try
89 {
90 // Throws on error. (It's annoying there's no error-code-returning API; but it's normal in boost.asio ctors.)
91 m_acceptor.reset(new Acceptor{*asio_engine, local_endpoint});
92 // @todo Is reuse_addr appropriate? Do we run into the already-exists error in practice? Revisit.
93 }
94 catch (const system_error& exc)
95 {
96 assert(!m_acceptor);
97 FLOW_LOG_WARNING("Acceptor [" << *this << "]: Unable to open/bind/listen native local stream socket; could "
98 "be due to address/name clash; details logged below.");
99 sys_err_code = exc.code();
100 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
101 return; // Escape the start() callback, that is.
102 }
103
104 FLOW_LOG_INFO("Acceptor [" << *this << "]: "
105 "Successfully made endpoint and open/bind/listen-ed on it. Ready for connections.");
106
107 /* OK! Background-wait for the first incoming connection. As explained in m_next_peer_socket doc header
108 * m_next_peer_socket is empty/unconnected at entry to each such background-wait; set to connected state
109 * by boost.asio as it launches our callback; and moved/emptied again by that callback, as it starts the next
110 * background-wait. We have the just the one thread, so it's just a simple serial sequence. */
111
112 m_acceptor->async_accept(m_next_peer_socket,
113 [this](const Error_code& async_err_code)
114 {
115 // We are in thread W.
116 on_next_peer_socket_or_error(async_err_code);
117 });
118
119 assert(!sys_err_code); // Success.
120 }); // m_worker.start()
121
122 if (sys_err_code)
123 {
124 /* Just keep the thread going; even though it's not gonna be doing any listening.
125 * @todo We could stop it here no problem. And it could help something, somewhere, marginally to not have an
126 * extra thread around. But then this has to be coded for all over the place;
127 * didn't seem essential, particularly since these error conditions are highly irregular and likely to be very
128 * bad anyway. */
129
130 if (err_code)
131 {
132 *err_code = sys_err_code;
133 return;
134 }
135 // else
136 throw Runtime_error{sys_err_code, FLOW_UTIL_WHERE_AM_I_STR()};
137 }
138 // else
139 assert(!sys_err_code);
140 err_code && (*err_code = {});
141
142 FLOW_LOG_INFO("Acceptor [" << *this << "]: Ready for incoming connections.");
143} // Native_socket_stream_acceptor::Native_socket_stream_acceptor()
144
146{
147 using flow::async::Single_thread_task_loop;
148 using flow::async::reset_thread_pinning;
149 using flow::util::ostream_op_string;
150
151 // We are in thread U. By contract in doc header, they must not call us from a completion handler (thread W).
152
153 FLOW_LOG_INFO("Acceptor [" << *this << "]: Shutting down. Next acceptor socket will close; all our internal "
154 "async handlers will be canceled; and worker thread will be joined.");
155
156 // stop() logic is similar to what happens in Native_socket_stream_impl dtor. Keeping cmnts light.
157 m_worker.stop();
158 // Thread W is (synchronously!) no more.
159
160 // Post-stop() poll() logic is similar to what happens in Native_socket_stream_impl dtor. Keeping cmnts light.
161
162 FLOW_LOG_INFO("Acceptor [" << *this << "]: Continuing shutdown. Next we will run pending handlers from some "
163 "other thread. In this user thread we will await those handlers' completion and then return.");
164 Single_thread_task_loop one_thread{get_logger(), ostream_op_string("NSSADeinit-", m_absolute_name.str())};
165
166 one_thread.start([&]()
167 {
168 reset_thread_pinning(get_logger()); // Don't inherit any strange core-affinity. Float free.
169
170 FLOW_LOG_INFO("Acceptor [" << *this << "]: "
171 "In transient finisher thread: Shall run all pending internal handlers (typically none).");
172
173 const auto task_engine = m_worker.task_engine();
174 task_engine->restart();
175 const auto count = task_engine->poll();
176 if (count != 0)
177 {
178 FLOW_LOG_INFO("Acceptor [" << *this << "]: "
179 "In transient finisher thread: Ran [" << count << "] internal handlers after all.");
180 }
181 task_engine->stop();
182
183 FLOW_LOG_INFO("Acceptor [" << *this << "]: "
184 "In transient finisher thread: Shall run all pending user handlers (feeding operation-aborted).");
185
186 while (!m_pending_user_requests_q.empty())
187 {
188 FLOW_LOG_TRACE("Running a queued async-accept completion handler.");
192 FLOW_LOG_TRACE("User accept handler finished. Popped from user request deficit queue.");
193 } // while (!m_pending_user_requests_q.empty())
194
195 FLOW_LOG_INFO("Transient finisher exiting.");
196 }); // one_thread.start()
197 // Here thread exits/joins synchronously.
198} // Native_socket_stream_acceptor::~Native_socket_stream_acceptor()
199
201{
202 using flow::util::ostream_op_string;
203 using std::holds_alternative;
204
205 // We are in thread W.
206 if (sys_err_code == boost::asio::error::operation_aborted)
207 {
208 return; // Stuff is shutting down. GTFO.
209 }
210 // else
211 assert(sys_err_code != boost::asio::error::would_block); // Not possible for async handlers.
212
213 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Incoming connection, or error when trying to accept one.");
214 if (sys_err_code)
215 {
216 // Close/empty the potentially-almost-kinda-accepted socket. Probably unnecessary but can't hurt.
217 Error_code dummy;
218 m_next_peer_socket.close(dummy);
219
220 if (sys_err_code == boost::asio::error::connection_aborted)
221 {
222 FLOW_LOG_WARNING("Incoming connection aborted halfway during connection; this is quite weird but "
223 "should not be fatal. Ignoring. Still listening.");
224 // Fall through.
225 }
226 else
227 {
228 FLOW_LOG_WARNING("Acceptor [" << *this << "]: The background accept failed fatally. "
229 "Closing acceptor; no longer listening. Details follow.");
230 FLOW_ERROR_SYS_ERROR_LOG_WARNING();
231
232 // Close/destroy the acceptor. And of course don't call async_accept() on it again as we normally would below.
233 m_acceptor->close(dummy);
234
235 // Queue up and handle result.
236
237 /* Shouldn't have gotten here if any other error had been emitted. We would've closed m_acceptor and certainly
238 * not run m_acceptor->async_accept() again. */
239 assert(m_pending_results_q.empty() || (!holds_alternative<Error_code>(m_pending_results_q.back())));
240
241 // We are in steady state. Make this one change...
242 m_pending_results_q.push(sys_err_code);
243 // ...and immediately handle it appropriately to get back into steady state.
245
246 // Do *not* m_acceptor->async_accept() again. (It's closed actually, so we couldn't anyway.)
247 return;
248 }
249 } // if (sys_err_code)
250 else // if (!sys_err_code)
251 {
252 /* We'll enqueue the resulting peer socket handle from the new socket. There are a few subtleties:
253 * - We need to make a new Peer, which must be passed the boost.asio Peer_socket, or similar,
254 * to wrap. On that guy, they will internally call things like Peer_socket::async_read_some().
255 * - m_next_peer_socket, basically, stores two pieces of data: the raw native socket handle (of new peer socket),
256 * and the Task_engine that is to be used to execute the logic of all async_ calls on it (e.g.,
257 * Peer_socket::async_read_some()) (basically 1-1 to our m_worker).
258 * - Can we pass m_next_peer_socket into the Native_socket_stream ctor then? Well, no. Why? Answer:
259 * Suppose we did. Now suppose Native_socket_stream calls async_read_some() on it. Suppose some bytes are
260 * indeed read from the opposing peer. Now some callback internal to the Native_socket_stream must be called.
261 * On what thread would it be called? Answer: per above, on m_worker. Do we want that? Well, no, because
262 * that class sets up its own thread for that. (Briefly: Actually, why not use one thread? Answer: Even
263 * if we thought that was a good design for perf or something, consider that m_worker goes away in
264 * our dtor, so we'd have to actively share m_worker (maybe via shared_ptr) with the offshoot
265 * Native_socket_stream. Simply, we just aren't designing it that way. We want them to run an independent
266 * thread, though this is not where I will justify that.)
267 * - So what to do? I was hoping there'd be a move-constructor-ish thing in Peer_socket,
268 * but there isn't one that lets one also specify the Task_engine; and it isn't apparently possible to change
269 * a Peer_socket's Task_engine (thing returned by .get_executor()) after construction.
270 * Worst-case, we could suck out the native socket handle (see just above) and then Native_socket_stream
271 * ctor could construct and take over that guy. It should be entirely safe, since it is a new socket, and
272 * we haven't started any async ops on it yet, but it still feels dodgy. Turns out the safe way to do it
273 * is basically that, but one can Peer_socket::release() to "officially" safely "eject" an open
274 * socket. So we do that, and Peer_socket can take over the "ejected" native socket handle in
275 * its ctor.
276 * - We promise in our contract to propagate get_logger() to any child peer sockets. This is where it happens. */
277
278 /* .release() won't throw except in Windows <8.1, where it'll always throw (unsupported; per boost.asio docs).
279 * We don't worry about Windows generally; and anyway in .hpp somewhere we already should've ensured Linux in
280 * particular. Just re-check that for sanity for now. (This is a bit of future-proofing, so that the problem is
281 * obvious if porting the code.) */
282#ifndef FLOW_OS_LINUX
283 static_assert(false, "Should not have gotten to this line; should have required Linux; "
284 "the next thing assumes not-Win-<8.1.");
285#endif
286 // Could store a raw handle too, but this is exactly as fast and adds some logging niceties.
287 Native_handle native_peer_socket{m_next_peer_socket.release()};
288 assert(!m_next_peer_socket.is_open()); // Non-exhaustive sanity check that it's back in empty/unconnected state.
289 FLOW_LOG_TRACE("Acceptor [" << *this << "]: "
290 "Ejected ownership of new incoming peer socket [" << native_peer_socket << "].");
291
292 auto new_peer
293 = boost::movelib::make_unique<Peer>
294 (get_logger(),
295 // Nickname is, like, "_pathName_of_this_acceptor=>native_hndl[35]" (as I write this).
296 ostream_op_string(m_absolute_name.str(), "=>", native_peer_socket),
297 std::move(native_peer_socket));
298 // Caution: native_peer_socket is now invalid.
299 assert(native_peer_socket.null());
300
301 // Queue up and handle result.
302
303 // As above -- on error we wouldn't have kept trying to accept more.
304 assert(m_pending_results_q.empty() || (!holds_alternative<Error_code>(m_pending_results_q.back())));
305
306 // We are in steady state. Make this one change...
307 m_pending_results_q.emplace(std::move(new_peer)); // (new_peer may now be hosed.)
308 // ...and immediately handle it appropriately to get back into steady state.
310 } // else if (!sys_err_code)
311
312 // Either there was success (!sys_err_code), or a non-fatal error (otherwise). Keep the async chain going.
313
314 /* @todo Does it help perf-wise to spin through non-blocking accepts here (in case more incoming peers have been
315 * queued up by OS) until would-block? I (ygoldfel) have done it in the past when doing TCP/UDP reads, but I never
316 * really checked whether it's beneficial, and anyway this situation is not really the same (incoming load
317 * should be much less intense here). */
318
319 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Starting the next background accept.");
320 m_acceptor->async_accept(m_next_peer_socket,
321 [this](const Error_code& async_err_code)
322 {
323 // We are in thread W.
324 on_next_peer_socket_or_error(async_err_code);
325 });
326} // Native_socket_stream_acceptor::on_next_peer_socket_or_error()
327
329{
330 using boost::movelib::make_unique;
331 using std::get;
332 using std::holds_alternative;
333
334 // We are in thread U/W. (They *are* allowed to invoke async_accept() from within their completion handler.)
335
336 /* We don't lock our state, hence we do everything in thread W.
337 *
338 * If we are in thread U: Post on thread W.
339 *
340 * If we are in thread W already (being invoked from earlier user completion handler): Still post on thread W.
341 * Otherwise we may well invoke handler synchronously (if surplus is available at the moment) which would
342 * mean nested handler invocation, which we promised not to do (for good reason: if, say, their handler
343 * is bracketed by a non-recursive lock, then they would get a deadlock trying to acquire the lock in
344 * the 2nd -- inner -- handler execution). */
345 m_worker.post([this, target_peer, on_done_func = std::move(on_done_func)]
346 () mutable // To allow for the on_done_func to be move()d again.
347 {
348 // We are in thread W.
349
350 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Handling async-accept request.");
351
352 auto new_req = make_unique<User_request>();
353 new_req->m_target_peer = target_peer;
354 new_req->m_on_done_func = std::move(on_done_func);
355
356 // We are in steady state. Make this one change....
357 m_pending_user_requests_q.emplace(std::move(new_req)); // (new_req may now be hosed.)
358 // ...and immediately handle it appropriately to get back into steady state:
359
360 if (m_pending_results_q.empty())
361 {
362 FLOW_LOG_TRACE("Acceptor [" << *this << "]: New async-accept request pushed onto deficit queue; "
363 "but there is no surplus (no pending results). Will await results.");
364 return;
365 }
366 // else if (!m_pending_results_q.empty())
367
368 /* If deficit existed *before* the request was pushed, and there's surplus too, then it wasn't steady state
369 * pre-push. Violates our invariant (see data member doc headers). */
370 assert(m_pending_user_requests_q.size() == 1);
371
372 auto& peer_or_err_code = m_pending_results_q.front();
373 if (holds_alternative<Error_code>(peer_or_err_code))
374 {
375 assert(m_pending_results_q.size() == 1); // An error always caps the queue (and never leaves it).
376 FLOW_LOG_TRACE("Acceptor [" << *this << "]: New async-request pushed onto deficit queue; "
377 "and there is surplus in the form of a fatal error code. Will feed error to the request "
378 "*without* popping it from surplus queue (size remains 1).");
379 feed_error_result_to_deficit(get<Error_code>(peer_or_err_code));
380 }
381 else
382 {
383 assert(holds_alternative<Peer_ptr>(peer_or_err_code));
384
385 FLOW_LOG_TRACE("Acceptor [" << *this << "]: New async-request pushed onto deficit queue; "
386 "and there is surplus in the form of a new peer handle. Will feed handle to the request. "
387 "Queue size will become [" << (m_pending_results_q.size() - 1) << "].");
388
389 Peer_ptr peer{std::move(get<Peer_ptr>(peer_or_err_code))};
391 feed_success_result_to_deficit(std::move(peer));
392 }
393 }); // m_worker.post()
394} // Native_socket_stream_acceptor::async_accept_impl()
395
397{
398 using std::get;
399 using std::holds_alternative;
400
401 // We are in thread W.
402
403 assert((!m_pending_results_q.empty()) && holds_alternative<Error_code>(m_pending_results_q.back()));
404
405 if (m_pending_user_requests_q.empty())
406 {
407 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Fatal error pushed onto surplus queue; "
408 "but there is no deficit (no pending requests). Will await async-accept request(s).");
409 return;
410 }
411 // else if (!m_pending_user_requests_q.empty())
412
413 /* If surplus existed *before* the error was pushed, and there's deficit too, then it wasn't steady state pre-push.
414 * Violates our pre-condition. */
415 assert(m_pending_results_q.size() == 1);
416
417 const auto err_code = get<Error_code>(m_pending_results_q.front());
418 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Fatal error pushed onto surplus queue; "
419 "and there is deficit (1+ pending requests). Will feed error to all pending requests *without* "
420 "popping surplus queue, whose size remains 1.");
422} // Native_socket_stream_acceptor::finalize_q_surplus_on_error()
423
425{
426 using std::get;
427 using std::holds_alternative;
428
429 // We are in thread W.
430
431 assert((!m_pending_results_q.empty()) && holds_alternative<Peer_ptr>(m_pending_results_q.back()));
432
433 if (m_pending_user_requests_q.empty())
434 {
435 FLOW_LOG_TRACE("Acceptor [" << *this << "]: New peer socket handle pushed onto surplus queue; "
436 "but there is no deficit (no pending requests). Will await async-accept request(s).");
437 return;
438 }
439 // else if (!m_pending_user_requests_q.empty())
440
441 /* If surplus existed *before* the handle was pushed, and there's deficit too, then it wasn't steady state pre-push.
442 * Violates our pre-condition. */
443 assert(m_pending_results_q.size() == 1);
444
445 Peer_ptr peer{std::move(get<Peer_ptr>(m_pending_results_q.front()))};
447 FLOW_LOG_TRACE("Acceptor [" << *this << "]: New peer socket handle pushed onto surplus queue; "
448 "and there is deficit (1+ pending requests). Will feed to next pending request, having "
449 "popped it from surplus queue (size is now 0).");
450 feed_success_result_to_deficit(std::move(peer));
451} // Native_socket_stream_acceptor::finalize_q_surplus_on_success()
452
454{
455 assert(!m_pending_user_requests_q.empty());
456
457 size_t idx = 0;
458 do // while (!empty())
459 {
460 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Feeding to user async-accept request handler [" << idx << "]: "
461 "Error code [" << err_code << "] [" << err_code.message() << "].");
462 m_pending_user_requests_q.front()->m_on_done_func(err_code);
464
465 ++idx;
466 }
467 while (!m_pending_user_requests_q.empty());
468} // Native_socket_stream_acceptor::feed_error_result_to_deficit()
469
471{
472 assert(!m_pending_user_requests_q.empty());
473
474 FLOW_LOG_TRACE("Acceptor [" << *this << "]: Feeding to user async-accept request handler: "
475 "Socket stream [" << *peer << "]. User request queue size post-pop is "
476 "[" << (m_pending_user_requests_q.size() - 1) << "].");
477 auto& head_request = m_pending_user_requests_q.front();
478 *head_request->m_target_peer = std::move(*peer);
479 head_request->m_on_done_func(Error_code{});
481} // Native_socket_stream_acceptor::feed_success_result_to_deficit()
482
484{
485 return m_absolute_name;
486}
487
488std::ostream& operator<<(std::ostream& os, const Native_socket_stream_acceptor& val)
489{
490 return os << "sh_name[" << val.absolute_name() << "]@" << static_cast<const void*>(&val);
491}
492
493} // namespace ipc::transport
A server object that binds to a Shared_name and listens for incoming Native_socket_stream connect att...
asio_local_stream_socket::Peer_socket< Native_socket_stream_cfg::Protocol > m_next_peer_socket
Unix domain peer socket, always empty/unconnected while a background m_acceptor.async_accept() is pro...
static const Shared_name & S_RESOURCE_TYPE_ID
Shared_name relative-folder fragment (no separators) identifying this resource type.
void async_accept_impl(Peer *target_peer, On_peer_accepted_func &&on_done_func)
Non-template impl of async_accept().
flow::async::Single_thread_task_loop m_worker
A single-threaded async task loop that starts in constructor and ends in destructor.
boost::movelib::unique_ptr< Peer > Peer_ptr
Short-hand for internally stored PEER-state sync_io::Native_socket_stream in m_pending_results_q.
std::queue< User_request::Ptr > m_pending_user_requests_q
Queue storing deficit async-accept requests queued up due to lacking pending ready peer socket handle...
void finalize_q_surplus_on_error()
In thread W, in steady state except for an Error_code just pushed to the back of m_pending_results_q ...
~Native_socket_stream_acceptor()
Destroys this acceptor which will stop listening in the background and cancel any pending completion ...
void feed_error_result_to_deficit(const Error_code &err_code)
In thread W, gets back to steady state by feeding the given Error_code (which must be the sole elemen...
boost::movelib::unique_ptr< asio_local_stream_socket::Acceptor< Native_socket_stream_cfg::Protocol > > m_acceptor
Unix domain socket acceptor.
flow::async::Task_asio_err On_peer_accepted_func
Short-hand for callback called on new peer-to-peer connection; or on unrecoverable error.
void finalize_q_surplus_on_success()
In thread W, in steady state, introduces the just-established peer socket handle into the state machi...
std::queue< std::variant< Peer_ptr, Error_code > > m_pending_results_q
Queue storing surplus finalized async-accept results queued up due to lacking async_accept() requests...
Native_socket_stream_acceptor(flow::log::Logger *logger_ptr, const Shared_name &absolute_name, Error_code *err_code=nullptr)
Creates the Native_socket_stream_acceptor and immediately begins listening in the background,...
const Shared_name & absolute_name() const
Returns the full name/address to which the constructor bound, or attempted to bind,...
void feed_success_result_to_deficit(Peer_ptr &&peer)
In thread W, gets back to steady state by feeding the given just-connected peer socket (which must ha...
void on_next_peer_socket_or_error(const Error_code &sys_err_code)
Handler for incoming connection on m_acceptor.
std::conditional_t< S_USE_OS_DGRAM_SUPPORT, asio_local_stream_socket::Protocol_pkt_stream, asio_local_stream_socket::Protocol_byte_stream > Protocol
sync_io::Native_socket_stream and derivative(s) shall internally use a boost.asio socket type of this...
Implements both sync_io::Native_handle_sender and sync_io::Native_handle_receiver concepts by using a...
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
const std::string & str() const
Returns (sans copying) ref to immutable entire wrapped name string, suitable to pass into sys calls w...
Definition: shared_name.cpp:89
Endpoint< Protocol > endpoint_at_shared_name(flow::log::Logger *logger_ptr, const Shared_name &absolute_name, Error_code *err_code)
Returns an Endpoint corresponding to the given absolute Shared_name, so that an Acceptor or Peer_sock...
typename Protocol::endpoint Endpoint
Short-hand for boost.asio Unix domain peer stream-socket endpoint.
typename Protocol::acceptor Acceptor
Short-hand for boost.asio Unix domain stream-socket acceptor (listening guy) socket.
@ S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER
Async completion handler is being called prematurely, because underlying object is shutting down,...
Flow-IPC module providing transmission of structured messages and/or low-level blobs (and more) betwe...
std::ostream & operator<<(std::ostream &os, const Generic_msg_batch_in< Msg_resource_t, NO_HNDLS > &val)
Prints string representation of the given Generic_msg_batch_in to the given ostream.
Definition: batch.hpp:431
util::Shared_name Shared_name
Convenience alias for the commonly used type util::Shared_name.
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
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.