Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
sync_io_fwd.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include "ipc/util/util_fwd.hpp"
22#include <boost/shared_ptr.hpp>
23
24/**
25 * Contains common code, as well as important explanatory documentation in the following text, for the `sync_io`
26 * pattern used in ipc::transport and ipc::session to provide fine-tuned control over integrating
27 * asynchronous Flow-IPC activities into the user's event loop.
28 *
29 * @see The guided Manual chapter @ref async_loop covers these topics.
30 *
31 * ### What's all this, then? ###
32 * You may notice that for ~every class/class template `X` in this library that provides 1+ `X::async_*(..., F)`
33 * method(s), where `F()` is a completion handler for an async operation, there exists also -- in a sub-namespace
34 * named `sync_io` -- a similar-looking type `sync_io::X`.
35 *
36 * @note At times, generally in ipc::session and class template ipc::transport::struc::Channel specifically, async
37 * ops don't necessarily use `async_*` naming, but the point remains that: an operation occurs in some
38 * sense in the background, and then a *handler* function, given as an arg, is called when the operation is
39 * completed. Nevertheless for this discussion we will often take the case of, indeed, an operation
40 * named `X::async_*(..., F)`, with `F()` a one-off completion handler. It's the basic, most mainstream pattern.
41 *
42 * Long story short: `X` does its background work in its own thread(s) and invokes your handlers from there;
43 * `sync_io::X` does its work only in your thread(s), when you call into it, invoking your handlers synchronously
44 * from within those calls. So `sync_io::X` gives you control over what runs in what thread (and spares you
45 * cross-thread hand-off of results); the price is a somewhat larger API (some setup per object; and async-ops
46 * that may complete either synchronously or later). Which is preferable depends on your application; see below.
47 * For what it is worth, internally `X` is usually written in terms of `sync_io::X`; the latter is essentially
48 * the core logic, while the former adds the background thread(s) and does the waiting for you.
49 *
50 * @note For this reason comments sometimes refer to a `sync_io::X` *core*: where the basic `X`-ish capabilities and
51 * data live. The *async-I/O* `X` is then often built around a `sync_io::X` core. Because of this it is
52 * usually easy, and fast, to convert a `sync_io::X` into an `X` -- via a move-like ctor called
53 * `sync_io`-core *adopting ctor*. Additionally, ipc::transport::Channel template -- which bundles local peer
54 * objects of 1-2 IPC pipes -- can bundle *either* async-I/O peer objects *or* `sync_io` peer objects, and
55 * one can always convert the latter to the former by calling `x.async_io_obj()`.
56 *
57 * @note In cases where performance is not a real concern, such as for the assumed-rare
58 * ipc::session::Session_server::async_accept() operations, internally `sync_io::X` may actually be written
59 * in terms of `X` instead... but we digress. Either way it is a black box.
60 *
61 * Some examples of `X`es that have `sync_io::X` counterparts:
62 * - core-layer transport::Native_socket_stream
63 * (including its `async_receive_*()`, and the handler-less -- but nevertheless potentially
64 * asynchronous -- `send_*()`) and all other `Blob_sender`, `Blob_receiver`, `Native_handle_sender`,
65 * `Native_handle_receiver` concept impls including the bundling transport::Channel;
66 * - structured-layer ipc::transport::struc::Channel;
67 * - session::Client_session (with its channel-accept and error handlers) and session::Session_server (ditto, plus
68 * with its `async_accept()`);
69 * - all their session::shm counterparts.
70 *
71 * ### The async-I/O pattern ###
72 * Consider `X` -- take for example transport::Native_socket_stream -- and a particular async operation -- take,
73 * e.g., transport::Native_socket_stream::async_receive_blob().
74 *
75 * When `x.async_receive_blob(..., F)` is invoked, `F()` is the user-specified completion handler, while ...
76 * specifies details about the operation, in this case the target buffer where to write data. It works as follows:
77 * `x` attempts to perform the operation (in this case receive a single in-message as soon as it becomes available
78 * which may or may not be instant); and once it has succeeded, it invokes `F(...)` (where ... indicates
79 * results, usually at least an `Error_code`) from *an unspecified thread* that is not the user's calling thread
80 * (call it thread U, where `x.async_*()` was called). Even if the op completes immediately, `x.async_*()` will
81 * never invoke `F()` synchronously; always from the *unspecified thread*.
82 *
83 * That's great, but what does really happen? Answer: `x`, usually at construction, invisibly, starts a separate
84 * thread (technically it could be co-using a thread with other objects; but in reality as of this writing each
85 * object really starts a thread). An async operation might complete synchronously (perhaps a message is available
86 * in a kernel receive buffer and is therefore immediately, internally, received inside `x.async_receive_blob()`
87 * body); or it might occur in the background and involve (internally) async waiting of native-handle readability --
88 * possibly even more threads might start (internally) to get things to work. *Either* way, there is that thread --
89 * call it thread W -- where *at least* the completion handler `F()` will be called.
90 *
91 * (If `x` is destroyed before this has a chance to happen, the `x`
92 * destructor shall -- last-thing -- invoke `F()`, passing it the special
93 * operation-aborted `Error_code`. That is the case for one-off async-ops like that one. There are also variations
94 * such as the completion handlers of transport::struc::Channel, but the key point -- that work happens in
95 * the background, in the object-created own thread W, and user-supplied handlers are run from thread W -- remains
96 * the same. Another variation is async-ops that don't require a completion handler; for example
97 * transport::Native_socket_stream::send_blob() may perform work in the background upon encountering would-block
98 * conditions internally -- and this again occurs in thread W -- but there is no completion handler to invoke.)
99 *
100 * What is the user supposed to do with an async-op like this? In practice we tend to think of this in terms
101 * of 2 basic possibilities for how the user's own event loop might be organized.
102 * - Proactor pattern: This is what boost.asio uses and what flow.async facilitates further. We won't describe
103 * it here in detail, but integrating such a loop with this async-I/O pattern in Flow-IPC is quite simple:
104 * -# Call `x.async_*(..., F)` as explained above.
105 * -# As `F()` supply a short wrapper that will place the true handling of the event onto the same event loop --
106 * thread U (though multiple such threads might be in use alternatively) -- where you invoked `x.async_*(F)`.
107 * For example:
108 *
109 * ~~~
110 * ...
111 * m_my_asio_loop.post([this]() { start_async_op(); });
112 * ...
113 * void start_async_op()
114 * {
115 * // We are in thread U.
116 * x.async_receive_blob(..., [this](const Error_code& err_code, size_t sz)
117 * {
118 * // We are in "unspecified thread" W.
119 * m_my_asio_loop.post([this, err_code, sz]() { on_async_op_done(err_code, sz); });
120 * });
121 * }
122 * void on_async_op_done(const Error_code& err_code, size_t sz)
123 * {
124 * // We are in thread U.
125 * // ...handle it....
126 * }
127 * ~~~
128 *
129 * Alternatively:
130 * - Reactor pattern: Usually built on top of an OS-supplied blocking polling method of some kind -- in POSIX
131 * nowadays usually at least `poll()`, in Linux possibly using the more advanced `epoll_*()` -- which centers
132 * on an "FD-set" (native handle set), where one describes `Native_handle`s (FDs) and the events one awaits
133 * (readable, writable) for each; and in each event loop iteration one runs a poll-wait operation
134 * (like `poll()` or `epoll_wait()`). This blocks until 1 or more events-of-interest are active; then wakes up
135 * and reports which ones they were. User code then synchronously invokes handling for each event of interest;
136 * such handling might modify the events-of-interest set, etc., until all such work is done, and the
137 * next poll-wait op executes. Thus in the reactor pattern methods like `on_async_op_done()` are invoked
138 * in a flow-control setup inverted versus the proactor pattern; but ultimately they're both doing the same thing.
139 * To integrate such a loop with this async-I/O pattern in Flow-IPC, a little extra work is required:
140 * - Sometimes event-loop libraries provide this as a built-in feature: a *task queue*. So a facility similar to
141 * `post()` above is supplied. Hence the code above would be adapted to a reactor-pattern loop and end up
142 * fairly similar: In `F()` do some equivalent to the `post()` in the snippet above. Internally it'll set up
143 * some "interrupter" handle in the central `[e]poll*()` handle-set and cause -- from thread W -- for thread U's
144 * poll-wait to wake up. Otherwise:
145 * - This task-queue facility can be written with relatively little difficulty. Essentially it involves
146 * a simple IPC mechanism, perhaps an anonymous pipe, through which thread-W-invoked `F()`s can
147 * inform the thread-U poll-wait that it must wake up and handle events, among any others that the poll-wait
148 * covers.
149 *
150 * So that's the async-I/O pattern in Flow-IPC. It cleanly separates Flow-IPC's internal needs from the rest of
151 * the application's: Flow-IPC needs to do background work? It takes care of its own needs: it starts and ends
152 * threads without your participation. Flow-IPC's cycles are then mostly spent in separate threads, which can
153 * shorten your loop's single iteration and automatically makes use of multiple hardware cores -- at the cost of
154 * context switching between those threads and yours.
155 *
156 * ### Rationale for the `sync_io` pattern ###
157 * So why might the pattern described above be insufficient? A detailed study of this is outside our scope
158 * here; but basically it is a matter of control. The way it starts threads, in a way that cannot be specified
159 * by you (the user), and switches between them may be fine (or even helpful) for many applications; but some
160 * applications want complete control of any such thing. For instance suppose I'd like to start with doing all
161 * that background work of `Native_socket_stream::async_receive_blob()` directly in thread U. It should be possible,
162 * right? Whatever events it waits on -- in reality, internally, principally it waits for readability of a Unix
163 * domain socket -- I could just wait-on in my own thread-U `epoll_wait()`. When an active event is detected, I
164 * could do the resulting non-blocking-reads -- that normally would be done in the background thread W -- directly
165 * after the poll-wait.
166 *
167 * Maybe that would be good, reducing context-switching overhead. Or maybe it wouldn't be good, as a big fat
168 * loop iteration could cause latency in serving the next batch of work. If so, and I *did* want
169 * to do some of the work in some other thread for parallelization, maybe I want to share that other thread with
170 * some other processing. Or... or.... Point is: perhaps I want to explicitly structure what threads do what, whether
171 * or not I want multi-threaded processing.
172 *
173 * If that is the case, then the `sync_io` pattern will serve that need. In this pattern, for example
174 * in transport::sync_io::Native_socket_stream, you'll notice completion handlers are still used as part of the API.
175 * However, they are *never* invoked in the background: *you* call into a `sync_io::X` API, and it might
176 * *synchronously only* and at very specific points invoke a completion handler `F()` that you supplied it earlier.
177 *
178 * We'll get into details below, but to summarize how this is integrated with the 2 above-covered user event loop
179 * patterns:
180 * - Proactor pattern: The `sync_io` pattern in Flow-IPC has first-class support for a boost.asio event loop
181 * on the user's part. So -- basically -- if you've got a `boost::asio::io_context E` (`flow::util::Task_engine E`)
182 * `run()`ning over 1+ threads U, then `sync_io::X` shall give you boost.asio `descriptor` objects associated
183 * with `E` and ask *you* -- yourself, albeit on its behalf -- to perform `.async_wait(write, G)` or
184 * `.async_wait(read, G)` on those `descriptor`s; and inform the `sync_io::X` when they've completed, inside `G()`.
185 * As a result, `sync_io::X` internals might inform you of the completion of an `async_...()` op earlier requested
186 * by you.
187 * - Reactor pattern: If your loop isn't boost.asio-based, then `sync_io::X` will similarly ask you to perform
188 * async-waits on this or that handle for read or write or both, just over a slightly different API -- one
189 * conducive to `poll()`, `epoll_ctl()`/`epoll_wait()`, and the like. In short it'll tell you what FD and what
190 * event it wants you to wait-on (and again give you function `G()` to call when it is indeed active).
191 *
192 * Thus, *you* control what happens in what thread -- and everything can happen in *your* single thread, if you
193 * so desire. *You* can create the other threads, arrange necessary synchronization -- including of access to
194 * the `sync_io::X` in question -- as opposed to rely on whatever we've internally designed inside non-`sync_io` `X`.
195 * *You* control when a `sync_io::X` does something. In particular, it is only in a couple of
196 * specific `sync_io::X::*` APIs that a completion handler you gave it can actually be called. *If* it is called,
197 * it is always called synchronously right then and there, not from some unknown background thread.
198 *
199 * ### So `sync_io`-pattern-implementing APIs will never start threads? ###
200 * Well, they might. In some cases it's an internal need that cannot be avoided. However, when *both* (1) it can be
201 * avoided, *and* (2) performance could possibly be affected, then correct: Flow-IPC will avoid starting a thread and
202 * performing context-switching. If it's immaterial for performance in practice, then it absolutely reserves the
203 * right to make background threads, whether for ease of internal implementation or some other reason. And,
204 * of course, if there's some blocking API that must be used internally -- and there is simply no choice but to
205 * use that API -- then a thread will need to be started behind the scenes. We can't very well block your thread U, so
206 * at that point we do what we must.
207 *
208 * However, even in *that* case, a `sync_io` *API* is still supplied. This may be helpful to more easily integrate
209 * with your reactor-pattern event loop. (However, if you have a proactor like boost.asio as your event loop, then
210 * in our view it is unlikely to be helpful in that sense. At that point you might as well use the async-I/O
211 * alternative API -- unless, again, there is some performance benefit to maintaining greater control of what
212 * part of Flow-IPC executes when from what thread.)
213 *
214 * ### Using the `sync_io` pattern: design rationale ###
215 * Though it will look different and perhaps complex, it is actually at its core similar to other third-party
216 * APIs that require the user to perform async-waits on their behalf. The most well known example of such an API
217 * is perhaps OpenSSL. Take `SSL_read()` -- quite similar in spirit to `sync_io`-pattern `x.async_receive_blob()`.
218 * When invoked, barring connection-hosing errors, one of 2 things will happen:
219 * - It will succeed and read 1 or more bytes and return this fact ("no error, I have read N bytes").
220 * - It will fail due to would-block, because it either needs the underlying stream socket to be
221 * readable, or to be writable. (The latter -- needing writability -- may seem strange for `SSL_read()`,
222 * but it's normal: perhaps the connection is in the middle of a cert negotiation, and at this stage needs
223 * to write something *out* first; and the connection happens to be in such a state as to not be able to write
224 * bytes to the kernel send buffer at that moment.)
225 * So it will return either:
226 * - `SSL_ERROR_WANT_READ` (meaning, "the underlying stream-socket handle (FD) needs to be readable -- call me
227 * again after you've *async-waited* successfully on this *event-of-interest*, and I will try again"); or
228 * - `SSL_ERROR_WANT_WRITE` (same but needs writability instead).
229 *
230 * Your application would then internally register interest in FD so-and-so to be readable or writable. Perhaps
231 * some `SSL_write()` would be interested in another such event simultaneously too. So then the next time the
232 * event loop came up to the next `poll()` or `epoll_wait()`, you'd indeed wait on these registered events.
233 * If the `SSL_read()`-related event-of-interest was indeed returned as active, your program would know that
234 * fact, based on its own data structures, and know to try `SSL_read()` again. That time `SSL_read()` might
235 * succeed; or it might require writability again, or readability this time, and would return
236 * `SSL_ERROR_WANT_*` again. Eventually it'd get what it needs and return success.
237 *
238 * `sync_io` pattern in Flow-IPC is not much different. It is arguably more complex to use, but there are good
239 * reasons for it. Namely there are some differences as to our requirements compared to OpenSSL's. To wit:
240 * - For a given operation -- whether it's `Native_socket_stream::async_receive_blob()` or the even more
241 * internally complex transport::struc::Channel::expect_msg() -- there will usually be more than 1
242 * event of interest at a time, in fact spread out over more than 1 handle (FD) at a time at that. Namely
243 * in addition to readability/writability of the underlying low-level transport, there are timer(s)
244 * (such as the idle timer -- `idle_timer_run()`); and `struc::Channel` can be configured to have
245 * 2 in-pipes and thus will be async-waiting on 2 FDs' read events.
246 * - Hence we need to be able to express the desired async-waits to the user in a more flexible way than
247 * a simple "can't do it, tell me when my socket is Xable and call me again." We have to communicate
248 * an entire set-of-events-of-interest as well as changes in it over time.
249 * - OpenSSL is targeted at old-school event loops -- ~all written in the reactor-pattern style.
250 * Flow-IPC is meant to be usable by modern proactor-pattern applications *and* old-school reactor-pattern
251 * ones as well. It would be unfortunate, in particular, if a given `X` is written in the modern handler-based way,
252 * while `sync_io::X` counterpart is just entirely different. In fact, suppose `sync_io::X` is available;
253 * *our* `X` internally "wants" to be written around a boost.asio loop *and* reuse `sync_io::X` internally
254 * as well. So in other words, both aesthetically and practically, an OpenSSL-style old-school API would be
255 * a pain to use in a boost.asio-based application (our own async-I/O-pattern classes being a good test case).
256 *
257 * Therefore the `sync_io` pattern is strongly inspired by the boost.asio proactor pattern API. As a corollary it
258 * provides special support for the case when the user's event loop *is* boost.asio-based. If your event loop
259 * is old-school, you will lose nothing however -- just don't use that feature.
260 *
261 * ### Using the `sync_io` pattern: how-to ###
262 * Take `x.async_receive_blob(..., F)`. If `x` is an `X`, we've already described it. Now let `x` be a `sync_io::X`.
263 * The good news is initiating the same operation uses *almost* the exact same signature. It takes the same arguments
264 * including a completion handler `F()` with the exact same signature itself. There is however one difference:
265 * if `F()` takes 1+ args (1st one usually `const Error_code& err_code`; followed at times by something like
266 * `size_t sz` indicating how many bytes were transferred) -- then the `sync_io` form of the `async_...()` method
267 * shall take `sync_`-prefixed *out-arg* counterparts to those args directly as well. Thus specifically:
268 * - If async-I/O sig is `x.async_receive_blob(..., F)`,
269 * where `F()` is of form `void (const Error_code& err_code, size_t sz)`, then:
270 * - `sync_io` sig is: `x.async_receive_blob(..., Error_code* sync_err_code, size_t* sync_sz, F)`,
271 * where `F()` is of the same form as before. You shall provide `&sync_err_code, &sync_sz`, and your local
272 * variables shall be set to certain values upon return.
273 * - You may also leave `sync_err_code` (if applicable) null. In that case standard Flow error-reporting
274 * semantics are active: if (and only if) `*sync_err_code` *would* be set to truthy (non-success) value,
275 * but `sync_err_code` is a null pointer, then a `flow::error::Runtime_error` is thrown with
276 * the would be `*sync_err_code` stored inside the exception object (and a message in its `.what()`).
277 *
278 * So let's say you call `sync_io` `x.async_receive_blob()`, providing it `&sync_err_code, &sz` args, otherwise
279 * using the same arg values as with async-I/O. (For simplicity of discussion let's assume you did not pass
280 * null pointer for the sync-err-code arg.) Then: `F()` will no longer
281 * execute from some unspecified thread at some unknown future time. Instead there are 2 possibilities.
282 * - If `x.async_receive_blob()` was able to receive a message (more generally -- complete the operation)
283 * synchronously (immediately), then:
284 * - The method *itself* shall emit (via `sync_err_code` out-arg) either *no* error (falsy value)
285 * or an error (truthy value) that is specifically *not* ipc::transport::error::Code::S_SYNC_IO_WOULD_BLOCK.
286 * - This is quite analogous to `SSL_read()` succeeding immediately, without `SSL_ERROR_WANT_*`.
287 * - The method *itself* shall synchronously emit other `sync_` out-arg values indicating the result
288 * of the operation. In this case that is `sync_sz == 0` on error, or `sync_sz > 0` indicating how many
289 * bytes long the successfully, *synchronously* received blob is.
290 * - Again: This is quite analogous to `SSL_read()` succeeding immediately, without `SSL_ERROR_WANT_*`.
291 * - The async completion handler, `F`, shall be utterly and completely ignored. It will *not* be saved.
292 * It will *not* be called. The operation already finished: you get to deal with it right then and there
293 * like a normal synchronous human being.
294 * - Conversely, if `x.async_receive_blob()` can only complete the op after some events-of-interest (to `x`)
295 * become active (we'll discuss how this works in a moment) -- then:
296 * - The method *itself* shall emit (via `sync_err_code` out-arg) the specific code
297 * ipc::transport::error::Code::S_SYNC_IO_WOULD_BLOCK.
298 * - This is analogous to `SSL_read()` returning `SSL_ERROR_WANT_*`.
299 * - It shall save `F` internally inside `x`.
300 * - Later, when your loop *does* detect an event-of-interest (to `x`) is active, it shall explicitly
301 * *call into another `x` API method* -- we call it `(*on_active_ev_func)()` -- and *that* method may
302 * *synchronously* invoke the memorized completion handler `F()`. In the case of `x.async_receive_blob()`
303 * this `F()` call will be just as you're used-to with async-I/O pattern:
304 * `err_code` truthy on error, `sz == 0`; or `err_code` falsy on success, `sz > 0` indicating size of
305 * received blob.
306 * - Since `x.async_receive_blob()` has one-off completion handler semantics, at this point it'll forget `F`.
307 * - However, with `sync_io` pattern, `x` shall *never* issue an operation-aborted call to `F()` on
308 * `x` destruction or anything like that. That's an async-I/O thing.
309 *
310 * The "later, when your loop" step is analogous to: your loop awaiting an event as asked by the would-block
311 * `SSL_read()`, then once active calling `SSL_read()` again; and the latter, this time, returning success.
312 * Note, however, that you do *not* re-call `x.async_receive_blob()` on the desired active event. Conceptually
313 * you're doing the same thing -- you're saying, "the event you wanted is ready; if you can get what I wanted
314 * then do it now" -- but you're doing it by using a separate API.
315 *
316 * That leaves the other major piece of the API: how to in fact be informed of a desired event-of-interest
317 * and subsequently indicate that event-of-interest is indeed active. In terms of the API, this procedure is
318 * decoupled from the actual `x.async_receive_blob()` API. Moreover it is not expressed as some kind of big
319 * set-of-handles-and-events-in-which-`x`-has-interest-at-a-given-time either. Instead, conceptually, it is
320 * expressed similarly to boost.asio: `x` says to itself: I want to do
321 * `handle_so_and_so.async_wait(readable, F)`; or: I want to do
322 * `handle_so_and_so.async_wait(writable, F)`. But since `handle_so_and_so` is not a boost.asio I/O object, it has
323 * to express the same thing to the user of `x`. How? Answer: sync_io::Event_wait_func. Its doc header explains
324 * the details. We summarize for our chosen example of `x.async_receive_blob()`:
325 * - Just after constructing the `x`, set up the async-wait function: `x.start_receive_blob_ops(E)`,
326 * where `E` is a function with signature a-la `Event_wait_func`.
327 * - (Note that `receive_blob` fragment of the method name. This is replaced with other fragments for other
328 * operations. In some cases an object -- ipc::transport::Native_socket_stream being a prime example -- supports
329 * 2+ sets of operations. For example `Native_socket_stream::start_send_blob_ops()` is for the *independent*
330 * outgoing-direction operations that can occur *concurrently to* incoming-direction `start_receive_blob_ops()`
331 * ops.)
332 * No need to worry about that for now though; we continue with our `receive_blob` example use case.
333 * - *If* (and only if!) your event loop is boost.asio-based, then precede this call with one that supplies
334 * the boost.asio `Task_engine` (or `strand` or...) where you do your async work: `.replace_event_wait_handles()`.
335 *
336 * ~~~
337 * using ipc::util::sync_io::Task_ptr;
338 * using ipc::util::sync_io::Asio_waitable_native_handle;
339 *
340 * x.replace_event_wait_handles
341 * ([this]() -> auto { return Asio_waitable_native_handle{m_my_task_engine}; });
342 * x.start_receive_blob_ops([this](Asio_waitable_native_handle* hndl_of_interest,
343 * bool ev_of_interest_snd_else_rcv,
344 * Task_ptr&& on_active_ev_func)
345 * {
346 * my_rcv_blob_sync_io_ev_wait(hndl_of_interest, ev_of_interest_snd_else_rcv, std::move(on_active_ev_func));
347 * });
348 * ~~~
349 *
350 * We are now ready for the last sub-piece: named `my_rcv_blob_sync_io_ev_wait()` in that snippet. This key piece
351 * responds to the instruction: async-wait for the specified event for me please, and let me know when that event
352 * is active by calling `(*on_active_ev_func)()` at that time. The details of how exactly to do this can be
353 * found in the sync_io::Event_wait_func doc header -- including tips on integrating with `poll()` and `epoll_*()`.
354 * Here we will assume you have a boost.asio loop, continuing from the above snippet. The simplest possible
355 * continuation:
356 *
357 * ~~~
358 * void my_rcv_blob_sync_io_ev_wait(Asio_waitable_native_handle* hndl_of_interest,
359 * bool ev_of_interest_snd_else_rcv,
360 * Task_ptr&& on_active_ev_func)
361 * {
362 * // sync_io wants us to async-wait. Oblige.
363 * hndl_of_interest->async_wait(ev_of_interest_snd_else_rcv
364 * ? Asio_waitable_native_handle::Base::wait_write
365 * : Asio_waitable_native_handle::Base::wait_read,
366 * [this, on_active_ev_func = std::move(on_active_ev_func)]
367 * (const Error_code& err_code)
368 * {
369 * if (err_code == boost::asio::error::operation_aborted) { return; } // Shutting down? Get out.
370 *
371 * // Event is active. sync_io wants us to inform it of this. Oblige.
372 * (*on_active_ev_func)();
373 * // ^-- THAT POTENTIALLY INVOKED COMPLETION HANDLER F() WE PASS IN TO x.async_receive_blob(F)!!!
374 * });
375 * }
376 * ~~~
377 *
378 * That's it. Note well: `(*on_active_ev_func)()` is (a) informing `sync_io`-pattern-implementing `x` of the
379 * event it (`x`) wanted; and (b) possibly getting the result *you* wanted in the original `async_receive_*()` call.
380 * Thus it is the equivalent of the 2nd `SSL_read()` call, after satisfying the `SSL_ERROR_WANT_READ` or
381 * `SSL_ERROR_WANT_WRITE` result. The only difference, really, is the mechanic of getting the result is through
382 * a *synchronous* call to your completion handler.
383 *
384 * There is, however, an extremely important subtlety to consider additionally. The key point:
385 * - `(*on_active_ev_func)()` should be thought of as an API -- a method! -- of `x`, the
386 * `sync_io`-pattern-implementing Flow-IPC object...
387 * - ...right alongside (in our use case here) `.async_receive_blob()`.
388 *
389 * That means it is *not* thread-safe to call `(*on_active_ev_func)()` concurrently with
390 * `x.async_receive_blob()`. What does this mean in practice? Answer:
391 * - If you execute all these things in a single-threaded event loop -- nothing. You need not worry about
392 * synchronization.
393 * - If you perform some of your work on `x` (perhaps the `.async_*()` calls) in one thread but
394 * the async-wait handling -- and the potentially resulting *synchronous* invocation of your own completion
395 * handlers -- in another...
396 * - ...then you, at a minimum, need a mutex-lock (or use of a strand, or...) around the call sites into
397 * `x.method(...)`, for all related `method`s of `x`...
398 * - ...*including* the call to `(*on_active_ev_func)()`.
399 *
400 * Chances are, though, that if you're operating in multiple threads, then anyway you'll need to protect *your own*
401 * data structures against concurrent writing. Remember: `(*on_active_ev_func)()` may well invoke
402 * *your own completion handler* from the original `x.async_receive_blob(..., F)` call. That code (*your* code)
403 * is more than likely to react to the received payload in some way, and that might be touching data structures
404 * accessed from 2+ threads. It would therefore make sense that the relevant mutex be already locked. In this
405 * example we presuppose your application might invoke async-receives from a thread U while placing handlers
406 * onto a thread W:
407 *
408 * ~~~
409 * using flow::util::Lock_guard;
410 *
411 * ...
412 * // In your thread U:
413 *
414 * Lock_guard<decltype(m_my_rcv_mutex)> lock{m_my_rcv_mutex}; // <-- ATTN! Protects x receive ops at least.
415 * ...
416 * ipc::Error_code sync_err_code;
417 * size_t sync_sz;
418 * x.async_receive_blob(m_target_msg,
419 * &sync_err_code, &sync_sz,
420 * [this](const ipc::Error_code& err_code, size_t sz) { on_msg_in(err_code, sz); });
421 * if (sync_err_code != ipc::transport::error::Code::S_SYNC_IO_WOULD_BLOCK)
422 * {
423 * // ...Handle contents of m_target_msg, or sync_err_code indicating error -- perhaps ~like on_msg_in()!
424 * // Note m_my_rcv_mutex is locked... as it would be in on_msg_in() (which won't run in this case)!
425 * }
426 * // else { on_msg_in() will run later. x.async_receive_blob() probably invoked my_rcv_blob_sync_io_ev_wait(). }
427 * ...
428 *
429 * void my_rcv_blob_sync_io_ev_wait(Asio_waitable_native_handle* hndl_of_interest,
430 * bool ev_of_interest_snd_else_rcv,
431 * Task_ptr&& on_active_ev_func)
432 * {
433 * // In your thread U (via x.async_receive_blob()) or W (via (*on_active_ev_func)() -- see below).
434 * // Either way the caller has locked m_my_rcv_mutex.
435 *
436 * // sync_io wants us to async-wait. Oblige. (Do not lock m_my_rcv_mutex! It is already locked.)
437 * hndl_of_interest->async_wait(ev_of_interest_snd_else_rcv
438 * ? Asio_waitable_native_handle::Base::wait_write
439 * : Asio_waitable_native_handle::Base::wait_read,
440 * [this, on_active_ev_func = std::move(on_active_ev_func)]
441 * (const Error_code& err_code)
442 * {
443 * if (err_code == boost::asio::error::operation_aborted) { return; } // Shutting down? Get out.
444 *
445 * // In your thread W:
446 *
447 * // Event is active. sync_io wants us to inform it of this. Oblige.
448 *
449 * flow::util::Lock_guard<decltype(m_my_rcv_mutex)> lock{m_my_rcv_mutex}; // <-- ATTN!
450 *
451 * (*on_active_ev_func)();
452 * // ^-- THAT POTENTIALLY INVOKED on_msg_in()!!!
453 * });
454 * }
455 *
456 * void on_msg_in(const Error_code& err_code, size_t sz)
457 * {
458 * // In your thread W:
459 * // m_my_rcv_mutex is locked!
460 *
461 * ... // Handle contents of m_target_msg, or err_code indicating error.
462 * }
463 * ~~~
464 *
465 * Just remember: *You* choose when `x` does anything that might touch your data, or itself. This happens
466 * in exactly three possible places and always synchronously:
467 * - The `x.async_receive_blob()` initiating call itself (and ones like it, all having async-I/O almost-identical-sig
468 * counterparts in `X::` for any given `sync_io::X::`).
469 * - OpenSSL analogy: initial `SSL_read()`.
470 * - Your code reacting to non-would-block return from `x.async_receive_blob()`, right after it (and ones like it).
471 * - OpenSSL analogy: reacting to initial `SSL_read()` succeeding right away.
472 * - `(*on_active_ev_func)()`.
473 * - OpenSSL analogy: subsequent `SSL_read()`, once you've detected its `SSL_ERROR_WANT_*` has been satisfied.
474 *
475 * ### Is there always a completion handler? ###
476 * Answer: No. An async op might not have a completion handler, but it is still an async op and may need to
477 * ask you to async-wait for some handle to be readable and/or writable to work properly. The most prominent
478 * case of this is sending items as exemplified by ipc::transport::sync_io::Native_socket_stream::send_blob().
479 * How to handle it? Well, it's the same as any `async_*()` op (which shall always take a completion handler) --
480 * but simpler. Simply put: Such an op shall be initiated by a method that takes no completion handler arg, so
481 * you simply don't provide one. (By convention it will also lack an `async_` prefix in the method name.)
482 * Beyond that, everything is the same:
483 * - It may (or may not) initiate an async-wait by calling the `Event_wait_func` you supplied via
484 * `start_*ops()`.
485 * - Your `Event_wait_func` should indeed -- on successful async-wait -- invoke `(*on_active_ev_func)()`.
486 * Internally that may continue, or complete, whatever async processing `x` needs to do.
487 * - The difference: it won't invoke some completion handler of yours... as you didn't (and couldn't) provide one.
488 *
489 * ### Multiple op-types in a given `sync_io`-pattern-implementing object ###
490 * Consider, first, ipc::session::sync_io::Session_server_adapter. It has only one operation, really:
491 * session::sync_io::Session_server_adapter::async_accept(). To set it up, you'll do (as explained above by another
492 * example use case): `x.start_ops()`, possibly preceded by `x.replace_event_wait_handles()` (but that guy is
493 * beside the point).
494 *
495 * Easy enough. The `x` can't do any other "type of thing." Same is true of, say,
496 * ipc::transport::sync_io::Blob_stream_mq_sender: it can only `x.send_blob()` (and `x.auto_ping()`,
497 * `x.end_sending()`, `x.async_end_sending()` -- all of which deal with sending messages *out*); and needs only
498 * `x.start_send_blob_ops()` for setup.
499 *
500 * What if an object can do multiple things though? `Native_socket_stream` (operating as a `Blob_sender` and
501 * `Blob_receiver`) can do at least 2: it can
502 * - send (`.send_blob()`, `.end_sending()`, `.async_end_sending()`, `.auto_ping()`),
503 * - and receive (`.async_receive_blob()`, `.idle_timer_run()`).
504 *
505 * These are 2 distinct *op-types*, and each one has its own independent API started via
506 * `.start_send_blob_ops()` and `.start_receive_blob_ops()` respectively. (If it were networked -- this might
507 * be in the works -- it would probably gain a 3rd op-type via `.start_connect_ops()`.)
508 *
509 * Not that interesting in and of itself; but what about concurrency? Answer:
510 *
511 * Things only get interesting once 2+ op-types (in practice, as of this writing, it's
512 * 2 at most in all known use cases) can occur concurrently. Namely that's
513 * sending (started via `.start_send_blob_ops()` in this case) and receiving (`.start_receive_blob_ops()`).
514 * How does it work? Answer: Formally speaking, it's described in the appropriate class's doc header; in this case
515 * ipc::transport::sync_io::Native_socket_stream. Informally the intuition behind it is as follows:
516 * - In a full-duplex object (like this one), you can indeed send and receive at the same time.
517 * That is, e.g., you can interleave an async-receive-blob op and its eventual completion with
518 * invoking something send-related and its eventual completion (if applicable).
519 * - Moreover you can use the API of op-type "receive" *actually concurrently* with the API of op-type "send."
520 * It *is* thread-safe, even though you're ostensibly mutating the same object `x`.
521 * This is analogous to being able to concurrently `read(fd, ...)` and `write(fd, ...)` on the same OS handle
522 * `fd` for many socket types including TCP and Unix-domain-socket. This may, all in all, lead to significant
523 * perf savings in your application, if you have multiple threads available to handle sending and receiving.
524 * If you *do* need a mutex, you can have 1 mutex for send ops and 1 mutex for receive ops (assuming of course
525 * your algorithm doesn't involve the 2 directions' algorithms concurrently mutating the same outside-`x` data).
526 *
527 * Do keep in mind: This applies to `sync_io::X` only -- not its async-I/O counterpart `X`. E.g., you cannot do
528 * `x.async_receive_blob()` and `x.send_blob()` concurrently, in the latter case, but you can too do so in the former.
529 * With plain `X` you'd need to mutex-protect (or use strands or ...) `x`. However you can of course still have 2+
530 * async operations outstanding simultaneously (e.g., initiating a send while an `.async_receive_*(..., F)` has not yet
531 * finished as signified by `F()`): you just cannot literally call mutating parts of `x` API concurrently.
532 *
533 * @see ipc::util::sync_io::Event_wait_func -- details about hooking up your event loop to a
534 * `sync_io`-pattern-implementing Flow-IPC object.
535 * @see ipc::util::sync_io::Asio_waitable_native_handle -- take a look particularly if your event loop is built on
536 * boost.asio.
537 * @see ipc::transport::Native_socket_stream internal source code: This exemplifies a fairly advanced
538 * "eat-our-own-dog-food" usage of a `sync_io`-pattern-implementing API
539 * (ipc::transport::sync_io::Native_socket_stream in this case) in a multi-threaded setting
540 * (user's thread U and internal worker thread W in this case). In this case the event loop is a boost.asio
541 * one.
542 *
543 * @todo Write an example of `sync_io`-pattern use with an old-school reactor-pattern event loop, using
544 * `poll()` and/or `epoll_*()`.
545 *
546 * @internal
547 *
548 * @see ipc::util::sync_io::Timer_event_emitter -- how to hook up a boost.asio `Timer` inside
549 * a `sync_io`-pattern-implementing Flow-IPC object.
550 */
551namespace ipc::util::sync_io
552{
553
554// Types.
555
556// Find doc headers near the bodies of these compound types.
557
558class Asio_waitable_native_handle;
559
560/**
561 * Short-hand for ref-counted pointer to a `Function<>` that takes no arguments and returns nothing;
562 * in particular used for `on_active_ev_func` arg of sync_io::Event_wait_func.
563 *
564 * ### Rationale ###
565 * This is defined here because of its central role in sync_io::Event_wait_func (see that doc header).
566 *
567 * Why wrap it in a smart pointer at all as opposed to passing around `Function<>`s as objects (particularly
568 * as arg to #Event_wait_func)? Answer: Performance. Our `sync_io` pattern is meant, in part, for the
569 * perf-conscious user who wants to avoid involuntary (from the user's point of view) thread creation and context
570 * switching; copying or moving polymorphic functors, including all their captures, is an unnecessary expense.
571 *
572 * In that case why `shared_ptr`, not `unique_ptr`, given that it adds potential ref-counting behind the scenes?
573 * Answer: `unique_ptr` would have been nice; however it is likely the user (and/or internal Flow-IPC code)
574 * will want to lambda-capture the wrapped `Task`; and capturing a movable-but-not-copyable type like `unique_ptr`
575 * makes the lambda itself move-only, so it cannot be stored in a `Function<>` (a/k/a `std::function`, which requires
576 * a copyable callable), even if one never copies it. Storing such lambdas in `Function<>`s (e.g., as a `Task`) is
577 * routine. One would need to upgrade to `shared_ptr` to capture, and that is annoying.
578 *
579 * @note That said it is recommended that one `std::move()` any #Task_ptr whenever possible, such as when
580 * capturing it in a lambda (e.g., `[task_ptr = std::move(task_ptr)]`). This advice applies generally
581 * to all `shared_ptr` captures ("whenever possible" being important), but this is just a reminder.
582 */
583using Task_ptr = boost::shared_ptr<Task>;
584
585/**
586 * In `sync_io` pattern, concrete type storing user-supplied function invoked by pattern-implementing
587 * ipc::transport and ipc::session object to indicate interest in an I/O status event (writable, readable)
588 * for a particular Native_handle.
589 *
590 * @see ipc::util::sync_io doc header first. It explains the pattern in detail including example code for
591 * setting up an `Event_wait_func`.
592 *
593 * Use in `sync_io` pattern
594 * ------------------------
595 * Suppose `T` is an ipc::transport or ipc::session object type, always in a `"sync_io"` sub-namespace, that
596 * operates according to the `sync_io` pattern. (For example, `T` might be transport::sync_io::Native_socket_stream.)
597 * Then `T::start_*ops(Event_wait_func&&)`, and/or a compatible template, must be invoked to begin unidirectional work
598 * of type X (e.g., `start_*ops()` might be `start_send_blob_ops()` or
599 * `start_receive_native_handle_ops()`) on a given `T`; the `T` memorizes the function until its destruction.
600 *
601 * From that point on, the `T` might at times be unable to complete an operation (for example
602 * transport::sync_io::Native_socket_stream::send_blob()) synchronously due to a would-block condition on some
603 * internal Native_handle (for example, internally, the Unix domain stream socket in this case encountering
604 * would-block on a `"::sendmsg()"` attempt). It shall then -- synchronously, inside
605 * `Native_socket_stream::send_blob()` -- invoke the saved #Event_wait_func and pass to it the following arguments:
606 * - `hndl_of_interest`: The native handle on which the user's own event loop must wait for an I/O event.
607 * In-depth tips on how to do so are below; but for now:
608 * - If your event loop is built on boost.asio, you may use `hndl_of_interest->async_wait()` directly.
609 * - Otherwise (if you're using `[e]poll*()` perhaps), obtain the raw handle as follows:
610 * `hndl_of_interest->native_handle().m_native_handle`. It can be input to `poll()`, `epoll_ctl()`, etc.
611 * - `ev_of_interest_snd_else_rcv`: Which event it must await: `true` means "writable"; `false` means "readable."
612 * - What if the user's wait (such as `epoll_wait()`) encounters an error-occurred event instead
613 * (`EPOLLERR`)?
614 * Answer: They must in fact report this as-if the requested event (whether writable or readable) is active.
615 * - `on_active_ev_func`: If (and, to avoid pointless perf loss, only if) the above-specified event is active,
616 * the user must invoke `(*on_active_ev_func)()` (without args and expecting no return value).
617 * - In terms of thread safety, and generally, one should consider this function a non-`const` member of `T`'s
618 * sub-API. (The sub-API in question is the set of methods that correspond to unidirectional-operation
619 * of type X, where `T::start_*ops()` was invoked to kick things off. For example, in `Native_socket_stream`
620 * as used as a Blob_sender, that's its `send_blob()` and `*end_sending()` methods.)
621 * That is, `(*on_active_ev_func)()` may not be called concurrently to any `T` sub-API method
622 * (`Native_socket_stream::send_blob()`, `*end_sending()` in the recent example) or other
623 * `(*on_active_ev_func)()`. `(*on_active_ev_func)()` may well, itself, synchronously invoke
624 * `Event_wait_func` to indicate interest in a new event.
625 *
626 * Naturally the key question arises: what, specifically, should a particular #Event_wait_func (as passed-into
627 * a `T::start_*ops()`) *do*? Indeed, this requires great care on the `sync_io` pattern user's part.
628 *
629 * Formally speaking the contract is as follows. Let `F` be the particular `Event_wait_func`.
630 * -# Upon `F()` being called, it shall register -- through a technique of the user's choice (a couple are
631 * explained below for your convenience) -- the specified event as one of interest. The sooner this registration
632 * occurs, the more responsively `T` will behave.
633 * - If the event is already active at registration time (e.g., `hndl_of_interest` is already
634 * readable, when readability is the specified event), the wait must still complete (soon) per the following
635 * steps; it must not sit awaiting a future edge/change. (Internally `T`s do at times request waits on
636 * possibly-already-active events.) `poll()`, level-triggered `epoll_*()`, and boost.asio `.async_wait()`
637 * all behave this way naturally; but see also the `EPOLLET` warning below.
638 * -# It shall arrange, via that same technique, to do the following upon detecting the
639 * event (or `hndl_of_interest` becoming hosed, i.e., the error event). The sooner it does so, the more
640 * responsively `T` will behave.
641 * -# *Deregister* the specified event. Each `Event_wait_func` invocation indicates a **one-off** wait.
642 * -# Call `(*on_active_ev_func)()`. (It is best, for a little perf bump, for the user
643 * to save `std::move(on_active_ev_func)` as opposed to a smart-pointer copy.)
644 *
645 * @warning Do not forget to deregister the event before `(*on_active_ev_func)()`. Failure to do so can easily
646 * result in processor pegging at best; or undefined behavior/assertion tripping.
647 * Worse still, if it's mere processor pegging, you might not notice it happening.
648 * E.g., if `Native_socket_stream::send*()` encounters would-block internally,
649 * it will register interest in writability of an internal handle; suppose when you report writability
650 * it is able to push-through any queued internal payload. Now it no longer needs writability; if
651 * informed of writability anyway, it will at best do nothing -- leading to an infinite loop
652 * of user reporting writability and `Native_socket_stream` ignoring it. Or, if that is how
653 * `Native_socket_stream` is written, it will detect that a write event is being reported despite it
654 * not asking for this and log WARNING or even FATAL/abort program. With `epoll_*()`, `EPOLLONESHOT` and/or
655 * `EPOLLET` may be of aid, but be very careful.
656 *
657 * @warning With this mechanism we are exposing native handles/FDs on which work is potentially performed
658 * by Flow-IPC internals. You may perform operations only as described here, namely read-only "watching"
659 * of events; do not transmit anything or close such handles under any circumstances. In addition such
660 * a native handle is guaranteed to be valid until the `sync_io`-pattern object exists, but once the
661 * destructor begins you must never touch it again. Corollary: See warning below in `epoll`-focused
662 * section below.
663 *
664 * ### Integrating with reactor-pattern `poll()` and similar ###
665 * Suppose your application is using POSIX `poll()`. Typically a data structure will be maintained mirroring
666 * the `fds[].events` (events of interest) sub-argument to `poll()`; for example an `unordered_map<>` from FD
667 * (Native_handle::m_native_handle) to an `enum { S_NONE, S_RD, S_WR, S_RD_WR }`; or simply the `fds[]` array itself.
668 * (In the latter case lookup by FD may not be constant-time. However `fds[].events` does not need to be built
669 * from a mirroring structure ahead of each `poll()`.)
670 *
671 * When `Event_wait_func F(hndl, snd_else_rcv, on_ev_func)` is invoked, you will register the event:
672 * -# If necessary, insert `raw_hndl = hndl->native_handle().m_native_handle` into the data structure,
673 * with no events of interest (which will change in the next step).
674 * If not necessary (already inserted, hence with a (different) event of interest already), continue to next step.
675 * -# Depending on `snd_else_rcv`, change the events-of-interest `enum` NONE->RD, NONE->WR, WR->RD_WR, or
676 * RD->RD_WR. (If operating on an `fds[].events` directly, that's: `fd.events = fd.events | POLLIN` or
677 * `fd.events = fd.events | POLLOUT`.)
678 * -# In some data structure keyed on, conceptually, the pair `(raw_hndl, bool snd_else_rcv)`,
679 * record `std::move(on_ev_func)`. Call it, say, `ipc_events_map`.
680 *
681 * At some point in your reactor-pattern event loop, you are ready to call `poll()`. Construct `fds[].events` array
682 * if needed, or just use the long-term-maintained one, depending on how you've set this up. Call `poll()`.
683 * For each *individual* active event in an `fds[].revents`:
684 * -# Construct the pair `(raw_hndl, snd_else_rcv)`.
685 * -# If it's not in `ipc_events_map`, no IPC-relevant event fired; it must be something relevant to other
686 * parts of your application (such as network traffic). Exit algorithm for now (until next `poll()`);
687 * the IPC-registered event is still being waited-on.
688 * -# If it *is* in `ipc_events_map`:
689 * -# Remove it from there.
690 * -# Deregister the event w/r/t next `poll()`:
691 * -# Depending on `snd_else_rcv`: Change the events-of-interest `enum` RD->NONE, WR->NONE, RD_WR->WR, or
692 * RD_WR->RD. (If operating on an `fds[].events` directly, that's `fd.events = fd.events & ~POLLIN` or
693 * `fd.events = fd.events & ~POLLOUT`.)
694 * -# If the events-of-interest for the FD have become NONE (or `fd.events == 0` if tracking it directly),
695 * delete the handle's entry from the `events`-mirroring structure (or `events` itself if tracking it
696 * directly).
697 * -# Invoke the saved `(*on_ev_func)()`.
698 *
699 * @note While each `F()` invocation indicates one-off event interest, the handler `(*on_ev_func)()` will sometimes,
700 * or possibly frequently, re-indicate interest in the same event within its own handler. If done very
701 * carefully, it *might* be possible to detect this situation by deferring the editing of `events` or
702 * the mirroring structure until `(*on_ev_func)()` finishes; possibly this would net to making no change to
703 * the events-of-interest structure. This could save some processor cycles. I (ygoldfel) would recommend
704 * only getting into that if compelling perf savings evidence appears.
705 *
706 * @note You might notice `hndl` is a pointer to Asio_waitable_native_handle, but you simply get `raw_hndl`
707 * out of it and forget about the rest of `*hndl`. Why not just provide `raw_hndl` to you? Answer:
708 * It is useful when *not* integrating with a reactor-pattern event loop a-la `poll()` or similar but
709 * rather when integrating with a boost.asio event loop. See "Integrating with boost.asio" below.
710 *
711 * ### What about `epoll_*()`? ###
712 * In Linux `epoll_*()` is considered superior (its `man` page at least says that, when used in non-`EPOLLET`
713 * mode, it is a "faster" `poll()`). We leave the exercise of how to apply the above suggestions (for `poll()`)
714 * to `epoll_*()` to the reader. Briefly: Essentially `epoll_ctl()` lets the kernel track a long-running `fds[].events`
715 * sub-array, with add/remove/modify operations specified by the user as syscalls. However, it's not possible to simply
716 * say "I am interested in FD X, event writable"; the events-of-interest per FD are still specified as
717 * an ORing of `EPOLLIN` and/or `EPOLLOUT` in one per-FD entry, whereas `Event_wait_func` is finer-grained than that.
718 * It is not possible to iterate through the kernel-stored events-of-interest set or obtain the existing
719 * `events` bit-mask so as to then `|=` or `&=` it. Therefore a mirroring data structure (such as the
720 * aforementioned `unordered_map<>` from FD to rd/wr/rd-wr) may be necessary in practice. In my (ygoldfel)
721 * experience using such a mirroring thing is typical in any case.
722 *
723 * @warning When a `sync_io`-pattern-API object (e.g., transport::sync_io::Native_socket_stream or
724 * transport::struc::sync_io::Channel or session::sync_io::Server_session_adapter) is about to be
725 * destroyed (by you), it is very possible you have 1+ outstanding async-waits on 1+ native handles (FDs).
726 * You *must* deregister any earlier `epoll_ctl(...ADD)`s via `epoll_ctl(...DEL)`, and you must do
727 * so *before* destroying the Flow-IPC object. Failure to do so can lead to unexpected chaos
728 * (such as [explained here](https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/)).
729 * Conversely doing so but after Flow-IPC object destructor has begun executing breaks the earlier-mentioned
730 * rule wherein a handle must not be touched at that point (and is likely invalid or refers to something else
731 * by then).
732 *
733 * One tip: `EPOLLONESHOT` may be quite useful: It means you can limit your code to just doing
734 * `epoll_ctl(...ADD)` without requiring the counterpart `epoll_ctl(...DEL)` once the event does fire.
735 * (`EPOLLET` may be usable to decrease the number of epoll-ctl calls needed further, but as of this writing
736 * we haven't investigated this sufficiently to make a statement.)
737 *
738 * However `EPOLLONESHOT` pertains to the entire descriptor, not one specific waited-on event (read or write).
739 * Therefore this optimization is helpful only if `.events` is being set to `EPOLLIN` or `EPOLLOUT` -- not
740 * `EPOLLIN | EPOLLOUT`. Be careful. That said, it should be possible to use `dup()` to clone the descriptor
741 * in question; then use the original for (e.g.) `EPOLLIN` exclusively and the clone for `EPOLLOUT` exclusively
742 * resulting in a clean solution.
743 *
744 * ### Integrating with boost.asio ###
745 * Suppose your application is using boost.asio (possibly with flow.async to manage threads),
746 * with a `flow::util::Task_engine::run()` embodying the event loop, in proactor pattern fashion.
747 * *Your* loop is asynchronously expressed, but you can still use the `sync_io` pattern to graft
748 * Flow-IPC operations into it, so that they are invoked synchronously, when you want, in the exact thread you
749 * want. In fact, doing so is *significantly* simpler than integrating with a reactor-style `[e]poll*()`.
750 * That is because, conceptually, each `Event_wait_func` invocation is essentially expressing the following
751 * boost.asio operation:
752 *
753 * ~~~
754 * some_low_level_transport.async_wait(wait_write, // (or `wait_read`.)
755 * [...](const Error_code& err_code)
756 * {
757 * // Can do I/O, or error? Do I/O, or handle error.
758 * // ...If no error then possibly even at some point continue the async-op chain...
759 * same_or_other_low_level_transport.async_wait(wait_write, // (or `wait_read`.)
760 * ...);
761 * }
762 * ~~~
763 *
764 * Because of that you have to code essentially none of the stuff above (pertaining to a reactor-style `[e]poll*()`
765 * loop): no manual registering/deregistering, wondering how to handle error-event, etc. etc.
766 *
767 * So what to do, specifically? It is quite straightforward. Suppose you've got `Task_engine E` doing
768 * `E.run()` as your event loop. (Any `flow::async::*_loop` does that too, just internally once you do
769 * `loop.start()`.) E.g., if you've got TCP sockets attached to `E`, you might be doing
770 * `m_tcp_sock.async_read_some(..., [](const Error_code& err_code, size_t n) { ... });` and so on.
771 *
772 * When `Event_wait_func F(hndl, snd_else_rcv, on_ev_func)` is invoked, you will simply do:
773 *
774 * ~~~
775 * hndl->async_wait(snd_else_rcv ? Asio_waitable_native_handle::Base::wait_write
776 * : Asio_waitable_native_handle::Base::wait_read,
777 * [on_ev_func = std::move(on_ev_func)](const Error_code& err_code)
778 * {
779 * if (err_code != boost::asio::error::operation_aborted)
780 * {
781 * (*on_ev_func)(); // Note: err_code is disregarded. Whether it's readable/writable or hosed, must invoke.
782 * }
783 * // else { Stuff is shutting down; do absolutely nothing! SOP on operation_aborted generally. }
784 * }
785 * ~~~
786 *
787 * Basically, Flow-IPC wants to do an `async_wait()`... so you do it for Flow-IPC, being the master of your
788 * (boost.asio) domain (so to speak).
789 *
790 * `hndl` is an `Asio_waitable_native_handle*`. Asio_waitable_native_handle is a razor-thin wrapper around
791 * a boost.asio `posix::descriptor` which itself is a thin wrapper around a native handle (FD). It has
792 * boost.asio-supplied `.async_wait()`. However, and this is a key point:
793 *
794 * To make it work, before invoking `T::start_*ops()`, you must supply your execution context/executor -- usually
795 * a boost.asio `Task_engine` (a/k/a `boost::asio::io_context`) or strand (`boost::asio::io_context::strand`) --
796 * w/r/t which you plan to `.async_wait()` down the line. This is done via `T::replace_event_wait_handles()`,
797 * an otherwise optional call. If using flow.async, this might be (e.g.):
798 *
799 * ~~~
800 * flow::async::Single_thread_task_loop m_your_single_threaded_event_loop;
801 * ipc::transport::sync_io::Native_socket_stream m_sock_stream;
802 * // ...
803 * m_sock_stream.replace_event_wait_handles([this]() -> auto
804 * {
805 * return ipc::util::sync_io::Asio_waitable_native_handle
806 * {*(m_your_single_threaded_event_loop.task_engine())};
807 * });
808 * m_sock_stream.start_send_blob_ops(F); // F is your Event_wait_func.
809 * ~~~
810 *
811 * @note As explained in "Integrating with reactor-pattern..." above, `hndl` is
812 * a boost.asio I/O object, as opposed to just a Native_handle or even Native_handle::handle_t,
813 * specifically for the boost.asio integration use case. If *not* integrating with boost.asio,
814 * `start_*ops()` is to be used without preceding it by `replace_event_wait_handles()`,
815 * and `hndl->native_handle()` is the only meaningful part of `*hndl`, with `.async_wait()` being meaningless
816 * and unused. Conversely, if integrating with boost.asio, `hndl->native_handle()` itself should not be
817 * required in your code, while `.async_wait()` is the only meaningful aspect of `*hndl`.
818 */
820 bool ev_of_interest_snd_else_rcv,
821 Task_ptr&& on_active_ev_func)>;
822
823} // namespace ipc::util::sync_io
Useful if using the sync_io pattern within a user event loop built on boost.asio (optionally with flo...
Contains common code, as well as important explanatory documentation in the following text,...
Definition: util_fwd.hpp:217
Function< void(Asio_waitable_native_handle *hndl_of_interest, bool ev_of_interest_snd_else_rcv, Task_ptr &&on_active_ev_func)> Event_wait_func
In sync_io pattern, concrete type storing user-supplied function invoked by pattern-implementing ipc:...
boost::shared_ptr< Task > Task_ptr
Short-hand for ref-counted pointer to a Function<> that takes no arguments and returns nothing; in pa...
flow::Function< Signature > Function
Short-hand for polymorphic functor holder which is very common. This is essentially std::function.
Definition: common.hpp:317