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