Flow-IPC 1.0.2
Flow-IPC project: Full implementation reference.
native_handle_transport.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
20// Not compiled: for documentation only. Contains concept docs as of this writing.
21#ifndef IPC_DOXYGEN_ONLY
22static_assert(false, "As of this writing this is a documentation-only \"header\" "
23 "(the \"source\" is for humans and Doxygen only).");
24#else // ifdef IPC_DOXYGEN_ONLY
25
26namespace ipc::transport
27{
28
29// Types.
30
31/**
32 * A documentation-only *concept* defining the behavior of an object capable of reliably/in-order *sending* of
33 * discrete messages, each containing a native handle, a binary blob, or both. This is paired with
34 * the Native_handle_receiver concept which defines reception of such messages.
35 *
36 * ### Concept contents ###
37 * The concept defines the following behaviors/requirements.
38 * - The object has at least 2 states:
39 * - NULL: Object is not a peer: is not connected/capable of transmission. Essentially it is not a useful
40 * state. A default-cted object is in NULL state; and a moved-from object is as-if default-cted, therefore
41 * also in a NULL state. In this state all the transmission methods return `false` and no-op.
42 * - PEER: Object is or has been a peer (connected/capable of transmission). It is not possible to exit PEER
43 * state, except via being moved-from. A pipe-hosing error retains PEER state for instance.
44 * - Other states are allowed but outside the scope of the concept. For example a CONNECTING state may or may
45 * not be relevant (but again not relevant to the concept).
46 * - The (outgoing) transmission-of-messages methods, including transmission of graceful-close message.
47 * See their doc headers.
48 * - Behavior when the destructor is invoked. See ~Native_handle_sender()
49 * doc header.
50 * - Default ctor (which creates a NULL-state object that can be moved-to and to which "as-if"
51 * state any moved-from object is changed).
52 * - `sync_io`-core adopting ctor (which creates PEER-state object from an idle, as-if-just cted PEER-state
53 * sync_io::Native_handle_sender).
54 * - Move ctor, move assigment operator; these do the reasonable thing including setting the moved-from object
55 * to NULL state. Various satellite APIs (e.g., Native_socket_stream_acceptor) shall
56 * need these. That is such APIs do not rely on the factory/shared-ownership pattern.
57 *
58 * The concept (intentionally) does *not* define the following behaviors:
59 * - How to create a Native_handle_sender, except the default, `sync_io`-core-adopting, and move ctors.
60 * That is it does not define how to create a *new* (not moved-from) and *functioning* (PEER state: transmitting,
61 * connected) peer object, except the latter from an existing `sync_io`-pattern core. It only defines behavior
62 * once the user has access to a Native_handle_sender that is already in PEER state: connected to a opposing
63 * peer Native_handle_receiver.
64 *
65 * @see Native_socket_stream: as of this writing one key class that implements this concept (and also
66 * Native_handle_receiver) -- using the Unix domain socket transport.
67 * @see Channel, a pipe-composing class template that potentially implements this concept.
68 * @see Blob_sender: a degenerate version of the present concept: capable of transmitting only blobs, not
69 * `Native_handle`s.
70 *
71 * @todo In C++20, if/when we upgrade to that, Native_handle_sender (and other such doc-only classes) can become an
72 * actual concept formally implemented by class(es) that, today, implement it via the "honor system."
73 * Currently it is a class `#ifdef`-ed out from actual compilation but still participating in doc generation.
74 * Note that Doxygen (current version as of this writing: 1.9.3) claims to support doc generation from formal
75 * C++20 concepts.
76 *
77 * ### Rationale: Why is send_native_handle() not asynchronous? ###
78 * send_native_handle(), as noted in its doc header (and similarly Blob_sender::send_blob()) has 2 key properties:
79 * - It will *never* return would-block despite *always* being non-blocking.
80 * - It takes no completion handler; and it returns pipe-hosing errors synchronously via an out-arg or
81 * exception.
82 * - async_end_sending() does take a completion handler.
83 *
84 * That is, it is "somehow" both non-blocking/synchronous -- yet requires no would-block contingency on the user's
85 * part. Magical! How can this possibly work? After all we know that all known low-level transports will yield
86 * would-block eventually (as of this writing -- Unix domain sockets, POSIX MQs, bipc SHM-based MQs), if the
87 * opposing side is not popping data off the pipe. Yet, as we look at the opposing Native_handle_receiver concept
88 * we see that it makes no requirement on its impls to pop everything off the low-level transport in-pipe as soon
89 * as it arrives. (In actual fact all existing impls in Flow-IPC refuse to do any such thing.)
90 *
91 * Naturally the only answer as to how it can work is: Any given Native_handle_sender (and native Blob_sender) impl
92 * shall be ready to encounter would-block internally; if this occurs it will need to make a copy of the offending
93 * message (the meta-blob part specifically included -- that's the part that involves significant copying),
94 * enqueue it internally -- and keep enqueuing any further send_native_handle()d messages until the would-block
95 * clears, and the queue is eventually emptied.
96 *
97 * Fair enough: But isn't this a performance hazard? Answer: yes but only in a necessary way. We reached this
98 * decision after considering the entire system end-to-end:
99 * -# user prepares message blob B and tells `Native_handle_sender` S to send B;
100 * -# S tells low-level transport (possibly kernel) to accept B;
101 * -# `Native_handle_receiver` R pops B from low-level transport;
102 * -# user tells R it wanst to accept B, and R does give the latter to the user.
103 *
104 * It was a no-go to have Flow-IPC be in charge of allocating either-end buffer for B on the user's behalf:
105 * the user may want to user their own allocator, or the stack, or ??? -- possibly with zero-copy and prefix/postfix
106 * data nearby. The user buffer has to be ready, and S and R need to work with the memory areas provided by the
107 * user.
108 *
109 * Given all that, *one* or more of the following "people" has to be ready to keep B, or a copy of B, around, until
110 * the whole thing (1-4) has gone through: the sending user (1 above), S (2 above), or R (3 above). (If
111 * the receiving user (4 above) is always receiving things ASAP, without that process being processor-pegged or
112 * something, then it won't be a problem: no copying is necessary, except in step 2 when B is copied into
113 * the low-level process. Indeed that should be the case usually; we are discussing what to do *if* something goes
114 * wrong, or the receivier application is mis-coded, or who knows -- the receiver just isn't quick enough.)
115 *
116 * The answer, as already noted: *One* or more of 1, 2, 3 has to make a copy of B long enough until 4 has received it.
117 * At that point, in terms of overall performance, it *which* one of 1, 2, 3 it should be. Upon deliberating
118 * a few options occurred, but it seemed clear enough that 1 (original user) should not be the done, if it can be
119 * helped. IPC is not networking; as an end user I expect sending an out-message to work synchronously. Making
120 * me worry about a completion handler complicates the outgoing-direction API hugely -- and not in a way that
121 * makes the incoming-direction API any simpler, since that one always has to be ready for would-block
122 * (i.e., no in-messages immediately ready -- an async-receive API is required).
123 *
124 * So that left 2 or 3. I (ygoldfel) simply made the decision that one has to be chosen, and of those 2 is earlier
125 * and (internally for the concept implementer, also me at the time) more straightforward. If 3 were always
126 * reading items out of the low-level transport -- even when the user was not invoking `async_receive_*()` --
127 * then it would have to at least sometimes make copies of incoming data before the user chose to pop them out.
128 * By contrast, queueing it -- only on would-block -- in 2 would effectively make it a last-resort activity, as
129 * it should be.
130 *
131 * To summarize: Perfect opposing-receiver behavior cannot be guaranteed, and the low-level transport will eventually
132 * have a limit as to how much data it can buffer => in that case some entity must make a copy of the overflowing data
133 * => we want to make life easier for end user, so that entity should not be them => it must be either
134 * the `*_sender` or `*_receiver` => we chose `*_sender`, because it is safer and easier and faster and a last resort.
135 *
136 * One arguable drawback of this API design is that the Native_handle_sender (and Blob_sender) user won't be
137 * informed of an out-pipe-hosing error quite as soon as that object itself would know of it -- in some (rare)
138 * cases. I.e., if some data are queued up during would-block, and the user has stopped doing `send_*()`
139 * (as far as she is concerned, they're all working fine and have succeeded), and some timer later
140 * `*this` determines (in the background now) that the transport is hosed, the user would only find out about it
141 * upon the next `send_*()` call (it would *then* return the `Error_code` immediately), if any, or via
142 * async_end_sending(). Is this really a problem? Answer: Just... no. Exercise in reasoning this out (granted
143 * it is *somewhat* subjective) left to the reader. (Note we *could* supply some kind of out-of-band
144 * on-any-send-error API, and we considered this. It just was not worth it: inelegant, annoying. async_end_sending()
145 * with its completion handler is well sufficient.)
146 *
147 * It should be noted that this decision to make send_native_handle() synchronous/non-blocking/never-would-blocking
148 * has percolated through much of ipc::transport and even ipc::session: all our impls; Blob_sender and all its
149 * impls; Channel; and quite significantly struc::Channel. You'll note struc::Channel::send() is
150 * also synchronous/non-blocking/never-would-blocking, and even its implementation at the stage of actually
151 * sending binary data via Native_handle_sender/Blob_sender is quite straightforward. In Flow-IPC a
152 * `send*()` "just works."
153 *
154 * @todo Comparing Blob_sender::send_blob_max_size() (and similar checks in that family of concepts) to test
155 * whether the object is in PEER state is easy enough, but perhaps we can have a utility that would more
156 * expressively describe this check: `in_peer_state()` free function or something? It can still use the same
157 * technique internally.
158 */
160{
161public:
162 // Constants.
163
164 /// Shared_name relative-folder fragment (no separators) identifying this resource type. Equals `_receiver`'s.
166
167 // Constructors/destructor.
168
169 /**
170 * Default ctor: Creates a peer object in NULL (neither connected nor connecting) state. In this state,
171 * all transmission-related methods (e.g., send_native_handle()) shall return `false` and otherwise no-op
172 * (aside from possible logging).
173 *
174 * This ctor is informally intended for the following uses:
175 * - A moved-from Native_handle_sender (i.e., the `src` arg for move-ctor and move-assignment operator)
176 * becomes as-if defaulted-constructed.
177 * - A target Native_handle_sender for a factory-like method (such as Native_socket_stream_acceptor::async_accept())
178 * shall typically be default-cted by the callin guse. (E.g.: Native_socket_stream_acceptor shall asynchronously
179 * move-assign a logger-apointed, nicely-nicknamed into that target `*this`, typically default-cted.)
180 *
181 * ### Informal corollary -- ctor that begins in PEER state ###
182 * Any functioning Native_handle_sender shall need at least one ctor that starts `*this` directly in PEER state.
183 * In that state the transmission-related methods (e.g., send_native_handle()) shall *not* return `false` and
184 * will in fact attempt transmission. However the form and function of such ctors is entirely dependent on
185 * the nature of the low-level transmission medium being encapsulated and is hence *intentionally* not a part
186 * of the concept. That said there is the `sync_io`-core-adopting ctor as well which *is* part of the concept.
187 *
188 * ### Informal corollary -- NULL-state ctors with 1+ args ###
189 * Other, 1+ arg, ctors that similarly create a NULL-state peer object are allowed/encouraged
190 * where relevant. In particular one taking a `Logger*` and a `nickname` string -- so as to subsequently enter a
191 * connecting phase via an `*_connect()` method, on the way to PEER state -- is reasonable.
192 * However this is not a formal part of this concept and is arguably not a general behavior.
193 * Such a ctor is informally intended for the following use at least:
194 * - One creates a Native_handle_sender that is `Logger`-appointed and nicely-`nickname`d; then one calls
195 * `*_connect()` on it in order to move it to a connecting phase and, hopefully, PEER state in that order.
196 * It will retain the logger and nickname (or whatever) throughout.
197 */
199
200 /**
201 * `sync_io`-core-adopting ctor: Creates a peer object in PEER state by subsuming a `sync_io` core in that state.
202 * That core must be as-if-just-cted (having performed no work and not been configured via `.start_*_ops()`).
203 * That core object becomes as-if default-cted (therefore in NULL state).
204 *
205 * @param sync_io_core_in_peer_state_moved
206 * See above.
207 */
208 Native_handle_sender(sync_io::Native_handle_sender&& sync_io_core_in_peer_state_moved);
209
210 /**
211 * Move-constructs from `src`; `src` becomes as-if default-cted (therefore in NULL state).
212 *
213 * @param src
214 * Source object. For reasonable uses of `src` after this ctor returns: see default ctor doc header.
215 */
217
218 /// Disallow copying.
220
221 /**
222 * Destroys this peer endpoint which will end the conceptual outgoing-direction pipe (in PEER state, and if it's
223 * still active) and cancels any pending completion handlers by invoking them ASAP with
224 * error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER.
225 * As of this writing these are the completion handlers that would therefore be called:
226 * - The handler passed to async_end_sending(), if it was not `.empty()` and has not yet been invoked.
227 * Since it is not valid to call async_end_sending() more than once, there is at most 1 of these.
228 *
229 * The pending completion handler will be called from an unspecified thread that is not the calling thread.
230 * Any associated captured state for that handler will be freed shortly after the handler returns.
231 *
232 * We informally but very strongly recommend that your completion handler immediately return if the `Error_code`
233 * passed to it is `error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER`. This is similar to what one should
234 * do when using boost.asio and receiving the conceptually identical `operation_aborted` error code to an
235 * `async_...()` completion handler. In both cases, this condition means, "we have decided to shut this thing down,
236 * so the completion handlers are simply being informed of this."
237 *
238 * In NULL state the dtor shall no-op.
239 *
240 * ### Thread safety ###
241 * Same as for the transmission methods. *Plus* it is not safe to call this from within a completion handler supplied
242 * to one of those methods on the same `*this`. An implication of the latter is as follows:
243 *
244 * Any user source code line that nullifies a `Ptr` (`shared_ptr`) handle to `*this` should be seen as potentially
245 * *synchoronously* invoking this dtor. (By nullification we mean `.reset()` and anything else that makes the
246 * `Ptr` null. For example destroying a `vector<Ptr>` that contains a `Ptr` pointing to `*this` = nullification.)
247 * Therefore, if a nullification statement can possibly make the ref-count reach 0, then the user must zealously
248 * protect that statement from running concurrently with another send/receive API call on `*this`. That is to say,
249 * informally: When ensuring thread safety with respect to concurrent access to a Native_handle_sender, don't forget
250 * that the destructor also is a write-type of access, and that nullifying a `Ptr` handle to it can synchronously
251 * invoke it.
252 *
253 * @see Native_handle_receiver::~Native_handle_receiver(): sister concept with essentially equal requirements.
254 * @see Native_socket_stream::~Native_socket_stream(): implements concept (also implements just-mentioned sister
255 * concept).
256 */
258
259 // Methods.
260
261 /**
262 * Move-assigns from `src`; `*this` acts as if destructed; `src` becomes as-if default-cted (therefore in NULL state).
263 * No-op if `&src == this`.
264 *
265 * @see ~Native_handle_sender().
266 *
267 * @param src
268 * Source object. For reasonable uses of `src` after this ctor returns: see default ctor doc header.
269 * @return `*this`.
270 */
272
273 /// Disallow copying.
275
276 /**
277 * In PEER state: Returns max `meta_blob.size()` such that send_native_handle() shall not fail due to too-long
278 * payload with error::Code::S_INVALID_ARGUMENT. Always the same value once in PEER state. The opposing
279 * Blob_receiver::receive_meta_blob_max_size() shall return the same value (in the opposing object potentially
280 * in a different process).
281 *
282 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns zero; else
283 * a positive value.
284 *
285 * @return See above.
286 */
288
289 /**
290 * In PEER state: Synchronously, non-blockingly sends one discrete message, reliably/in-order, to the opposing peer;
291 * the message consists of the provided native handle (if supplied); or the provided binary blob (if supplied);
292 * or both (if both supplied). The opposing peer's paired Native_handle_receiver shall receive it reliably and
293 * in-order via Native_handle_receiver::async_receive_native_handle().
294 *
295 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns `false` immediately
296 * instead and otherwise no-ops (logging aside).
297 *
298 * Providing neither a handle nor a blob results in undefined behavior (assertion may trip).
299 * To *not* supply a handle, provide object with `.null() == true`. To *not* supply a blob, provide
300 * a buffer with `.size() == 0`.
301 *
302 * ### Blob copying behavior; synchronicity/blockingness guarantees ###
303 * If a blob is provided, it need only be valid until this method returns. The implementation shall, informally,
304 * strive to *not* save a copy of the blob (except into the low-level transport mechanism); but it is *allowed* to
305 * save it if required, such as if the low-level transport mechanism encounteres a would-block condition.
306 *
307 * This means, to the user, that this method is *both* non-blocking *and* synchronous *and* it must not
308 * refuse to send and return any conceptual would-block error (unlike, say, with a typical networked TCP socket API).
309 * Informally, the implementation must deal with any (typically quite rare) internal low-level would-block condition
310 * internally.
311 *
312 * However! Suppose send_native_handle() returns without error. This concept does *not* guarantee the message has
313 * been passed to the low-level transport at *this* point in time (informally it shall strive to make that
314 * happen in most cases however). A low-level would-block condition may cause it to be deferred. If one
315 * invokes ~Native_handle_sender() (dtor), the message may never be sent.
316 *
317 * However (part II)! It *is* guaranteed to have been passed to the transport once `*end_sending()`
318 * has been invoked, *and* its `on_done_func()` (if any) callback has been called. Therefore the expected use
319 * pattern is as follows:
320 * - Obtain a connected `Native_handle_sender` via factory.
321 * - Invoke send_native_handle() synchronously, hopefully without error.
322 * - (Repeat as needed for more such messages.)
323 * - Invoke `async_end_sending(F)` or `end_sending()` (to send graceful-close).
324 * - In `F()` destroy the `Native_handle_sender` (its dtor is invoked).
325 * - `F()` is invoked only once all queued messages, and the graceful-close, have been sent.
326 *
327 * ### Error semantics ###
328 * Firstly: See `flow::Error_code` docs for error reporting semantics (including the exception/code dichotomy).
329 *
330 * Secondly: If the generated #Error_code is error::Code::S_INVALID_ARGUMENT:
331 * - This refers to an invalid argument to *this* method invocation. This does *not* hose the pipe:
332 * further sends, etc., may be attempted with reasonable hope they will succeed.
333 * - send_meta_blob_max_size() is the limit for `meta_blob.size()`. Exceeding it leads to `S_INVALID_ARGUMENT`.
334 * - This interacts with the opposing Native_handle_receiver::async_receive_native_handle() in a subtle
335 * way. See "Blob underflow semantics" in that class concept's doc header.
336 * - At the impl's discretion, other conditions *may* lead to `S_INVALID_ARGUMENT`. If so they must
337 * be documented.
338 *
339 * Thirdly: All other non-success `Error_code`s generated should indicate the pipe is now indeed hosed; the user
340 * can count on the pipe being unusable from that point on. (It is recommended they cease to use the pipe
341 * and invoke the destructor to free resources.) If such a non-success code is emitted, the same one shall be emitted
342 * by all further send_native_handle() and async_end_sending() on `*this`.
343 *
344 * In particular error::Code::S_SENDS_FINISHED_CANNOT_SEND shall indicate that *this* method invocation
345 * occurred after invoking `*end_sending()` earlier.
346 *
347 * No would-block-like error condition shall be emitted.
348 *
349 * Lastly: The implementation is allowed to return a non-`INVALID_ARGUMENT` #Error_code in *this* invocation even
350 * though the true cause is a *previous* invocation of send_native_handle().
351 * Informally this may occur (probably in rare circumstances) if internally the handling of a previous
352 * call had to be deferred due to a low-level would-block condition; in that case the resulting problem is to be
353 * reported opportunistically with the next call. This allows for the non-blocking/synchronous semantics without
354 * the need to make send_native_handle() asynchronous.
355 *
356 * ### Thread safety ###
357 * You may call this from any thread. It is *not* required to be safe to call concurrently with
358 * any Native_handle_sender API, including this one and the destructor, invoked on `*this`.
359 *
360 * @param hndl_or_null
361 * A native handle (a/k/a FD) to send; or none if `hndl_or_null.null() == true`.
362 * @param meta_blob
363 * A binary blob to send; or none if `meta_blob.size() == 0`.
364 * @param err_code
365 * See above.
366 * @return `false` if `*this` is not in PEER (connected, transmitting) state;
367 * otherwise `true`.
368 */
369 bool send_native_handle(Native_handle hndl_or_null, const util::Blob_const& meta_blob, Error_code* err_code = 0);
370
371 /**
372 * Equivalent to send_native_handle() but sends a graceful-close message instead of the usual payload; the opposing
373 * peer's Native_handle_receiver shall receive it reliably and in-order via
374 * Native_handle_receiver::async_receive_native_handle() in the form of
375 * #Error_code = error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE. If invoked after already invoking
376 * `*end_sending()`, the method shall no-op and return `false` and neither an exception nor truthy
377 * `*err_code`. Otherwise it shall return `true` -- but potentially emit a truthy #Error_code (if an error is
378 * detected).
379 *
380 * In addition: if `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns
381 * `false` immediately instead and otherwise no-ops (logging aside).
382 *
383 * Informally one should think of async_end_sending() as just another message, which is queued after
384 * preceding ones; but: it is the *last* message to be queued by definition. It's like an EOF or a TCP-FIN.
385 *
386 * ### Synchronicity/blockingness guarantees ###
387 * async_end_sending() is, unlike send_native_handle(), formally asynchronous. The reason for this,
388 * and the expected use pattern of async_end_sending() in the context of preceding send_native_handle()
389 * calls, is found in the doc header for Native_handle_sender::send_native_handle(). We omit further discussion here.
390 *
391 * ### Error semantics ###
392 * This section discusses the case where `true` is returned, meaning `*end_sending()` had not already
393 * been called.
394 *
395 * An #Error_code is generated and passed as the sole arg to `on_done_func()`.
396 * A falsy one indicates success. A truthy one indicates failure; but in particular:
397 * - error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER (destructor called, canceling all pending ops;
398 * spiritually identical to `boost::asio::error::operation_aborted`);
399 * - other arbitrary codes are possible, as with send_native_handle().
400 *
401 * Reminder: By the nature of `on_done_func()`, the pipe is finished regardless of whether it receives a
402 * success or non-success #Error_code.
403 *
404 * ### Thread safety ###
405 * The notes for send_native_handle() apply.
406 *
407 * @internal
408 * The semantic re. calling `*end_sending()` after already having called it and having that exclusively
409 * return `false` and do nothing was a judgment call. As of this writing there's a long-ish comment at the top of
410 * of `"sync_io::Native_socket_stream::Impl::*end_sending()"`" body discussing why I (ygoldfel) went that way.
411 * @endinternal
412 *
413 * @tparam Task_err
414 * A functor type with signature identical to `flow::async::Task_asio_err`.
415 * @param on_done_func
416 * See above. This shall be invoked from an unspecified thread that is not the calling thread.
417 * @return `false` if and only if *either* `*this` is not in PEER (connected, transmitting) state, *or*
418 * `*end_sending()` has already been called before (so this is a no-op, in both cases).
419 * Otherwise `true`.
420 */
421 template<typename Task_err>
422 bool async_end_sending(Task_err&& on_done_func);
423
424 /**
425 * Equivalent to `async_end_sending(F)` wherein `F()` does nothing.
426 *
427 * Informally: If one uses this overload, it is impossible to guarantee everything queued has actually been sent,
428 * as `on_done_func()` is the only mechanism for this. Most likely this is only useful for test or proof-of-concept
429 * code; production-level robustness typically requires one to ensure everything queued has been sent. Alternatively
430 * the user's application-level protocol may already guarantee the message exchange has completed (e.g., by receiving
431 * an acknowledgment message of some sort) -- but in that case one can simply not call `*end_sending()`
432 * at all.
433 *
434 * @return Same as in async_end_sending().
435 */
437
438 /**
439 * In PEER state: Irreversibly enables periodic auto-pinging of opposing receiver with low-level messages that
440 * are ignored except that they reset any idle timer as enabled via Native_handle_receiver::idle_timer_run()
441 * (or similar). Auto-pings, at a minimum, shall be sent (subject to internal low-level transport would-block
442 * conditions) at the following times:
443 * - as soon as possible after a successful auto_ping() call; and subsequently:
444 * - at such times as to ensure that the time between 2 adjacent sends of *any* message is no more than
445 * `period`, until `*end_sending()` (if any) or error (if any).
446 * To clarify the 2nd point: If user messages are being sent already, an auto-ping may not be required or may be
447 * delayed until the last-sent message plus `period`). The implementation shall strive not to wastefully
448 * add auto-ping traffic unnecessary to this goal but is not *required* to do so. However, per the 1st point,
449 * an auto-ping shall be sent near auto_ping() time to establish a baseline.
450 *
451 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns `false` immediately
452 * instead and otherwise no-ops (logging aside). If auto_ping() has already been called successfuly,
453 * subsequently it will return `false` and no-op (logging aside). If `*end_sending()` has been called succesfully,
454 * auto_ping() will return `false` and no-op (logging side).
455 *
456 * ### Behavior past `*end_sending()` ###
457 * As noted: auto_ping() returns `false` and no-ops if invoked after successful `*end_sending()`.
458 * Rationale: If `*end_sending()` has been called, then the receiver shall (assuming no other error) receive
459 * graceful-close (and hose the in-pipe as a result) as soon as possible, or it has already received it
460 * (and hosed the pipe). Therefore any potential "further" hosing of the pipe due to idle timer would be redundant
461 * and ignored anyway given the nature of the Native_handle_receiver (and similar) API.
462 *
463 * ### Thread safety ###
464 * The notes for send_native_handle() apply.
465 *
466 * @param period
467 * Pinging occurs so as to cause the opposing receiver to receive *a* message (whether auto-ping or user
468 * message) at least this frequently, subject to limitations of the low-level transport.
469 * The optional default is chosen by the impl to most reasonably work with the opposing `idle_timer_run()`
470 * to detect a zombified/overloaded peer.
471 * @return `false` if `*this` is not in PEER (connected, transmitting) state, or if already called successfully
472 * in `PEER` state, or if `*end_sending()` was called successfully in `PEER` state; otherwise `true`.
473 */
474 bool auto_ping(util::Fine_duration period = default_value);
475}; // class Native_handle_sender
476
477/**
478 * A documentation-only *concept* defining the behavior of an object capable of reliably/in-order *receiving* of
479 * discrete messages, each containing a native handle, a binary blob, or both. This is paired with
480 * the Native_handle_sender concept which defines sending of such messages.
481 *
482 * Concept contents
483 * ----------------
484 * The concept defines the following behaviors/requirements.
485 * - The object has at least 2 states, NULL and PEER. See notes in Native_handle_sender w/r/t this;
486 * they apply equally here.
487 * - The (incoming) transmission-of-messages methods, including reception of graceful-close message and
488 * cutting off any further receiving. See their doc headers.
489 * - Behavior when the destructor is invoked. See ~Native_handle_receiver() doc header.
490 * - Default ctor. See notes in Native_handle_sender w/r/t this; they apply equally here.
491 * - `sync_io`-core adopting ctor. Same deal.
492 * - Move ctor, move assigment operator. Same deal.
493 *
494 * The concept (intentionally) does *not* define the following behaviors:
495 * - How to create a Native_handle_receiver, except the default, `sync_io`-core-adopting, and move ctors.
496 * Notes for Native_handle_sender apply equally here.
497 *
498 * @see Native_socket_stream: as of this writing one key class that implements this concept (and also
499 * Native_handle_sender) -- using the Unix domain socket transport.
500 * @see Channel, a pipe-composing class template that potentially implements this concept.
501 * @see Blob_receiver: a degenerate version of the present concept: capable of transmitting only blobs, not
502 * `Native_handle`s.
503 *
504 * Blob underflow semantics
505 * ------------------------
506 * This potentially important subtlety is regarding the interplay between
507 * Native_handle_receiver::S_META_BLOB_UNDERFLOW_ALLOWED, async_receive_native_handle() `meta_blob.size()`,
508 * and Native_handle_sender::send_native_handle() `meta_blob.size()`.
509 *
510 * Consider a `*this` named `R` and an opposing `*_sender` named `S`. Note that `R.receive_meta_blob_max_size()
511 * == S.send_meta_blob_max_size()`; call this limit `L`. `L` shall always be in practice small enough to where
512 * allocating a buffer of this size as the receive target is reasonable; hence kilobytes at most, not megabytes.
513 * (In some cases, such as with `Blob_stream_mq_*`, it is configurable at runtime. In others, as with
514 * Native_socket_stream, it is a constant: sync_io::Native_socket_stream::S_MAX_META_BLOB_LENGTH.)
515 *
516 * As noted in that concept's docs, naturally, `S.send_native_handle()` shall yield immediate, non-pipe-hosing
517 * error::Code::S_INVALID_ARGUMENT, if `meta_blob.size() > L`. This is called overflow and is always enforced.
518 *
519 * Now consider `N = meta_blob.size()` for `R.async_receive_native_handle()`. This is the size of the target
520 * blob: how many bytes it *can* receive. How `L` is enforced by `R` is not uniform among this concept's impls:
521 * rather it depends on the compile-time constant Native_handle_receiver::S_META_BLOB_UNDERFLOW_ALLOWED.
522 *
523 * ### Semantics: `META_BLOB_UNDERFLOW_ALLOWED` is `false` ###
524 * Suppose it is `false` (as is the case for Blob_stream_mq_receiver). In that case `N < L` is *disallowed*:
525 * `R.async_receive_native_handle()` *shall always* fail with non-pipe-hosing error::Code::S_INVALID_ARGUMENT
526 * for that reason. Certainly `N` cannot be zero -- not even if the incoming message is expected to
527 * contain only a non-`.null() Native_handle`.
528 *
529 * @note Formally it's as written. Informally the rationale for allowing this restriction is that some low-level
530 * transports have it; and working around it would probably affect performance and is unnatural.
531 * In particular Persistent_mq_handle::try_receive() (MQ receive) shall immediately fail if a target buffer
532 * is supplied smaller than Persistent_mq_handle::max_msg_size(). For both POSIX (`man mq_receive`) and
533 * bipc (`boost::interprocess::message_queue::try_receive()` docs and source code) it is disallowed at the
534 * low-level API level.
535 *
536 * Thus since `N >= L` is guaranteed before any actual receipt attempt is made, it is *impossible* for
537 * a message to arrive such that its meta-blob's size would overflow the target blob (buffer) (of size `N`).
538 * Therefore error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE shall not be emitted.
539 *
540 * ### Semantics: `META_BLOB_UNDERFLOW_ALLOWED` is `true` ###
541 * Suppose it is `true` (as is the case for Native_socket_stream). In that case `N < L` is allowed, in the sense
542 * that `R.async_receive_native_handle()` *shall not* fail with non-pipe-hosing error::Code::S_INVALID_ARGUMENT
543 * for that reason. In particular, in that case, `N` can even be zero: this implies the incoming message *must*
544 * contain a non-`.null() Native_handle` (it is not allowed to send a message with no content at all).
545 * (In the case of degenerate concept Blob_sender, `N` must exceed zero, as it cannot transmit anything but
546 * blobs.)
547 *
548 * However suppose a message has arrived, and its meta-blob's size -- which cannot exceed `L` -- is larger than
549 * `N`. I.e., it would *underflow* the user-supplied target buffer (blob). In that case:
550 * - async_receive_native_handle() shall emit error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE.
551 * - This error shall be pipe-hosing (further receive attempts will yield the same error).
552 *
553 * Informal tips: Any blob-size error can be completely avoided by always supplying target blob with `N == L`.
554 * (`N > L` will also work but may be considered wasteful, all else being equal.) In addition this tactic will
555 * ensure the code will generically work with a different Native_handle_receiver impl for which
556 * `S_META_BLOB_UNDERFLOW_ALLOWED == false`. Alternatively, the user protocol may be such that you simply know
557 * that `S` will never send messages beyond some limit (smaller than `L`), perhaps for particular messages.
558 * In that case `N` can be set to this smaller-than-`L` value. However -- such code will fail
559 * if the concept impl is later to switched to one with `S_META_BLOB_UNDERFLOW_ALLOWED == true`. This may or
560 * may not be an issue depending on your future dev plans.
561 *
562 * ### Summary ###
563 * This dichotomy of possible semantics is a conscious choice. Mode `false` is a necessity: some low-level transports
564 * enforce it, period. Mode `true` is to support a possible algorithmic desire to allocate smaller-than-`L`
565 * target buffers. The problem is some low-level transports allow this; but others don't. We did not want to
566 * impose one type's limitations on users of the other type. Therefore, if your code is agnostic to the type
567 * of transport, then code as-if `S_META_BLOB_UNDERFLOW_ALLOWED == false`. Otherwise you may code otherwise.
568 *
569 * Rationale: Why no `end_receiving()`, given opposing concept has `*end_sending()`?
570 * ---------------------------------------------------------------------------------
571 * An early version of Native_handle_receiver and Blob_receiver did have an `end_receiving()`.
572 * The idea was it would have 2 effects (plus 1 bonus):
573 * -# Any ongoing `async_receive_*()`s would be interrupted with in-pipe-hosing error
574 * error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE -- the same one as when receing a graceful-close
575 * from opposing `*end_sending()`.
576 * -# There would be no further reading from the low-level transport for any reason.
577 * -# There was also the *thought*, but not the action, that an impl could inform the *opposing sender* of the
578 * pipe being closed by the reader. This would be conceptually similar to SIGPIPE in POSIX. In practice, of
579 * the available low-level transports as of this writing, only Native_socket_stream could actually implement it,
580 * as the otherwise-full-duplex independent pipes do live in this same peer (Unix domain socket), so the out-pipe
581 * could be used to inform the other guy that *his* out-pipe (our in-pipe) is now pointless. `Blob_stream_mq_*`
582 * are fully independent, operating on separate MQ kernel objects, so it was not realistically possible in
583 * that case.
584 *
585 * In order: (1) is fairly dubious: the local user should know when they've stopped receiving and don't need to
586 * use this mechanism to inform themselves. (3), as stated, sounded like a possible future improvement, but time
587 * told us it was not going to work out: if only a 2-pipe transport can do it in any case, organizing a concept
588 * around such a thing is just complicated. If necessary the user can (and we think likely will) just arrange
589 * their own protocol. So that leaves (2).
590 *
591 * To begin with -- in reality user protocols tend to be designed to not need such measures. If the reader is no
592 * longer interested, writer will probably know that. This is not networking after all: it is IPC.
593 * That aside, though, it only makes sense in any case if an impl *chooses* to greedily cache all low-level
594 * in-traffic in user RAM, even while no `async_receive_*()`s are pending to read it. Early on we actually did do
595 * that; but with the move to `sync_io`-pattern cores, and after contemplating
596 * "Rationale: Why is send-native-handle not asynchronous?" (Native_handle_sender concept doc header),
597 * we got rid of this waste of compute, letting unwanted in-traffic accumulate within a sender peer object
598 * (Native_handle_sender, Blob_sender) only. So in practice:
599 *
600 * If `end_receiving()` does not signal anything of value ((1) and (3)), and does not actually prevent any
601 * low-level reading that would otherwise occur, then it is a pointless complication. So that is why we got rid of it.
602 */
604{
605public:
606 // Constants.
607
608 /// Shared_name relative-folder fragment (no separators) identifying this resource type. Equals `_sender`'s.
610
611 /**
612 * If `false` then `meta_blob.size() > receive_meta_blob_max_size()` in PEER-state async_receive_native_handle()
613 * shall yield non-pipe-hosing error::Code::INVALID_ARGUMENT, and it shall never yield
614 * pipe-hosing error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE; else the latter may occur, while the former
615 * shall never occur for that reason.
616 *
617 * @see "Blob underflow semantics" in class concept doc header.
618 */
619 static constexpr bool S_META_BLOB_UNDERFLOW_ALLOWED = value;
620
621 // Constructors/destructor.
622
623 /**
624 * Default ctor: Creates a peer object in NULL (neither connected nor connecting) state. In this state,
625 * all transmission-related methods (e.g., async_receive_native_handle()) shall return `false` and otherwise no-op
626 * (aside from possible logging).
627 *
628 * All notes from Native_handle_sender() default ctor doc header apply here analogously. They are helpful so
629 * please read Native_handle_sender() doc header.
630 */
632
633 /**
634 * `sync_io`-core-adopting ctor: Creates a peer object in PEER state by subsuming a `sync_io` core in that state.
635 * That core must be as-if-just-cted (having performed no work and not been configured via `.start_*_ops()`).
636 * That core object becomes as-if default-cted (therefore in NULL state).
637 *
638 * @param sync_io_core_in_peer_state_moved
639 * See above.
640 */
641 Native_handle_receiver(sync_io::Native_handle_receiver&& sync_io_core_in_peer_state_moved);
642
643 /**
644 * Move-constructs from `src`; `src` becomes as-if default-cted (therefore in NULL state).
645 *
646 * @param src
647 * Source object. For reasonable uses of `src` after this ctor returns: see default ctor doc header.
648 */
650
651 /// Disallow copying.
653
654 /**
655 * Destroys this peer endpoint which will end the conceptual incoming-direction pipe (in PEER state, and if it's
656 * still active) and cancels any pending completion handlers by invoking them ASAP with
657 * error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER.
658 * As of this writing these are the completion handlers that would therefore be called:
659 * - Any handler passed to async_receive_native_handle() that has not yet been invoked.
660 * There can be 0 or more of these.
661 *
662 * The pending completion handler(s) (if any) will be called from an unspecified thread that is not the calling
663 * thread. Any associated captured state for that handler will be freed shortly after the handler returns.
664 *
665 * The rest of the notes in ~Native_handle_sender() apply equally here.
666 *
667 * @see Native_handle_sender::~Native_handle_sender(): sister concept with essentially equal requirements.
668 * @see Native_socket_stream::~Native_socket_stream(): implements concept (also implements just-mentioned sister
669 * concept).
670 */
672
673 // Methods.
674
675 /**
676 * Move-assigns from `src`; `*this` acts as if destructed; `src` becomes as-if default-cted (therefore in NULL state).
677 * No-op if `&src == this`.
678 *
679 * @see ~Native_handle_receiver().
680 *
681 * @param src
682 * Source object. For reasonable uses of `src` after this ctor returns: see default ctor doc header.
683 * @return `*this`.
684 */
686
687 /// Disallow copying.
689
690 /**
691 * In PEER state: Returns min `target_meta_blob.size()` such that (1) async_receive_native_handle() shall not fail
692 * with error::Code::S_INVALID_ARGUMENT (only if #S_META_BLOB_UNDERFLOW_ALLOWED is `false`; otherwise not relevant),
693 * and (2) it shall *never* fail with error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE. Please see
694 * "Blob underflow semantics" for explanation of these semantics.
695 *
696 * Always the same value once in PEER state. The opposing
697 * Blob_sender::send_meta_blob_max_size() shall return the same value (in the opposing object potentially
698 * in a different process).
699 *
700 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns zero; else
701 * a positive value.
702 *
703 * @return See above.
704 */
706
707 /**
708 * In PEER state: Asynchronously awaits one discrete message -- as sent by the opposing peer via
709 * Native_handle_sender::send_native_handle() or `"Native_handle_sender::*end_sending()"` -- and
710 * receives it into the given target locations, reliably and in-order. The message is, therefore, one of the
711 * following:
712 * - A binary blob; a native handle; or both. This is indicated by `on_done_func(Error_code(), N)`.
713 * The falsy code indicates success; `N <= target_meta_blob.size()` indicates the number of bytes received into
714 * `target_meta_blob.data()` (zero means no blob was sent in the message). `*target_hndl` is set
715 * (`target_hndl->null() == true` means no handle was sent in the message).
716 * - Graceful-close. This is indicated by `on_done_func(error::code::S_RECEIVES_FINISHED_CANNOT_RECEIVE, 0)`;
717 * neither the target blob nor target native handle are touched.
718 *
719 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns `false` immediately
720 * instead and otherwise no-ops (logging aside).
721 *
722 * ### Blob copying behavior; synchronicity/blockingness guarantees ###
723 * `*target_hndl` and the area described by `target_meta_blob` must both remain valid until `on_done_func()`
724 * executes. The method itself shall be non-blocking.
725 *
726 * The implementation shall, informally, strive to *not* copy the received blob (if any) into
727 * `target_meta_blob.data()...` except from the low-level transport mechanism. That is: it shall strive to not
728 * store the blob data in some internal buffer before ending up in `target_meta_blob.data()...`.
729 *
730 * ### Error semantics ###
731 * An #Error_code is generated and passed as the 1st arg to `on_done_func()`.
732 * A falsy one indicates success. A truthy one indicates failure; but in particular:
733 * - error::Code::S_OBJECT_SHUTDOWN_ABORTED_COMPLETION_HANDLER (destructor called, canceling all pending ops;
734 * spiritually identical to `boost::asio::error::operation_aborted`);
735 * - only if #S_META_BLOB_UNDERFLOW_ALLOWED is `true`:
736 * - error::Code::S_MESSAGE_SIZE_EXCEEDS_USER_STORAGE (opposing peer has sent a message with a meta-blob exceeding
737 * `target_meta_blob.size()` in length; in particular one can give an empty buffer if no meta-blob expected);
738 * - error::Code::S_RECEIVES_FINISHED_CANNOT_RECEIVE (peer gracefully closed pipe via `*end_sending()`);
739 * - error::Code::S_RECEIVER_IDLE_TIMEOUT (idle timeout: see idle_timer_run());
740 * - other arbitrary codes are possible.
741 * - No would-block-like error condition shall be emitted.
742 *
743 * A non-success on-done invocation means the incoming pipe is hosed. (It is recommended they cease to use the pipe
744 * and invoke the destructor to free resources.) If such a non-success code is emitted, the same one shall be emitted
745 * by all further async_receive_native_handle() calls on `*this`. Key exception:
746 * - error::Code::S_INVALID_ARGUMENT:
747 * - This refers to an invalid argument to *this* method invocation.
748 * - If and only if #S_META_BLOB_UNDERFLOW_ALLOWED is `false`: A length floor on the target blob is imposed.
749 * Hence if `target_meta_blob.size() < receive_meta_blob_max_size()` then `S_INVALID_ARGUMENT` is emitted.
750 * - This shall *not* hose the pipe. Subsequent async-receives may be attempted with reasonable hope of
751 * success.
752 * - At the impl's discretion, other conditions *may* lead to `S_INVALID_ARGUMENT`. If so they must
753 * be documented.
754 *
755 * ### Thread safety ###
756 * You may call this either from any thread including the unspecified thread running within another call's
757 * `on_done_func()`. It is *not* required to be safe to call concurrently with any Native_handle_receiver API,
758 * including this one and the destructor, invoked on `*this`.
759 *
760 * @tparam Task_err_sz
761 * A functor type with signature identical to `flow::async::Task_asio_err_sz`.
762 * @param target_hndl
763 * `*target_hndl` shall be set by the time `on_done_func()` is executed with a falsy code. See above.
764 * @param target_meta_blob
765 * `target_meta_blob.data()...` shall be written to by the time `on_done_func()` is executed with a falsy
766 * code, bytes numbering `N`, where `N` is passed to that callback. `N` shall not exceed
767 * `target_meta_blob.size()`. See above.
768 * @param on_done_func
769 * See above. This shall be invoked from an unspecified thread that is not the calling thread.
770 * @return `false` if `*this` is not in PEER (connected, transmitting) state;
771 * otherwise `true`.
772 */
773 template<typename Task_err_sz>
775 const util::Blob_mutable& target_meta_blob,
776 Task_err_sz&& on_done_func);
777
778 /**
779 * In PEER state: Irreversibly enables a conceptual idle timer whose potential side effect is, once at least
780 * the specified time has passed since the last received low-level traffic (or this call, whichever most
781 * recently occurred), to emit the pipe-hosing error error::Code::S_RECEIVER_IDLE_TIMEOUT. The implementation
782 * shall guarantee the observer idle timeout is at least the provided value but may exceed this value for
783 * internal reasons, as long as it's by a less than human-perceptible period (roughly speaking -- milliseconds,
784 * not seconds).
785 *
786 * If `*this` is not in PEER state (in particular if it is default-cted or moved-from), returns `false` immediately
787 * instead and otherwise no-ops (logging aside). If idle_timer_run() has already been called successfuly,
788 * subsequently it will return `false` and no-op (logging aside).
789 *
790 * ### Important: Relationship between idle_timer_run() and async_receive_native_handle() ###
791 * idle_timer_run() is optional: if you have not called it, then the following does not apply. If you *have* called
792 * it:
793 *
794 * It will work usefully if and only if subsequently an async_receive_native_handle() is oustanding at least
795 * once each `timeout`. Informally this means it's best to immediately issue async_receive_native_handle() --
796 * unless one is already oustanding -- and then as soon as that completes (sans error) issue at least one more;
797 * and so on. Why? What are we talking about? Simple: async_receive_native_handle() impl is *not* required
798 * to read any more data from the low-level transport than is sufficient to satisfy the current
799 * async_receive_native_handle() deficit. (If it were required to do so, it would've been required to store copies
800 * of incoming meta-blobs when there's no user-request deficit; we've consciously avoided unnecessary copying.)
801 * So while there's no async_receive_native_handle() outstanding, `*this` will be considered idle; and once
802 * that continues long enough for `timeout` to be exceeded, the idle timer will hose the in-pipe.
803 *
804 * So: If you plan to use idle_timer_run(), then you need to be ~always async-receiving. Otherwise you'll risk
805 * hitting idle-timeout, even as the other side diligently sends stuff (`auto_ping()` or otherwise).
806 *
807 * ### Error semantics ###
808 * If and only if the timeout does occur down the line, the aforementioned error will be emitted via
809 * async_receive_native_handle() (or similar) handler. It shall be treated as the reason to hose the pipe
810 * (assuming it was not hosed by something else earlier).
811 *
812 * ### Thread safety ###
813 * You may call this either from any thread including the unspecified thread running within another call's
814 * `on_done_func()`. It is *not* required to be safe to call concurrently with any API, including this one
815 * and the destructor, invoked on `*this`.
816 *
817 * ### Suggested use ###
818 * Informally: There are roughly two approaches to using idle_timer_run().
819 *
820 * Firstly it can be used to gauge the state of the opposing process; if no auto-pings are arriving regularly,
821 * then the opposing Native_handle_sender (or similar) must be zombified or overloaded. To use it in this
822 * capacity, typically one must use Native_handle_sender::auto_ping() (or similar) on the opposing side.
823 * Typically one would then leave `timeout` at its suggested default value.
824 *
825 * Secondly it can be used to more generally ensure some application algorithm on the opposing side (presumably
826 * cooperating with the algorithm on the local side) is sending messages with expected frequency.
827 * That is, one would not use `auto_ping()` but rather send their own messages in a way that makes sense for
828 * the applications' algorithm.
829 *
830 * @param timeout
831 * The idle timeout to observe.
832 * The optional default is chosen by the impl to most reasonably work with the opposing `auto_ping()`
833 * to detect a zombified/overloaded peer.
834 * @return `false` if `*this` is not in PEER (connected, transmitting) state, or if already called successfully
835 * in `PEER` state; otherwise `true`.
836 */
837 bool idle_timer_run(util::Fine_duration timeout = default_value);
838}; // class Native_handle_receiver
839
840} // namespace ipc::transport
A documentation-only concept defining the behavior of an object capable of reliably/in-order receivin...
Native_handle_receiver(Native_handle_receiver &&src)
Move-constructs from src; src becomes as-if default-cted (therefore in NULL state).
bool idle_timer_run(util::Fine_duration timeout=default_value)
In PEER state: Irreversibly enables a conceptual idle timer whose potential side effect is,...
Native_handle_receiver & operator=(const Native_handle_receiver &)=delete
Disallow copying.
static constexpr bool S_META_BLOB_UNDERFLOW_ALLOWED
If false then meta_blob.size() > receive_meta_blob_max_size() in PEER-state async_receive_native_hand...
Native_handle_receiver()
Default ctor: Creates a peer object in NULL (neither connected nor connecting) state.
Native_handle_receiver & operator=(Native_handle_receiver &&src)
Move-assigns from src; *this acts as if destructed; src becomes as-if default-cted (therefore in NULL...
static const Shared_name S_RESOURCE_TYPE_ID
Shared_name relative-folder fragment (no separators) identifying this resource type....
Native_handle_receiver(sync_io::Native_handle_receiver &&sync_io_core_in_peer_state_moved)
sync_io-core-adopting ctor: Creates a peer object in PEER state by subsuming a sync_io core in that s...
bool async_receive_native_handle(Native_handle *target_hndl, const util::Blob_mutable &target_meta_blob, Task_err_sz &&on_done_func)
In PEER state: Asynchronously awaits one discrete message – as sent by the opposing peer via Native_h...
~Native_handle_receiver()
Destroys this peer endpoint which will end the conceptual incoming-direction pipe (in PEER state,...
size_t receive_meta_blob_max_size() const
In PEER state: Returns min target_meta_blob.size() such that (1) async_receive_native_handle() shall ...
Native_handle_receiver(const Native_handle_receiver &)=delete
Disallow copying.
A documentation-only concept defining the behavior of an object capable of reliably/in-order sending ...
Native_handle_sender(Native_handle_sender &&src)
Move-constructs from src; src becomes as-if default-cted (therefore in NULL state).
bool async_end_sending(Task_err &&on_done_func)
Equivalent to send_native_handle() but sends a graceful-close message instead of the usual payload; t...
Native_handle_sender(const Native_handle_sender &)=delete
Disallow copying.
Native_handle_sender(sync_io::Native_handle_sender &&sync_io_core_in_peer_state_moved)
sync_io-core-adopting ctor: Creates a peer object in PEER state by subsuming a sync_io core in that s...
size_t send_meta_blob_max_size() const
In PEER state: Returns max meta_blob.size() such that send_native_handle() shall not fail due to too-...
Native_handle_sender & operator=(Native_handle_sender &&src)
Move-assigns from src; *this acts as if destructed; src becomes as-if default-cted (therefore in NULL...
static const Shared_name S_RESOURCE_TYPE_ID
Shared_name relative-folder fragment (no separators) identifying this resource type....
bool auto_ping(util::Fine_duration period=default_value)
In PEER state: Irreversibly enables periodic auto-pinging of opposing receiver with low-level message...
Native_handle_sender & operator=(const Native_handle_sender &)=delete
Disallow copying.
~Native_handle_sender()
Destroys this peer endpoint which will end the conceptual outgoing-direction pipe (in PEER state,...
bool end_sending()
Equivalent to async_end_sending(F) wherein F() does nothing.
Native_handle_sender()
Default ctor: Creates a peer object in NULL (neither connected nor connecting) state.
bool send_native_handle(Native_handle hndl_or_null, const util::Blob_const &meta_blob, Error_code *err_code=0)
In PEER state: Synchronously, non-blockingly sends one discrete message, reliably/in-order,...
A documentation-only concept defining the behavior of an object that is the sync_io-pattern counterpa...
A documentation-only concept defining the behavior of an object that is the sync_io-pattern counterpa...
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:140
flow::Fine_duration Fine_duration
Short-hand for Flow's Fine_duration.
Definition: util_fwd.hpp:117
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:134
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:298
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.