Flow-IPC 1.0.1
Flow-IPC project: Full implementation reference.
native_socket_stream.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/util.hpp>
27#include <experimental/propagate_const>
28
29namespace ipc::transport
30{
31
32// Types.
33
34/**
35 * Implements both Native_handle_sender and Native_handle_receiver concepts by using a stream-oriented Unix domain
36 * socket, allowing high-performance but non-zero-copy transmission of discrete messages, each containing a native
37 * handle, a binary blob, or both. This is a low-level (core) transport mechanism; higher-level (structured)
38 * transport mechanisms may use Native_socket_stream to enable their work. Native_socket_stream, as of this writing,
39 * is unique in that it is able to transmit not only blobs but also native handles.
40 *
41 * @see sync_io::Native_socket_stream and util::sync_io doc headers. The latter describes a general pattern which
42 * the former implements. In general we recommend you use a `*this` rather than a sync_io::Native_socket_stream --
43 * but you may have particular needs (summarized in util::sync_io doc header) that would make you decide
44 * otherwise.
45 *
46 * ### Quick note on naming ###
47 * It appears somewhat inconsistent to name it `Native_socket_stream[_acceptor]`
48 * and not `Native_handle_stream[_acceptor]`, given the implemented concept names `Native_handle_*er` and
49 * a key payload type being `Native_handle`. It's subjective and a matter of aesthetics even, of course,
50 * but the reasoning is: It briefly conveys (or at least suggests) that the underlying transport
51 * is the Unix domain *socket* (*stream*-oriented at that); while also suggesting that *native* handles
52 * are transmissible over it (and it in fact is unique in that capability by the way). Perhaps
53 * something like `Native_handle_socket_stream[_acceptor]` would've been more accurate, but brevity is a virtue.
54 *
55 * @note The same reasoning applies for Socket_stream_channel and Socket_stream_channel_of_blobs with the
56 * `native_` part being left out for orthogonal aesthetic reasons.
57 *
58 * ### Blob_sender and Blob_receiver concept compatibility ###
59 * Native_socket_stream also implements the Blob_sender and Blob_receiver concepts. To wit:
60 * You must choose whether you shall use this as a `Blob_sender/receiver` or `Native_handle_sender/receiver`,
61 * and conversely the other side must do the same in matched fashion. Doing otherwise leads to undefined behavior.
62 * To use the latter concept pair simply use send_native_handle() and async_receive_native_handle() and never
63 * send_blob() or async_receive_blob(). To use the former concept pair simply do the reverse.
64 * On the other side do the matched thing. All the other methods/dtor are the same regardless of concept pair chosen.
65 *
66 * How does this work? Trivially: consider, say, send_native_handle() which can send a native handle, or none;
67 * and a blob, or none (but at least 1 of the 2). send_blob() can only take a blob (which must be present) and
68 * internally will simply act as send_native_handle() with a null handle. Conversely async_receive_native_handle()
69 * receives a handle (or none) and blob (or none); so async_receive_blob() will receive only a blob. The only added
70 * behaviors in `Blob_sender/receiver` "mode":
71 * - send_blob() requires a blob of size 1+. (send_native_handle() allows an empty meta-blob; i.e., no blob.)
72 * - async_receive_blob() will emit an error if a payload arrives with a native handle in it:
73 * error::Code::S_BLOB_RECEIVER_GOT_NON_BLOB. This would only occur if the other side were using the mismatched
74 * API.
75 *
76 * Further discussion ignores the degraded `Blob_*` concepts, as they are a trivial degenerate case of
77 * `Native_handle_*`.
78 *
79 * @see other, dedicated Blob_sender and Blob_receiver impls namely, at least, the persistent-MQ-based
80 * Blob_stream_mq_sender and Blob_stream_mq_receiver. They may appeal due to potentially better performance
81 * and/or a creation/addressing scheme that is more appealing, depending.
82 *
83 * ### Informal comparison to other core transport mechanisms ###
84 * Firstly, as noted, it is currently unique in that it can transmit native handles. The rest of the discussion
85 * here is about transmitting blobs (called meta-blobs, meaning they accompany native handles).
86 *
87 * It is intended for transmission of relatively short messages -- rough guidance
88 * being for max length being in the 10s-of-KiB range. With a modern Linux kernel on server hardware from about 2014
89 * to 2020, our performance tests show that its raw speed for messages of aforementioned size is comparable to
90 * zero-copy mechanisms based on POSIX message queues and Boost SHM-based message queues. Technically we found it
91 * to be somewhat faster than the latter and somewhat slower than the former. However, for best perf, it is recommended
92 * to send handles to SHM areas containing arbitrarily long structured messages (such as ones [de]serialized using
93 * zero-copy builders: capnp and the like). This further reduces the importance of relative perf compared to
94 * other low-level transports (which, as noted, is pretty close regardless -- though this is bound to stop being true
95 * for much longer messages, if the send-SHM-handles technique were to *not* be used).
96 *
97 * ### Cleanup ###
98 * Some core transports rely on SHM or SHM-style semantics, wherein the transport has kernel persistence, meaning
99 * some or all processes exiting does not free the resources involved. For those transports special measures must
100 * be taken to ensure cleanup after both relevant processes die or no longer use a transport instance. This is not the
101 * case for Native_socket_stream: When the underlying (hidden from user) Unix domain socket handle is closed by both
102 * sides, all resources are automatically cleaned up; and processes exiting always closes all such handles.
103 * These matters for all transports are hidden from the user as much as possible, but these internal facts are still
104 * relevant knowledge for the reader/user.
105 *
106 * ### How to use ###
107 * Generally, this class's behavior is dictated by the 2 main concepts it implements: Native_handle_sender and
108 * Native_handle_receiver. Though internally a single mechanism is used for both (incoming and outgoing) *pipes*
109 * (a term I use generically, not meaning "Unix FIFOs"), the two pipes are mostly decoupled. Bottom line:
110 * Behavior is dictated by the 2 concepts, so please see their respective doc headers first. Next, see the doc
111 * headers of the main concrete APIs which discuss any behaviors relevant to Native_socket_stream specifically:
112 * - dtor;
113 * - send_native_handle() (or send_blob()), `*end_sending()`, auto_ping();
114 * - async_receive_native_handle() (or async_receive_blob()), idle_timer_run();
115 * - send_meta_blob_max_size() (or send_blob_max_size());
116 * - receive_meta_blob_max_size() (or receive_blob_max_size());
117 * - default ctor;
118 * - move ctor, move assignment.
119 *
120 * There is also:
121 * - remote_peer_process_credentials() which is specific to Native_socket_stream and not those concepts.
122 *
123 * That above list (in?)conspicuously omits the initialization API. So how does one create this connection
124 * and hence the two required `Native_socket_stream`s that are connected to each other (each in PEER state)?
125 *
126 * A Native_socket_stream object is always in one of 2 states:
127 * - PEER. Upon entering this state, the peer is connected (is an actual peer). At this stage it exactly
128 * implements the concepts Native_handle_sender, Native_handle_receiver, Blob_sender, Blob_receiver.
129 * If the pipe is hosed (via an error or graceful-close), it remains in this PEER state (but cannot do anything
130 * useful anymore). It is not possible to exit PEER state.
131 * - The only added behavior on top of the implemented concepts is remote_peer_process_credentials() (which is not
132 * a concept API but logically only makes sense when already connected, i.e., in PEER state).
133 * - NULL. In this state, the object is doing nothing; neither connected nor connecting.
134 *
135 * Therefore, to be useful, one must somehow get to PEER state in which it implements the aforementioned concepts.
136 * How to do this? Answer: There are 2* ways:
137 * - Use Native_socket_stream_acceptor on the side you've designated as the server for that connection or set of
138 * connections. This allows to wait for a connection and eventually, on success, moves the target
139 * Native_socket_stream passed to Native_socket_stream_acceptor::async_accept() to PEER state.
140 * - On the other side, use a Native_socket_stream in NULL state and invoke sync_connect() which will move
141 * synchronously and non-blockingly to PEER state (mission accomplished).
142 * If the connect fails (also synchronously, non-blockingly), it will go back to NULL state.
143 * - Use a mechanism, as of this writing ipc::transport::Channel at least, that uses the following technique:
144 * -# A process-wide Native_socket_stream connection is established using via the client-server method in the
145 * above bullet point.
146 * -# A socket-pair-generating OS call generates 2 pre-connected native stream-oriented Unix domain socket handles.
147 * -# E.g., use boost.asio `asio_local_stream_socket::connect_pair()`.
148 * -# 1 of the 2 handles is passed to the other side using the process-wide connection from step 1.
149 * -# On each side, construct Native_socket_stream using the #Native_handle-taking ctor thus entering PEER
150 * state directly at construction (on each side).
151 *
152 * Way 2 is better, because it requires no `Shared_name`s whatsoever, hence there are no thorny naming issues.
153 * The drawback is it requires an already-existing Native_socket_stream in order to transmit the handle! Chicken/egg!
154 * Therefore, the informal recommendation is -- if practical -- use way 1 once (create chicken); then subsequently use
155 * way 2 as needed to easily create further connections.
156 *
157 * Native_socket_stream is move-constructible and move-assignable (but not copyable). This is, at any rate,
158 * necessary to work with the boost.asio-style Native_socket_stream_acceptor::async_accept() API.
159 * A moved-from Native_socket_stream is as-if default-constructed; therefore it enters NULL state.
160 *
161 * (*) There is, in fact, another way -- one could call it way 2a. It is just like way 2; except that -- upon
162 * obtaining a pre-connected `Native_handle` (one of a pair) -- one first constructs a "`sync_io` core",
163 * namely a sync_io::Native_socket_stream object, using the *exact* same signature (which takes a `Logger*`,
164 * a nickname, and the pre-connected `Native_handle`). Then, one constructs a `*this` by `move()`ing that
165 * guy into the `sync_io`-core-adopting ctor. The result is exactly the same. (Internally, whether one uses way 1
166 * or way 2, `*this` will create a sync_io::Native_socket_stream core itself. I tell you this, in case you are
167 * curious.)
168 *
169 * ### Why no async-connect method, only sync-connect? ###
170 * Without networking, the other side (Native_stream_socket_acceptor) either exists/is listening; or no.
171 * Connecting is a synchronous, non-blocking operation; so an `async_connect()` API in this context only makes
172 * life harder for the user. (However, there are some serious plans to add a networking-capable counterpart
173 * (probably via TCP at least) to Native_socket_stream; that one will almost certainly have an `async_connect()`,
174 * while its `sync_connect()` will probably become potentially blocking.)
175 *
176 * If you are worried about attempting to connect to a Native_stream_socket_acceptor whose internal backlog limit has
177 * been reached: please don't. It won't happen; we have contemplated the details.
178 *
179 * ### Thread safety ###
180 * We add no more thread safety guarantees than those mandated by the main concepts. To wit:
181 * - Concurrent access to 2 separate Native_socket_stream objects is safe.
182 * - After construction, concurrent access to the main transmission API methods (or
183 * remote_peer_process_credentials()) is not safe.
184 *
185 * @internal
186 * ### Implementation design/rationale ###
187 * Internally Native_socket_stream strictly uses the pImpl idiom (see https://en.cppreference.com/w/cpp/language/pimpl
188 * for an excellent overview). Very briefly:
189 * - The "true" Native_socket_stream is actually the self-contained, but not publicly exposed,
190 * Native_socket_stream::Impl class.
191 * - #m_impl is the `unique_ptr` to `*this` object's `Impl`. This becomes null only when Native_socket_stream
192 * is moved-from; but if one attempts to call a method (such as sync_connect()) on the moved-from `*this`
193 * #m_impl is lazily re-initialized to a new default-cted (therefore NULL-state) Native_socket_stream::Impl.
194 * - Every public method of Native_socket_stream `*this` forwards to essentially the same-named method
195 * of `Impl` `*m_impl`. `Impl` is an incomplete type inside the class declaration at all times;
196 * it becomes complete only inside the bodies of the (`Impl`-forwarding) public methods of Native_socket_stream.
197 * - The usual impl caveats apply: `Impl` must not have templated methods; so in particular the handler-taking
198 * method templates (such as async_receive_native_handle()) transform the parameterized arguments to
199 * concretely-typed objects (in this case `Function<>` objects such as `flow::async::Task_asio_err_sz`).
200 * - Because the implementation is 100% encapsulated inside the `Impl` #m_impl, move-assignment and move-construction
201 * are acquired for "free":
202 * - Coding-ease "free": Default-generated move-ctor and move-assignment simply move the `unique_ptr` #m_impl.
203 * - Perf "free": `unique_ptr` move is lightning-quick (nullify the source pointer after copying the pointer value
204 * into the target pointer).
205 *
206 * Okay, if one is familiar with pImpl, none of this is surprising in terms of how it works. *Why* though? Answer:
207 * - I (ygoldfel) chose pImpl *not* to maintain a stable ABI/guarantee the implementation of methods can be changed
208 * without needing to recompile the code that `#include`s the present header file Native_socket_stream.hpp...
209 * - ...but rather specifically to get the move-ction and move-assignment for "free" as briefly explained above.
210 * - Consider the alternative. Suppose Native_socket_stream is implemented directly inside Native_socket_stream
211 * itself; and we still want move-semantics available to the user.
212 * - Writing the move-ctor/assignment in and of itself is very much non-trivial. The implementation involves
213 * many delicate data members including mutexes. Swapping them properly, etc. etc., is no mean feat.
214 * Plus it's not exactly perf-y either.
215 * - Even having accomplished that, there is the following problem. There is an internally maintained worker
216 * thread W which does much async work. So consider something like a boost.asio `async_read_some(.., F)`
217 * call, where `F()` is the completion handler. `F()` will be a lambda that captures `this` so it continue
218 * the async work chain. But if `*this` is moved-from in the meantime, `this` is no longer pointing to the
219 * right object. Making that work is difficult.
220 * - Yet with pImpl-facilitated move semantics it becomes trivial. `Impl` is non-copyable, non-movable and can
221 * rely on a stable `this`. Move semantics become entirely the concern of the wrapper Native_socket_stream:
222 * nice and clean.
223 *
224 * This leaves two questions.
225 * - One: why do we want public move-semantics anyway? Why not just provide a non-movable Native_socket_stream
226 * and let the user worry about wrapping it in a `shared_ptr` or `unique_ptr`, if they want to move these objects
227 * around? Answer: Sure, that's perfectly reasonable. However:
228 * - Native_socket_stream_acceptor would need to impose shared-ownership/factory API in supplying the connected
229 * Native_socket_stream to the user asynchronously. That could be fine -- but it'd be nice to have
230 * acceptor semantics that are like boost.asio's as opposed to our own design.
231 * - Nevertheless Native_socket_stream_acceptor API could still be designed in factory fashion, even if it's
232 * inconsistent with what boost.asio users might expect. But: now consider the other guys implementing
233 * Blob_sender and Blob_receiver, namely the message queue-based (MQ-based) Blob_stream_mq_sender and
234 * and Blob_stream_mq_receiver. They don't use an acceptor API at all by their nature; and hence they don't
235 * need any factory semantics. So now some Blob_sender and Blob_receiver implementing classes use ownership
236 * semantics X, while others (Native_socket_stream) use ownership semantics Y -- despite implementing the
237 * same concepts. Is this okay? Sure, it's not bad; but it's not ideal all in all.
238 * - Therefore for consistency inside the overall ipc::transport API, as well as versus boost.asio API,
239 * making Native_socket_stream directly constructible (and movable when needed for acceptor semantics)
240 * ultimately is the nicer approach. (Historical note: An early version of this class and
241 * Native_socket_stream_acceptor indeed used the alternative, factory-based approach. So we've tried both.)
242 * - Two: what about the negatives of pImpl? It is after all a trade-off. I won't go over all the negatives here
243 * "formally" (see the cppreference.com page mentioned above for a survey). Long story short:
244 * - The indirection of all calls through a pointer is probably a minor perf hit in and of itself.
245 * - Factories involve their own perf hits, particularly since typically they use `shared_ptr` and not
246 * `unique_ptr` (which is quite quick in comparison).
247 * - The laborious need to code a copy of the entire public API, so as to forward to `Impl`, is *easily*
248 * no big deal compared to what it would take to make Native_socket_stream movable directly.
249 * - pImpl is not inlining-friendly and cannot be used for a header-only library. However:
250 * - The entire Flow-IPC library -- and all of Flow too -- to begin with refuses to be inlining-friendly and
251 * header-only; so this does not change that.
252 * - Both Flow-IPC and Flow encourage the use of LTO which will inline everything anyway.
253 * If LTO is not available then the resulting perf loss already applies to both libraries; so this
254 * does not change that. Conversely adding LTO will benefit everything including Native_socket_stream.
255 * - So we are consistent in this decision for better or worse.
256 *
257 * That's my (ygoldfel) story, and I am sticking to it.
258 *
259 * The rest of the implementation is inside Native_socket_stream::Impl and is discussed in that class's doc header.
260 *
261 * @see Native_socket_stream::Impl doc header.
262 *
263 * @endinternal
264 *
265 * @see Native_handle_sender: implemented concept.
266 * @see Native_handle_receiver: implemented concept.
267 * @see Blob_sender: alternatively implemented concept.
268 * @see Blob_receiver: alternatively implemented concept.
269 */
271{
272public:
273 // Types.
274
275 /// Useful for generic programming, the `sync_io`-pattern counterpart to `*this` type.
277 /// You may disregard.
279
280 // Constants.
281
282 /// Implements concept API.
284
285 /**
286 * Implements concept API; namely it is `true`: async_receive_native_handle() with
287 * smaller-than-`receive_meta_blob_max_size()` size shall *not* lead to error::Code::S_INVALID_ARGUMENT; hence
288 * error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE is possible.
289 *
290 * @see Native_handle_receiver::S_META_BLOB_UNDERFLOW_ALLOWED: implemented concept. Accordingly also see
291 * "Blob underflow semantics" in that concept class's doc header for potentially important discussion.
292 */
293 static constexpr bool S_META_BLOB_UNDERFLOW_ALLOWED = true;
294
295 /**
296 * Implements concept API; namely it is `true`: async_receive_blob() with smaller-than-`receive_blob_max_size()`
297 * size shall *not* lead to error::Code::S_INVALID_ARGUMENT; hence error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE
298 * is possible.
299 *
300 * @see Blob_receiver::S_BLOB_UNDERFLOW_ALLOWED: implemented concept. Accordingly also see
301 * "Blob underflow semantics" in sister concept class's doc header for potentially important discussion.
302 */
303 static constexpr bool S_BLOB_UNDERFLOW_ALLOWED = true;
304
305 // Constructors/destructor.
306
307 /**
308 * Default ctor (object is in NULL state).
309 * Implements Native_handle_sender *and* Native_handle_receiver APIs at the same time, per their concept contracts.
310 * (Also implements Blob_sender *and* Blob_receiver APIs; they are identical.)
311 * All the notes for both concepts' default ctors apply.
312 *
313 * This ctor is informally intended for the following uses:
314 * - A moved-from Native_socket_stream (i.e., the `src` arg move-ctor and move-assignment operator)
315 * becomes as-if defaulted-constructed.
316 * - A target Native_socket_stream for Native_socket_stream_acceptor::async_accept() shall typically be
317 * default-cted; Native_socket_stream_acceptor shall asynchronously move-assign a logger-apointed,
318 * nicely-nicknamed into that target `*this`.
319 *
320 * Therefore it would be unusual (though allowed) to make direct calls such as sync_connect() and send_blob()
321 * on a default-cted Native_socket_stream without first moving a non-default-cted object into it.
322 *
323 * @see Native_handle_sender::Native_handle_sender(): implemented concept.
324 * @see Native_handle_receiver::Native_handle_receiver(): implemented concept.
325 * @see Blob_sender::Blob_sender(): implemented concept.
326 * @see Blob_receiver::Blob_receiver(): implemented concept.
327 */
329
330 /**
331 * Creates a Native_socket_stream in NULL (not connected) state.
332 *
333 * This ctor is informally intended for the following use:
334 * - You create a Native_socket_stream that is logger-appointed and nicely-nicknamed; then you call
335 * sync_connect() on it in order to move it to, hopefully, PEER states.
336 * It will retain the logger and nickname throughout.
337 *
338 * Alternatively:
339 * - If you intend to get a Native_socket_stream from a pre-connected #Native_handle:
340 * Use that Native_socket_stream ctor.
341 * - If you intend to get a Native_socket_stream by accepting it server-style:
342 * Use the default Native_socket_stream ctor; pass that object to Native_socket_stream_acceptor::async_accept().
343 *
344 * @param logger_ptr
345 * Logger to use for subsequently logging.
346 * @param nickname_str
347 * Human-readable nickname of the new object, as of this writing for use in `operator<<(ostream)` and
348 * logging only.
349 */
350 explicit Native_socket_stream(flow::log::Logger* logger_ptr, util::String_view nickname_str);
351
352 /**
353 * Constructs the socket-and-meta-blob stream by taking over an already-connected native Unix domain socket handle.
354 * The socket must be freshly connected without any traffic exchanged on the connection so far; otherwise behavior
355 * undefined.
356 *
357 * ### Performance ###
358 * The taking over of `native_peer_socket_moved` should be thought of as light-weight.
359 *
360 * @param logger_ptr
361 * Logger to use for subsequently logging.
362 * @param native_peer_socket_moved
363 * The wrapped native handle shall be taken over by `*this`; and this wrapper object will be made
364 * `.null() == true`. In plainer English, the main point is, this is the native socket
365 * over which traffic will proceed.
366 * @param nickname_str
367 * Human-readable nickname of the new object, as of this writing for use in `operator<<(ostream)` and
368 * logging only.
369 */
370 explicit Native_socket_stream(flow::log::Logger* logger_ptr, util::String_view nickname_str,
371 Native_handle&& native_peer_socket_moved);
372
373 /**
374 * Implements Native_handle_sender *and* Native_handle_receiver APIs at the same time, per their concept contracts.
375 * (Also implements Blob_sender *and* Blob_receiver APIs; they are identical.)
376 *
377 * ### Performance ###
378 * The taking over of `sync_io_core_in_peer_state_moved` should be thought of as light-weight.
379 *
380 * @param sync_io_core_in_peer_state_moved
381 * See above.
382 *
383 * @see Native_handle_sender::~Native_handle_sender(): implemented concept.
384 * @see Native_handle_receiver::~Native_handle_receiver(): implemented concept.
385 * @see Blob_sender::~Blob_sender(): alternatively implemented concept.
386 * @see Blob_receiver::~Blob_receiver(): alternatively implemented concept.
387 */
388 explicit Native_socket_stream(Sync_io_obj&& sync_io_core_in_peer_state_moved);
389
390 /**
391 * Move-constructs from `src`; `src` becomes as-if default-cted (therefore in NULL state).
392 * Implements Native_handle_sender *and* Native_handle_receiver APIs at the same time, per their concept contracts.
393 * (Also implements Blob_sender *and* Blob_receiver APIs; they are identical.)
394 *
395 * @param src
396 * See above.
397 *
398 * @see Native_handle_sender::Native_handle_sender(): implemented concept.
399 * @see Native_handle_receiver::Native_handle_receiver(): implemented concept.
400 * @see Blob_sender::Blob_sender(): implemented concept.
401 * @see Blob_receiver::Blob_receiver(): implemented concept.
402 */
404
405 /// Copy construction is disallowed.
407
408 /**
409 * Implements Native_handle_sender *and* Native_handle_receiver APIs at the same time, per their concept contracts.
410 * (Also implements Blob_sender *and* Blob_receiver APIs; they are identical.)
411 * All the notes for both concepts' destructors apply but as a reminder:
412 *
413 * Destroys this peer endpoint which will end both-direction pipes (of those still open) and cancel any pending
414 * completion handlers by invoking them ASAP with error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER.
415 * As of this writing these are the completion handlers that would therefore be called:
416 * - Any handler passed to async_receive_native_handle() that has not yet been invoked.
417 * There can be 0 or more of these.
418 * - The handler passed to async_end_sending().
419 * Since it is not valid to call async_end_sending() more than once, there is at most 1 of these.
420 *
421 * @see Native_handle_sender::~Native_handle_sender(): implemented concept.
422 * @see Native_handle_receiver::~Native_handle_receiver(): implemented concept.
423 * @see Blob_sender::~Blob_sender(): alternatively implemented concept.
424 * @see Blob_receiver::~Blob_receiver(): alternatively implemented concept.
425 */
427
428 // Methods.
429
430 /**
431 * Move-assigns from `src`; `*this` acts as if destructed; `src` becomes as-if default-cted (therefore in NULL state).
432 * No-op if `&src == this`.
433 * Implements Native_handle_sender *and* Native_handle_receiver APIs at the same time, per their concept contracts.
434 * (Also implements Blob_sender *and* Blob_receiver APIs; they are identical.)
435 *
436 * @see ~Native_socket_stream().
437 *
438 * @param src
439 * See above.
440 * @return `*this` (see concept API).
441 *
442 * @see Native_handle_sender move assignment: implemented concept.
443 * @see Native_handle_receiver move assignment: implemented concept.
444 * @see Blob_sender move assignment: implemented concept.
445 * @see Blob_receiver move assignment: implemented concept.
446 */
448
449 /// Copy assignment is disallowed.
451
452 /**
453 * Returns nickname, a brief string suitable for logging. This is included in the output by the `ostream<<`
454 * operator as well. This method is thread-safe in that it always returns the same value.
455 *
456 * @return See above.
457 */
458 const std::string& nickname() const;
459
460 // Connect-ops API.
461
462 /**
463 * To be invoked in NULL state only, it synchronously and non-blockingly attempts to connect to an opposing
464 * Native_socket_stream_acceptor or sync_io::Native_socket_stream_acceptor
465 * listening at the given absolute Shared_name; and synchronously reports failure or success, the latter
466 * showing `*this` has entered PEER state. Failure means `*this` remains in NULL state.
467 *
468 * If invoked outside of NULL state this returns `false` and otherwise does nothing.
469 *
470 * Note that `*this` (modulo moves) that has entered PEER state can never change state subsequently
471 * (even on transmission error); once a PEER, always a PEER.
472 *
473 * #Error_code generated and passed to `on_done_func()`:
474 * system codes most likely from `boost::asio::error` or `boost::system::errc` (but never would-block).
475 *
476 * @see Class doc header, section "Why no async-connect method, only sync-connect?" for potentially interesting
477 * context.
478 *
479 * @return `false` if and only if invoked outside of NULL state (that is: in PEER state).
480 *
481 * @param absolute_name
482 * Absolute name at which the `Native_socket_stream_acceptor` is expected to be listening.
483 * @param err_code
484 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
485 * system codes most likely from `boost::asio::error` or `boost::system::errc` (but never would-block).
486 */
487 bool sync_connect(const Shared_name& absolute_name, Error_code* err_code = 0);
488
489 // Send-ops API.
490
491 /**
492 * Implements Native_handle_sender API per contract. Note this value equals send_blob_max_size() at any given
493 * time which is *not* a concept requirement. Its PEER-state constant value can also be accessed as
494 * non-concept-mandated Native_socket_stream::S_MAX_META_BLOB_LENGTH.
495 *
496 * @return See above.
497 *
498 * @see Native_handle_sender::send_meta_blob_max_size(): implemented concept.
499 */
500 size_t send_meta_blob_max_size() const;
501
502 /**
503 * Implements Blob_sender API per contract. Note this value equals send_meta_blob_max_size() at any given
504 * time which is *not* a concept requirement. Its PEER-state constant value can also be accessed as
505 * non-concept-mandated Native_socket_stream::S_MAX_META_BLOB_LENGTH.
506 *
507 * @return See above.
508 *
509 * @see Blob_sender::send_blob_max_size(): implemented concept.
510 */
511 size_t send_blob_max_size() const;
512
513 /**
514 * Implements Native_handle_sender API per contract. Reminder: You may call this directly from within a
515 * completion handler you supplied to an earlier async_receive_native_handle(). Reminder: It's not thread-safe
516 * to call this concurrently with other transmission methods or destructor on the same `*this`.
517 *
518 * @param hndl_or_null
519 * See above.
520 * @param meta_blob
521 * See above. Reminder: The memory area described by this arg need only be valid until this
522 * method returns. Perf reminder: That area will not be copied except for very rare circumstances.
523 * @param err_code
524 * See above. Reminder: In rare circumstances, an error emitted here may represent something
525 * detected during handling of a *preceding* send_native_handle() call but after it returned.
526 * #Error_code generated:
527 * error::Code::S_INVALID_ARGUMENT (non-pipe-hosing error: `meta_blob.size()` exceeds
528 * send_meta_blob_max_size()),
529 * error::Code::S_SENDS_FINISHED_CANNOT_SEND (`*end_sending()` was called earlier),
530 * error::Code::S_LOW_LVL_TRANSPORT_HOSED_CANNOT_SEND (the incoming-direction processing detected
531 * that the underlying transport is hosed; specific code was logged and can be obtained via
532 * async_receive_native_handle()),
533 * `boost::asio::error::eof` (underlying transport hosed due to graceful closing by the other side),
534 * other system codes most likely from `boost::asio::error` or `boost::system::errc` (but never
535 * would-block), indicating the underlying transport is hosed for that specific reason, as detected during
536 * outgoing-direction processing.
537 * @return See above.
538 *
539 * @see Native_handle_sender::send_native_handle(): implemented concept.
540 */
541 bool send_native_handle(Native_handle hndl_or_null, const util::Blob_const& meta_blob,
542 Error_code* err_code = 0);
543
544 /**
545 * Implements Blob_sender API per contract. Reminder: You may call this directly from within a
546 * completion handler you supplied to an earlier async_receive_blob(). Reminder: It's not thread-safe
547 * to call this concurrently with other transmission methods or destructor on the same `*this`.
548 *
549 * Reminder: `blob.size() == 0` results in undefined behavior (assertion may trip).
550 *
551 * @param blob
552 * See above. Reminder: The memory area described by this arg need only be valid until this
553 * method returns. Perf reminder: That area will not be copied except for very rare circumstances.
554 * @param err_code
555 * See above. Reminder: In rare circumstances, an error emitted here may represent something
556 * detected during handling of a *preceding* send_blob() call but after it returned.
557 * #Error_code generated: see send_native_handle().
558 * @return See above.
559 *
560 * @see Blob_sender::send_blob(): implemented concept.
561 */
562 bool send_blob(const util::Blob_const& blob, Error_code* err_code = 0);
563
564 /**
565 * Implements Native_handle_sender, Blob_sender API per contract. Reminder: You may call this directly from within a
566 * completion handler you supplied to an earlier async_receive_native_handle() or async_receive_blob().
567 * Reminder: It's not thread-safe to call this concurrently with other transmission methods or destructor on
568 * the same `*this`.
569 *
570 * #Error_code generated and passed to `on_done_func()`:
571 * error::Code::S_LOW_LVL_TRANSPORT_HOSED_CANNOT_SEND (same meaning as for send_native_handle()/send_blob()),
572 * `boost::asio::error::eof` (ditto),
573 * other system codes most likely from `boost::asio::error` or `boost::system::errc` (ditto),
574 * error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER (destructor called, canceling all pending ops;
575 * spiritually identical to `boost::asio::error::operation_aborted`),
576 *
577 * Reminder: In rare circumstances, an error emitted there may represent something
578 * detected during handling of a preceding send_native_handle() or send_blob() call but after it returned.
579 *
580 * @tparam Task_err
581 * See above.
582 * @param on_done_func
583 * See above.
584 * @return See above. Reminder: If and only if it returns `false`, we're not in PEER state, or `*end_sending()` has
585 * already been called; and `on_done_func()` will never be called, nor will an error be emitted.
586 *
587 * @see Native_handle_sender::async_end_sending(): implemented concept.
588 * @see Blob_sender::async_end_sending(): alternatively implemented concept.
589 */
590 template<typename Task_err>
591 bool async_end_sending(Task_err&& on_done_func);
592
593 /**
594 * Implements Native_handle_sender, Blob_sender API per contract. Reminder: It is equivalent to async_end_sending()
595 * but with a no-op `on_done_func`.
596 *
597 * @return See async_end_sending().
598 *
599 * @see Native_handle_sender::end_sending(): implemented concept.
600 * @see Blob_sender::end_sending(): alternatively implemented concept.
601 */
602 bool end_sending();
603
604 /**
605 * Implements Native_handle_sender, Blob_sender API per contract.
606 *
607 * @param period
608 * See above.
609 * @return See above.
610 *
611 * @see Native_handle_sender::auto_ping(): implemented concept.
612 * @see Blob_sender::auto_ping(): alternatively implemented concept.
613 */
614 bool auto_ping(util::Fine_duration period = boost::chrono::seconds(2));
615
616 // Receive-ops API.
617
618 /**
619 * Implements Native_handle_receiver API per contract. Note this value equals receive_blob_max_size() at any given
620 * time which is *not* a concept requirement.
621 *
622 * @return See above.
623 *
624 * @see Native_handle_receiver::receive_meta_blob_max_size(): implemented concept.
625 */
626 size_t receive_meta_blob_max_size() const;
627
628 /**
629 * Implements Blob_receiver API per contract. Note this value equals receive_meta_blob_max_size() at any given
630 * time which is *not* a concept requirement.
631 *
632 * @return See above.
633 *
634 * @see Blob_receiver::receive_blob_max_size(): implemented concept.
635 */
636 size_t receive_blob_max_size() const;
637
638 /**
639 * Implements Native_handle_receiver API per contract. Reminder: You may call this directly from within a
640 * completion handler you supplied to an earlier async_receive_native_handle(). Reminder: It's not thread-safe
641 * to call this concurrently with other transmission methods or destructor on the same `*this`.
642 *
643 * #Error_code generated and passed to `on_done_func()`:
644 * error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER (destructor called, canceling all pending ops;
645 * spiritually identical to `boost::asio::error::operation_aborted`),
646 * error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE (opposing peer has sent a message with a meta-blob exceeding
647 * `target_meta_blob.size()` in length; in particular one can give an empty buffer if no meta-blob expected),
648 * error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE (peer gracefully closed pipe via `*end_sending()`),
649 * error::Code::S_LOW_LVL_TRANSPORT_HOSED_CANNOT_RECEIVE (the outgoing-direction processing detected
650 * that the underlying transport is hosed; specific code was logged and can be obtained via send_native_handle()),
651 * `boost::asio::error::eof` (underlying transport hosed due to graceful closing by the other side),
652 * other system codes most likely from `boost::asio::error` or `boost::system::errc` (but never
653 * would-block), indicating the underlying transport is hosed for that specific reason, as detected during
654 * incoming-direction processing.
655 *
656 * error::Code::S_INVALID_ARGUMENT shall never be emitted due to `target_meta_blob.size()` (or as of this writing
657 * for any other reason). See #S_META_BLOB_UNDERFLOW_ALLOWED.
658 *
659 * @tparam Task_err_sz
660 * See above.
661 * @param target_hndl
662 * See above.
663 * @param target_meta_blob
664 * See above. Reminder: The memory area described by this arg must be valid up to
665 * completion handler entry.
666 * @param on_done_func
667 * See above.
668 * @return See above.
669 *
670 * @see Native_handle_receiver::async_receive_native_handle(): implemented concept.
671 */
672 template<typename Task_err_sz>
673 bool async_receive_native_handle(Native_handle* target_hndl, const util::Blob_mutable& target_meta_blob,
674 Task_err_sz&& on_done_func);
675
676 /**
677 * Implements Blob_receiver API per contract. Reminder: You may call this directly from within a
678 * completion handler you supplied to an earlier async_receive_blob(). Reminder: It's not thread-safe
679 * to call this concurrently with other transmission methods or destructor on the same `*this`.
680 *
681 * #Error_code generated and passed to `on_done_func()`: see async_receive_native_handle().
682 * In addition: error::Code::S_BLOB_RECEIVER_GOT_NON_BLOB (opposing peer seems to have used a
683 * Native_socket_stream::send_native_handle() call, which they shouldn't in the first place, and supplied
684 * a non-null #Native_handle, which this Blob_receiver cannot accept).
685 *
686 * @tparam Task_err_sz
687 * See above.
688 * @param target_blob
689 * See above. Reminder: The memory area described by this arg must be valid up to
690 * completion handler entry.
691 * @param on_done_func
692 * See above.
693 * @return See above.
694 *
695 * @see Blob_receiver::async_receive_blob(): implemented concept.
696 */
697 template<typename Task_err_sz>
698 bool async_receive_blob(const util::Blob_mutable& target_blob, Task_err_sz&& on_done_func);
699
700 /**
701 * Implements Native_handle_receiver, Blob_receiver API per contract.
702 *
703 * @param timeout
704 * See above.
705 * @return See above.
706 *
707 * @see Blob_receiver::idle_timer_run(): implemented concept.
708 * @see Native_handle_receiver::idle_timer_run(): alternatively implemented concept.
709 */
710 bool idle_timer_run(util::Fine_duration timeout = boost::chrono::seconds(5));
711
712 // Misc API.
713
714 /**
715 * OS-reported process credential (PID, etc.) info about the *other* connected peer's process, at the time
716 * that the OS first established (via local-socket-connect or local-socket-connected-pair-generate call) that
717 * opposing peer socket. The value returned, assuming a non-error-emitting execution, shall always be the same for a
718 * given `*this`.
719 *
720 * Informally: To avoid (though, formally, not guarantee) error::Code::S_LOW_LVL_TRANSPORT_HOSED, it is best
721 * to call this immediately upon entry of `*this` to PEER state and/or before
722 * invoking any other APIs.
723 *
724 * If invoked outside of PEER state returns `Process_credentials()` immediately
725 * and otherwise does nothing.
726 *
727 * @return See above; or `Peer_credentials()` if invoked outside of PEER state or in case of error.
728 * The 2 eventualities can be distinguished by checking `*err_code` truthiness. Better yet
729 * only call remote_peer_process_credentials() in PEER state, as it is otherwise conceptually meaningless.
730 *
731 * @param err_code
732 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
733 * error::Code::S_LOW_LVL_TRANSPORT_HOSED (the incoming/outgoing-direction processing detected
734 * that the underlying transport is hosed; specific code was logged and can be obtained via
735 * async_receive_native_handle() or similar),
736 * system codes (albeit unlikely).
737 */
739
740private:
741 // Types.
742
743 // Forward declare the pImpl-idiom true implementation of this class. See native_socket_stream_impl.hpp.
744 class Impl;
745
746 /// Short-hand for `const`-respecting wrapper around Native_socket_stream::Impl for the pImpl idiom.
747 using Impl_ptr = std::experimental::propagate_const<boost::movelib::unique_ptr<Impl>>;
748
749 // Friends.
750
751 /// Friend of Native_socket_stream.
752 friend std::ostream& operator<<(std::ostream& os, const Native_socket_stream& val);
753 /// Friend of Native_socket_stream.
754 friend std::ostream& operator<<(std::ostream& os, const Impl& val);
755
756 // Methods.
757
758 /**
759 * Helper that simply returns #m_impl while guaranteeing that #m_impl is non-null upon return. All
760 * forwarding-to-#m_impl methods (including `const` ones) shall access #m_impl through this impl() method only.
761 *
762 * ### Design/rationale ###
763 * It returns #m_impl... but if it is null, then it is first re-initialized to a new default-cted
764 * Native_socket_stream::Impl. That is:
765 * - Any ctor will make #m_impl non-null.
766 * - But moving-from `*this` will make #m_impl null.
767 * - However *if* the user were to invoke a public method of `*this` after the latter, then that public method
768 * shall invoke impl() which shall make #m_impl non-null again, which will ensure the public method works fine.
769 *
770 * Why? Answer: This is the usual question of what to do with null m_impl (e.g., it's mentioned in cppreference.com
771 * pImpl page) which occurs when one moves-from a given object. There are various possibilities; but in our case we
772 * do want to enable regular use of a moved-from object; hence we promised that a moved-from object is simply in
773 * NULL (not-connected) state, as-if default-cted. So we do that lazily (on-demand) on encountering
774 * null m_impl. Of course we could do it directly inside move ctor/assignment (non-lazily), but in many cases such
775 * objects are abandoned in practice, so it's best not to start threads, etc., unless really desired. The
776 * null check cost is minor.
777 *
778 * Caveat: To be used by `const` methods this method must be `const`; but it must at times modify #m_impl
779 * (the pointer... not the pointee). For that reason #m_impl must be `mutable`. That is fine: `mutable` for
780 * lazy evaluation is a vanilla pattern.
781 *
782 * @return Reference to #m_impl.
783 */
784 Impl_ptr& impl() const;
785
786 /**
787 * Template-free version of async_end_sending() as required by pImpl idiom.
788 *
789 * @param on_done_func
790 * See async_end_sending().
791 * @return See async_end_sending().
792 */
793 bool async_end_sending_fwd(flow::async::Task_asio_err&& on_done_func);
794
795 /**
796 * Template-free version of async_receive_native_handle() as required by pImpl idiom.
797 *
798 * @param target_hndl
799 * See async_receive_native_handle().
800 * @param target_meta_blob
801 * See async_receive_native_handle().
802 * @param on_done_func
803 * See async_receive_native_handle().
804 * @return See async_receive_native_handle().
805 */
807 const util::Blob_mutable& target_meta_blob,
808 flow::async::Task_asio_err_sz&& on_done_func);
809
810 /**
811 * Template-free version of async_receive_blob() as required by pImpl idiom.
812 *
813 * @param target_blob
814 * See async_receive_blob().
815 * @param on_done_func
816 * See async_receive_blob().
817 * @return See async_receive_blob().
818 */
819 bool async_receive_blob_fwd(const util::Blob_mutable& target_blob,
820 flow::async::Task_asio_err_sz&& on_done_func);
821
822 // Data.
823
824 /**
825 * The true implementation of this class. See also our class doc header; and impl() (in particular explaining
826 * why this is `mutable`).
827 *
828 * Do not access directly but only via impl().
829 */
831}; // class Native_socket_stream
832
833// Free functions: in *_fwd.hpp.
834
835// Template implementations.
836
837template<typename Task_err>
838bool Native_socket_stream::async_end_sending(Task_err&& on_done_func)
839{
840 using flow::async::Task_asio_err;
841
842 /* Perf note: In all cases, as of this writing, Impl would wrap the various handler parameterized args in
843 * concrete Function<>s anyway for its own impl ease; so we change nothing by doing this higher up in the call stack
844 * in this template and its siblings below. */
845
846 return async_end_sending_fwd(Task_asio_err(std::move(on_done_func)));
847}
848
849template<typename Task_err_sz>
851 const util::Blob_mutable& target_meta_blob,
852 Task_err_sz&& on_done_func)
853{
854 using flow::async::Task_asio_err_sz;
855
856 return async_receive_native_handle_fwd(target_hndl, target_meta_blob, Task_asio_err_sz(std::move(on_done_func)));
857}
858
859template<typename Task_err_sz>
860bool Native_socket_stream::async_receive_blob(const util::Blob_mutable& target_blob, Task_err_sz&& on_done_func)
861{
862 using flow::async::Task_asio_err_sz;
863
864 return async_receive_blob_fwd(target_blob, Task_asio_err_sz(std::move(on_done_func)));
865}
866
867} // namespace ipc::transport
Internal, non-movable pImpl implementation of Native_socket_stream class.
Implements both Native_handle_sender and Native_handle_receiver concepts by using a stream-oriented U...
Impl_ptr & impl() const
Helper that simply returns m_impl while guaranteeing that m_impl is non-null upon return.
Native_socket_stream(const Native_socket_stream &)=delete
Copy construction is disallowed.
Native_socket_stream & operator=(const Native_socket_stream &)=delete
Copy assignment is disallowed.
size_t send_blob_max_size() const
Implements Blob_sender API per contract.
bool idle_timer_run(util::Fine_duration timeout=boost::chrono::seconds(5))
Implements Native_handle_receiver, Blob_receiver API per contract.
~Native_socket_stream()
Implements Native_handle_sender and Native_handle_receiver APIs at the same time, per their concept c...
const std::string & nickname() const
Returns nickname, a brief string suitable for logging.
bool async_receive_native_handle(Native_handle *target_hndl, const util::Blob_mutable &target_meta_blob, Task_err_sz &&on_done_func)
Implements Native_handle_receiver API per contract.
bool send_blob(const util::Blob_const &blob, Error_code *err_code=0)
Implements Blob_sender API per contract.
Native_socket_stream(Native_socket_stream &&src)
Move-constructs from src; src becomes as-if default-cted (therefore in NULL state).
bool send_native_handle(Native_handle hndl_or_null, const util::Blob_const &meta_blob, Error_code *err_code=0)
Implements Native_handle_sender API per contract.
static constexpr bool S_BLOB_UNDERFLOW_ALLOWED
Implements concept API; namely it is true: async_receive_blob() with smaller-than-receive_blob_max_si...
bool async_receive_blob_fwd(const util::Blob_mutable &target_blob, flow::async::Task_asio_err_sz &&on_done_func)
Template-free version of async_receive_blob() as required by pImpl idiom.
static const Shared_name & S_RESOURCE_TYPE_ID
Implements concept API.
size_t send_meta_blob_max_size() const
Implements Native_handle_sender API per contract.
static constexpr bool S_META_BLOB_UNDERFLOW_ALLOWED
Implements concept API; namely it is true: async_receive_native_handle() with smaller-than-receive_me...
bool sync_connect(const Shared_name &absolute_name, Error_code *err_code=0)
To be invoked in NULL state only, it synchronously and non-blockingly attempts to connect to an oppos...
bool async_end_sending_fwd(flow::async::Task_asio_err &&on_done_func)
Template-free version of async_end_sending() as required by pImpl idiom.
bool auto_ping(util::Fine_duration period=boost::chrono::seconds(2))
Implements Native_handle_sender, Blob_sender API per contract.
Native_socket_stream()
Default ctor (object is in NULL state).
bool async_receive_native_handle_fwd(Native_handle *target_hndl, const util::Blob_mutable &target_meta_blob, flow::async::Task_asio_err_sz &&on_done_func)
Template-free version of async_receive_native_handle() as required by pImpl idiom.
std::experimental::propagate_const< boost::movelib::unique_ptr< Impl > > Impl_ptr
Short-hand for const-respecting wrapper around Native_socket_stream::Impl for the pImpl idiom.
Impl_ptr m_impl
The true implementation of this class.
bool async_end_sending(Task_err &&on_done_func)
Implements Native_handle_sender, Blob_sender API per contract.
bool async_receive_blob(const util::Blob_mutable &target_blob, Task_err_sz &&on_done_func)
Implements Blob_receiver API per contract.
friend std::ostream & operator<<(std::ostream &os, const Native_socket_stream &val)
Friend of Native_socket_stream.
size_t receive_meta_blob_max_size() const
Implements Native_handle_receiver API per contract.
bool end_sending()
Implements Native_handle_sender, Blob_sender API per contract.
size_t receive_blob_max_size() const
Implements Blob_receiver API per contract.
util::Process_credentials remote_peer_process_credentials(Error_code *err_code=0) const
OS-reported process credential (PID, etc.) info about the other connected peer's process,...
Native_socket_stream & operator=(Native_socket_stream &&src)
Move-assigns from src; *this acts as if destructed; src becomes as-if default-cted (therefore in NULL...
Dummy type for use as a template param to Channel when either the blobs pipe or handles pipe is disab...
Definition: channel.hpp:1000
Implements both sync_io::Native_handle_sender and sync_io::Native_handle_receiver concepts by using a...
A process's credentials (PID, UID, GID as of this writing).
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
Flow-IPC module providing transmission of structured messages and/or low-level blobs (and more) betwe...
boost::asio::mutable_buffer Blob_mutable
Short-hand for an mutable blob somewhere in memory, stored as exactly a void* and a size_t.
Definition: util_fwd.hpp:134
flow::Fine_duration Fine_duration
Short-hand for Flow's Fine_duration.
Definition: util_fwd.hpp:111
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:128
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:109
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:297
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.