Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
session_adapter.hpp
Go to the documentation of this file.
1/* Flow-IPC: Sessions
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
25#include <boost/move/make_unique.hpp>
26#include <queue>
27
29{
30
31// Types.
32
33/* @todo Technically by the established conventions of this project, there should be a ..._fwd.hpp with
34 * forward-declaration of `template<typename Session> class Session_adapter;`. Maybe we should do that. It's just...
35 * exactly two things use it (Client_session_adapter, Server_session_adapter), and it's just such pointless
36 * boiler-plate at the moment to make a sync_io/detail/sync_io_fwd.hpp just for that, when in reality they're
37 * templates and are going to include the present file anyway. Just, eh. For now anyway. It's just in most other
38 * contexts postponing doing this just leads to needing to do it later anyway, at which point it feels more painful.
39 * Here though... ehhhh.... */
40
41/**
42 * Internal-use workhorse containing common elements of Client_session_adapter and Server_session_adapter.
43 * As such it contains the machinery for:
44 * - storing (deriving from) the adapted `Session` as accessible via Session_adapter::core();
45 * - adapting async-I/O on-error and on-passive-channel-open (if enabled) events to `sync_io` pattern;
46 * - storing and invoking the central util::sync_io::Event_wait_func supplied by the user;
47 * - type aliases common to all sessions.
48 *
49 * @tparam Session
50 * See, e.g., Client_session_adapter.
51 */
52template<typename Session>
54{
55public:
56 // Types.
57
58 /// See, e.g., Client_session_adapter.
60
61 /// See, e.g., Client_session_adapter.
63 /// See, e.g., Client_session_adapter.
65
66 /// Short-hand for session-openable Channel type.
68
69 /// Short-hand for session-open metadata reader.
71
72 /// Short-hand for passive-channel-open handler.
74
75 // Constructors/destructor.
76
77 /**
78 * Compilable only when #Session_obj is a `Client_session` variant, forwards to its ctor of identical form,
79 * except that the handlers are replaced with `sync_io`-adapting ones. Do not use init_handlers() after this ctor,
80 * as that is for `Server_session`.
81 *
82 * @param logger_ptr
83 * See Client_session_mv.
84 * @param cli_app_ref
85 * See Client_session_mv.
86 * @param srv_app_ref
87 * See Client_session_mv.
88 * @param on_err_func
89 * See Client_session_mv. However in `*this` case this shall be invoked -- though still only following
90 * successful `core()->sync_connect()` as usual -- according to `sync_io` pattern, synchronously inside
91 * an async-wait performed by you and reported via `(*on_active_ev_func)()`.
92 * @param on_passive_open_channel_func
93 * See Client_session_mv. However in `*this` case this shall be invoked -- though still only following
94 * successful `core()->sync_connect()` as usual -- according to `sync_io` pattern, synchronously inside
95 * an async-wait performed by you and reported via `(*on_active_ev_func)()`.
96 * @tparam On_passive_open_channel_handler
97 * See Client_session_mv.
98 * @tparam Task_err
99 * See Client_session_mv.
100 */
101 template<typename On_passive_open_channel_handler, typename Task_err>
102 explicit Session_adapter(flow::log::Logger* logger_ptr,
103 const Client_app& cli_app_ref, const Server_app& srv_app_ref,
104 Task_err&& on_err_func,
105 On_passive_open_channel_handler&& on_passive_open_channel_func);
106
107 /**
108 * Compilable only when #Session_obj is a `Client_session` variant, forwards to its ctor of identical form,
109 * except that the handler is replaced with `sync_io`-adapting one. Do not use init_handlers() after this ctor,
110 * as that is for `Server_session`.
111 *
112 * @param logger_ptr
113 * Logger to use for logging subsequently.
114 * @param cli_app_ref
115 * See Client_session_mv.
116 * @param srv_app_ref
117 * See Client_session_mv.
118 * @param on_err_func
119 * See Client_session_mv. However in `*this` case this shall be invoked -- though still only following
120 * successful `*_connect()` as usual -- according to `sync_io` pattern, synchronously inside
121 * an async-wait performed by you and reported via `(*on_active_ev_func)()`.
122 * @tparam Task_err
123 * See Client_session_mv.
124 */
125 template<typename Task_err>
126 explicit Session_adapter(flow::log::Logger* logger_ptr,
127 const Client_app& cli_app_ref, const Server_app& srv_app_ref,
128 Task_err&& on_err_func);
129
130 /// Forwards to the #Session_obj default ctor.
132
133 /**
134 * See, e.g., Client_session_adapter.
135 *
136 * @tparam Event_wait_func_t
137 * See above.
138 * @param ev_wait_func
139 * See above.
140 * @return See above.
141 */
142 template<typename Event_wait_func_t>
143 bool start_ops(Event_wait_func_t&& ev_wait_func);
144
145 /**
146 * See, e.g., Client_session_adapter.
147 *
148 * @tparam Create_ev_wait_hndl_func
149 * See above.
150 * @param create_ev_wait_hndl_func
151 * See above.
152 * @return See above.
153 */
154 template<typename Create_ev_wait_hndl_func>
155 bool replace_event_wait_handles(const Create_ev_wait_hndl_func& create_ev_wait_hndl_func);
156
157 /**
158 * Compilable only when #Session_obj is a `Server_session` variant, forwards to its method of identical form,
159 * except that the handlers are replaced with `sync_io`-adapting ones. Do not use non-default ctor before this,
160 * as that is for `Client_session`.
161 *
162 * @tparam Task_err
163 * See Session_server.
164 * @tparam On_passive_open_channel_handler
165 * See Session_server.
166 * @param on_err_func_arg
167 * See Session_server.
168 * @param on_passive_open_channel_func_arg
169 * See Session_server.
170 * @return See Session_server; in addition returns `false`/no-ops if invoked before start_ops().
171 */
172 template<typename Task_err, typename On_passive_open_channel_handler>
173 bool init_handlers(Task_err&& on_err_func_arg, On_passive_open_channel_handler&& on_passive_open_channel_func_arg);
174
175 /**
176 * Compilable only when #Session_obj is a `Server_session` variant, forwards to its method of identical form,
177 * except that the handlers are replaced with `sync_io`-adapting ones. Do not use non-default ctor before this,
178 * as that is for `Client_session`.
179 *
180 * @tparam Task_err
181 * See Session_server.
182 * @param on_err_func_arg
183 * See Session_server.
184 * @return See Session_server; in addition returns `false`/no-ops if invoked before start_ops().
185 */
186 template<typename Task_err>
187 bool init_handlers(Task_err&& on_err_func_arg);
188
189 /**
190 * See `flow::log::Log_context`.
191 * @return See above.
192 */
193 flow::log::Logger* get_logger() const;
194
195 /**
196 * See `flow::log::Log_context`.
197 * @return See above.
198 */
199 const flow::log::Component& get_log_component() const;
200
201protected:
202 // Methods.
203
204 /**
205 * Forwards to the util::sync_io::Event_wait_func saved in start_ops().
206 *
207 * @tparam Args
208 * See above.
209 * @param args
210 * See above.
211 */
212 template<typename... Args>
213 void async_wait(Args&&... args);
214
215 /**
216 * Utility that sets up an IPC-pipe in the given peer objects as well as loading a watcher-descriptor
217 * object for the read-end. This should be called before start_ops() or replace_event_wait_handles().
218 *
219 * @param reader
220 * Pipe read end to load. It should be constructed with its intended execution context/executor but
221 * not `.is_open()`.
222 * @param writer
223 * Pipe write end to load. It should be constructed with its intended execution context/executor but
224 * not `.is_open()`.
225 * @param ev_wait_hndl
226 * Watcher for `reader`. It should be constructed with its intended execution context/executor but
227 * not `.is_open()`.
228 */
229 void init_pipe(util::Pipe_reader* reader, util::Pipe_writer* writer,
231
232 /**
233 * The adapted mutable #Session_obj.
234 * @return See above.
235 */
236 Session_obj* core();
237
238 /**
239 * The adapted mutable #Session_obj.
240 * @return See above.
241 */
242 const Session_obj* core() const;
243
244private:
245 // Types.
246
247 /// Set of result arg values from a successful passive-channel-open from a #Session_obj invoking #On_channel_func.
249 {
250 // Types.
251
252 /// Short-hand for pointer wrapper around a `*this`.
253 using Ptr = boost::movelib::unique_ptr<Channel_open_result>;
254
255 // Data.
256
257 /// Result 1/2 about to be given to #m_on_channel_func_or_empty.
259
260 /// Result 2/2 about to be given to #m_on_channel_func_or_empty.
262 };
263
264 /// Queue of Channel_open_result.
265 using Channel_open_result_q = std::queue<typename Channel_open_result::Ptr>;
266
267 // Methods.
268
269 /**
270 * Signaled by the function returned by on_channel_func_sio(), it pops the next result from
271 * #m_target_channel_open_q and invokes #m_on_channel_func_or_empty with it; then consumes the corresponding
272 * signal byte from the IPC-pipe; and lastly begins the next async-wait for the procedure.
273 */
274 void on_ev_channel_open();
275
276 /**
277 * Returns the proper on-error handler to set up on the underlying #Session_obj (`Client_session`:
278 * via ctor; `Server_session`: via `init_handlers()`).
279 *
280 * The resulting handler merely records the result and signals the IPC-pipe (so it may fire at any time); the
281 * user-thread handling of that signal invokes #m_on_err_func, which must be set by then. (It is: via ctor, before
282 * the core even exists; via init_handlers(), right after installing the handler, in the same user-thread call.)
283 *
284 * @return See above.
285 */
286 flow::async::Task_asio_err on_err_func_sio();
287
288 /**
289 * Returns the proper on-passive-channel-open handler to set up on the underlying #Session_obj (`Client_session`:
290 * via ctor; `Server_session`: via `init_handlers()` 2-arg form).
291 *
292 * Same notes as for on_err_func_sio() apply here, with #m_on_channel_func_or_empty in place of #m_on_err_func.
293 *
294 * @return See above.
295 */
297
298 // Data.
299
300 /**
301 * The `Task_engine` for `m_ready_*`. It is necessary to construct those pipe-end objects, but we never
302 * use that guy's `->async_*()` APIs -- only non-blocking operations, essentially leveraging boost.asio's
303 * portable transmission APIs but not its actual, um, async-I/O abilities in this case. Accordingly we
304 * never load any tasks onto #m_nb_task_engine and certainly never `.run()` (or `.poll()` or ...) it.
305 *
306 * In the `sync_io` pattern the user's outside event loop is responsible for awaiting readability/writability
307 * of a guy like #m_ready_reader_chan via our exporting of its `.native_handle()`.
308 */
309 flow::util::Task_engine m_nb_task_engine;
310
311 /**
312 * The `Task_engine` for `m_ev_wait_hndl_*`, unless it is replaced via replace_event_wait_handles().
313 *
314 * This is to fulfill the `sync_io` pattern.
315 */
316 flow::util::Task_engine m_ev_hndl_task_engine_unused;
317
318 /**
319 * Read-end of IPC-pipe used by `*this` to detect that the error-wait has completed. The signal byte
320 * is detected in #m_ready_reader_err. There is no need to read it, as an error can occur at most once.
321 *
322 * @see #m_ready_writer_err.
323 */
325
326 /// Write-end of IPC-pipe together with #m_ready_reader_err.
328
329 /**
330 * Descriptor waitable by outside event loop async-waits -- storing the same `Native_handle` as (and thus being
331 * used to wait on events from) #m_ready_reader_err.
332 */
334
335 /**
336 * Read-end of IPC-pipe used by `*this` to detect that a channel-open-wait has completed. The signal byte
337 * is read out of #m_ready_reader_chan, after it was written there via #m_ready_writer_chan. As explained in
338 * #m_target_channel_open_q doc header, channel-opens can occur at any time from another thread, even while
339 * `*this` is synchronously dealing with an existing one; therefore this is a rare case where more than 1 byte
340 * could be readable at a given time. Nevertheless we issue one async-wait on #m_ready_reader_chan per
341 * channel-open, in series; eventually all of them get popped, meaning all the channel-opens have been served.
342 *
343 * This is not a rare occurrence necessarily: an opposing `Session` user can perform 2+ `open_channel()` right in
344 * a row, and since creating a `Channel` with all its 2-4 fat peer objects can be relatively lengthy on this
345 * side, the 2nd/3rd/... on-channel-open firing can "step on" a preceding one.
346 *
347 * @see #m_ready_writer_chan.
348 */
350
351 /// Write-end of IPC-pipe together with #m_ready_reader_chan.
353
354 /**
355 * Descriptor waitable by outside event loop async-waits -- storing the same `Native_handle` as (and thus being
356 * used to wait on events from) #m_ready_reader_chan.
357 */
359
360 /**
361 * Function (set forever in start_ops()) through which we invoke the outside event loop's
362 * async-wait facility for descriptors/events relevant to our ops. See util::sync_io::Event_wait_func
363 * doc header for a refresher on this mechanic.
364 */
366
367 /// `on_err_func` from init_handlers() or ctor; `.empty()` until then, and once it has been invoked.
368 flow::async::Task_asio_err m_on_err_func;
369
370 /**
371 * `on_passive_open_channel_func_or_empty` from init_handlers() or ctor (possibly `.empty()` if not supplied);
372 * until then `.empty()`.
373 */
375
376 /// Result given to (or about to be given to) #m_on_err_func.
378
379 /**
380 * Queue of #On_channel_func handler arg sets received from async-I/O #Session_obj -- meaning
381 * the `Session`, in unspecified background thread, informing us (which we signal via #m_ready_writer_chan)
382 * a channel has been passively open -- and not yet fed to #m_on_channel_func_or_empty.
383 *
384 * Protected by #m_target_channel_open_q_mutex.
385 *
386 * ### Rationale ###
387 * There's a queue here, which stands in contrast to (as of this writing) all other `sync_io` pattern impls
388 * in the project, including the on-error stuff in `*this`: those always deal with a single async-op at a time,
389 * and therefore no queue is necessary (just a single arg-set; usually just an `Error_code`, sometimes
390 * with a `size_t sz` though).
391 *
392 * The reason this one is different is as follows.
393 * - It's unlike, e.g., a `"Client_session_adapter::async_connect()"`, because an async-connect is triggered by the
394 * user explicitly, which triggers a single async-wait, and we disallow in our API to trigger more, until
395 * that one completes. (Confusing note: There is no such async-connect method as of this writing, because there's
396 * no `async_connect()` on Client_session_mv, which the latter's doc header justifies... while also noting it
397 * *would* exist, if that guy were network-enabled; we'll probably add this at some point. So pretend it did to
398 * grok this bullet point. Or just ignore it for now.)
399 * - It's unlike, e.g., #m_on_err_func stuff in `*this`, because while that one can indeed happen at any time
400 * from another thread -- without the user explicitly beginning the async-op (it sort of begins by itself) --
401 * it can also only happen at most once per `*this` (modulo move-assignment).
402 *
403 * At any rate: the handler we register with #m_async_io -- on_channel_func_sio() -- can be invoked
404 * from a background thread at any time. This triggers signaling #m_ready_reader_chan (via #m_ready_writer_chan),
405 * on which there is always a `sync_io`-pattern async-wait outstanding. We are informed of a completed
406 * async-wait (byte is available on #m_ready_reader_chan); we consume the byte and pop and pass the result
407 * from top of #m_target_channel_open_q. Then we start another async-wait. If there are more items in the queue
408 * that were added in the meantime, the same number of bytes have been pushed into #m_ready_writer_chan;
409 * so those async-waits are satisfied (in series) quickly, leading to the progressive emptying of this queue
410 * and passing along those results to `m_on_channel_func_or_empty()`.
411 */
413
414 /// Protects #m_target_channel_open_q, accessed from user async-wait-reporter thread; and #Session_obj worker thread.
415 mutable flow::util::Mutex_non_recursive m_target_channel_open_q_mutex;
416
417 /// This guy does all the work. In our dtor this will be destroyed (hence thread stopped) first-thing.
419}; // class Session_adapter
420
421// Template implementations.
422
423template<typename Session>
425 m_ready_reader_err(m_nb_task_engine), // No handle inside but will be set-up soon below.
426 m_ready_writer_err(m_nb_task_engine), // Ditto.
427 m_ev_wait_hndl_err(m_ev_hndl_task_engine_unused), // This needs to be .assign()ed still.
428 m_ready_reader_chan(m_nb_task_engine),
429 m_ready_writer_chan(m_nb_task_engine),
430 m_ev_wait_hndl_chan(m_ev_hndl_task_engine_unused)
431{
434}
435
436template<typename Session>
437template<typename On_passive_open_channel_handler, typename Task_err>
438Session_adapter<Session>::Session_adapter(flow::log::Logger* logger_ptr,
439 const Client_app& cli_app_ref, const Server_app& srv_app_ref,
440 Task_err&& on_err_func,
441 On_passive_open_channel_handler&& on_passive_open_channel_func) :
442 m_ready_reader_err(m_nb_task_engine), // No handle inside but will be set-up soon below.
443 m_ready_writer_err(m_nb_task_engine), // Ditto.
444 m_ev_wait_hndl_err(m_ev_hndl_task_engine_unused), // This needs to be .assign()ed still.
445 m_ready_reader_chan(m_nb_task_engine),
446 m_ready_writer_chan(m_nb_task_engine),
447 m_ev_wait_hndl_chan(m_ev_hndl_task_engine_unused),
448 m_on_err_func(std::move(on_err_func)),
449 m_on_channel_func_or_empty(std::move(on_passive_open_channel_func)),
450 m_async_io(logger_ptr, cli_app_ref, srv_app_ref, on_err_func_sio(), on_channel_func_sio())
451{
454}
455
456template<typename Session>
457template<typename Task_err>
458Session_adapter<Session>::Session_adapter(flow::log::Logger* logger_ptr,
459 const Client_app& cli_app_ref, const Server_app& srv_app_ref,
460 Task_err&& on_err_func) :
461 m_ready_reader_err(m_nb_task_engine), // No handle inside but will be set-up soon below.
462 m_ready_writer_err(m_nb_task_engine), // Ditto.
463 m_ev_wait_hndl_err(m_ev_hndl_task_engine_unused), // This needs to be .assign()ed still.
464 m_ready_reader_chan(m_nb_task_engine),
465 m_ready_writer_chan(m_nb_task_engine),
466 m_ev_wait_hndl_chan(m_ev_hndl_task_engine_unused),
467 m_on_err_func(std::move(on_err_func)),
468 m_async_io(logger_ptr, cli_app_ref, srv_app_ref, on_err_func_sio())
469{
472}
473
474template<typename Session>
475template<typename Task_err, typename On_passive_open_channel_handler>
476bool Session_adapter<Session>::init_handlers(Task_err&& on_err_func_arg,
477 On_passive_open_channel_handler&& on_passive_open_channel_func_arg)
478{
479 if (m_ev_wait_func.empty())
480 {
481 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: init_handlers() called before start_ops(). Ignoring.");
482 return false;
483 }
484 // else
485
486 /* Install into the core first; it rejects a duplicate call (or a not-yet-accepted, as-if-default-cted core).
487 * Only then save the user handlers. That order is fine: the *_sio() handlers, which may fire (in thread W) as soon
488 * as this returns, touch only the IPC-pipes, m_target_*; while the user handlers are read only via
489 * (*on_active_ev_func)() -- in the user's thread, not concurrently with us (per `sync_io` pattern). */
490 if (!core()->init_handlers(on_err_func_sio(), on_channel_func_sio()))
491 {
492 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: init_handlers() rejected by the async-I/O core "
493 "(called duplicately? session not yet accepted?). Ignoring.");
494 return false;
495 }
496 // else
497
498 m_on_err_func = std::move(on_err_func_arg);
499 m_on_channel_func_or_empty = std::move(on_passive_open_channel_func_arg);
500 return true;
501} // Session_adapter::init_handlers()
502
503template<typename Session>
504template<typename Task_err>
505bool Session_adapter<Session>::init_handlers(Task_err&& on_err_func_arg)
506{
507 if (m_ev_wait_func.empty())
508 {
509 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: init_handlers() called before start_ops(). Ignoring.");
510 return false;
511 }
512 // else
513
514 // Same comment as in the other overload.
515 if (!core()->init_handlers(on_err_func_sio()))
516 {
517 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: init_handlers() rejected by the async-I/O core "
518 "(called duplicately? session not yet accepted?). Ignoring.");
519 return false;
520 }
521 // else
522
523 m_on_err_func = std::move(on_err_func_arg);
524 return true;
525} // Session_adapter::init_handlers()
526
527template<typename Session>
530{
532 using boost::asio::connect_pipe;
533
534 Error_code sys_err_code;
535
536 connect_pipe(*reader, *writer, sys_err_code);
537 if (sys_err_code)
538 {
539 FLOW_LOG_FATAL("Session_adapter [" << m_async_io << "]: Constructing: connect-pipe failed. Details follow.");
540 FLOW_ERROR_SYS_ERROR_LOG_FATAL();
541 assert(false && "We chose not to complicate the code given how unlikely this is, and how hosed you'd have to be.");
542 std::abort();
543 }
544
545 ev_wait_hndl->assign(Native_handle{reader->native_handle()});
546}
547
548template<typename Session>
549template<typename Event_wait_func_t>
550bool Session_adapter<Session>::start_ops(Event_wait_func_t&& ev_wait_func)
551{
552 using util::Task;
553
554 if (!m_ev_wait_func.empty())
555 {
556 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: Start-ops requested, "
557 "but we are already started. Probably a user bug, but it is not for us to judge.");
558 return false;
559 }
560 // else
561
562 m_ev_wait_func = std::move(ev_wait_func);
563
564 /* No time to waste! on_err_func_sio() and on_channel_func_sio() had to have been called already; begin waiting
565 * for those events to be indicated via our IPC-pipes as set up in those methods. */
566
567 async_wait(&m_ev_wait_hndl_err,
568 false, // Wait for read.
569 boost::make_shared<Task>([this]()
570 {
571 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Async-IO core on-error event: informed via IPC-pipe; "
572 "invoking handler.");
573 util::pipe_consume(get_logger(), &m_ready_reader_err); // No need really -- it's a one-time thing -- but just....
574
575 auto on_done_func = std::move(m_on_err_func);
576 m_on_err_func.clear(); // In case move() didn't do it. Might as well forget it.
577
578 on_done_func(m_target_err_code_err);
579 FLOW_LOG_TRACE("Handler completed.");
580 }));
581
582 /* Subtlety: It is tempting to guard this with `if (!m_on_channel_func_or_empty.empty()) {}` in the effort to
583 * avoid an async-wait that will never get satisfied. However that only works in the Client_session case,
584 * when m_on_channel_func_or_empty is known from ction. Server_session still needs init_handlers() by user,
585 * and indeed that would be after start_ops(), so => bug. We could add fancier logic to account for this,
586 * but who really cares? So it'll register an extra FD in some boost.asio epoll-set. Meh. */
587 async_wait(&m_ev_wait_hndl_chan,
588 false, // Wait for read.
589 boost::make_shared<Task>([this]() { on_ev_channel_open(); }));
590
591 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Start-ops requested. Done.");
592 return true;
593} // Session_adapter::start_ops()
594
595template<typename Session>
597{
598 using util::Task;
599 using flow::util::Lock_guard;
600
601 // We are in user calling thread.
602
603 typename Channel_open_result::Ptr result;
604 {
605 Lock_guard<decltype(m_target_channel_open_q_mutex)> lock{m_target_channel_open_q_mutex};
606
607 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Async-IO core passively-opened channel event: "
608 "informed via IPC-pipe; invoking handler. Including this one "
609 "[" << m_target_channel_open_q.size() << "] are pending.");
610
611 assert((!m_target_channel_open_q.empty())
612 && "Algorithm bug? Result-queue elements and pipe signal bytes must be 1-1, so either something "
613 "failed to correctly push, or something overzealously popped.");
614
615 result = std::move(m_target_channel_open_q.front());
616 m_target_channel_open_q.pop();
617 } // Lock_guard lock{m_target_channel_open_q_mutex};
618
619 m_on_channel_func_or_empty(std::move(result->m_channel), std::move(result->m_mdt_reader_ptr));
620 FLOW_LOG_TRACE("Handler completed. Beginning next async-wait immediately. If more is/are pending "
621 "it/they'll be popped quickly due to immediately-completing async-wait(s).");
622
623 util::pipe_consume(get_logger(), &m_ready_reader_chan);
624 async_wait(&m_ev_wait_hndl_chan,
625 false, // Wait for read -- again.
626 boost::make_shared<Task>([this]() { on_ev_channel_open(); }));
627} // Session_adapter::on_ev_channel_open()
628
629template<typename Session>
630template<typename Create_ev_wait_hndl_func>
631bool Session_adapter<Session>::replace_event_wait_handles(const Create_ev_wait_hndl_func& create_ev_wait_hndl_func)
632{
634
635 if (!m_ev_wait_func.empty())
636 {
637 FLOW_LOG_WARNING("Session_adapter [" << m_async_io << "]: Cannot replace event-wait handles after "
638 "a start-*-ops procedure has been executed. Ignoring.");
639 return false;
640 }
641 // else
642
643 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Replacing event-wait handles (probably to replace underlying "
644 "execution context with outside event loop's boost.asio Task_engine or similar).");
645
646 assert(m_ev_wait_hndl_err.is_open());
647 assert(m_ev_wait_hndl_chan.is_open());
648
649 Native_handle saved{m_ev_wait_hndl_err.release()};
650 m_ev_wait_hndl_err = create_ev_wait_hndl_func();
651 m_ev_wait_hndl_err.assign(saved);
652
653 saved = m_ev_wait_hndl_chan.release();
654 m_ev_wait_hndl_chan = create_ev_wait_hndl_func();
655 m_ev_wait_hndl_chan.assign(saved);
656
657 return true;
658} // Session_adapter::replace_event_wait_handles()
659
660template<typename Session>
662{
663 using flow::async::Task_asio_err;
664
665 // Careful in here! *this may not have been constructed yet.
666
667 return [this](const Error_code& err_code)
668 {
669 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Async-IO core reports on-error event: tickling IPC-pipe to "
670 "inform user.");
671
672 assert((!m_target_err_code_err)
673 && "Error handler must never fire more than once per Session! Bug in the particular Session_obj type?");
674 m_target_err_code_err = err_code;
675
676 util::pipe_produce(get_logger(), &m_ready_writer_err);
677 };
678
679 // start_ops() will set up the (1-time) async-wait for m_ready_reader_err being readable.
680} // Session_adapter::on_err_func_sio()
681
682template<typename Session>
685{
686 using flow::util::Lock_guard;
687
688 // Careful in here! *this may not have been constructed yet.
689
690 return [this](Channel_obj&& new_channel, Mdt_reader_ptr&& new_channel_mdt)
691 {
692 // We are in Client_session_mv/Server_session_mv "unspecified" worker thread (called thread W in internal docs).
693 {
694 Lock_guard<decltype(m_target_channel_open_q_mutex)> lock{m_target_channel_open_q_mutex};
695
696 FLOW_LOG_INFO("Session_adapter [" << m_async_io << "]: Async-IO core reports passively-opened channel event: "
697 "tickling IPC-pipe to inform user. This will make the # of pending such events "
698 "[" << (m_target_channel_open_q.size() + 1) << "].");
699 m_target_channel_open_q.emplace(boost::movelib::make_unique<Channel_open_result>());
700 auto& result = *(m_target_channel_open_q.back());
701 result.m_channel = std::move(new_channel);
702 result.m_mdt_reader_ptr = std::move(new_channel_mdt);
703 } // Lock_guard lock{m_target_channel_open_q_mutex};
704
705 /* By Session contract, handlers are never called concurrently with each other. Though this
706 * in POSIX is thread-safe even otherwise (w/r/t another pipe_produce() and most certainly w/r/t
707 * pipe_consume() of the other end). */
708 util::pipe_produce(get_logger(), &m_ready_writer_chan);
709 }; // return [](){}
710
711 /* start_ops() will set up the (first) async-wait for m_ready_reader_chan being readable, as well as
712 * the action on that wait being satisfied; namely: to pipe_consume() that signal + begin the next wait. */
713} // Session_adapter::on_channel_func_sio()
714
715template<typename Session>
716template<typename... Args>
718{
719 m_ev_wait_func(std::forward<Args>(args)...);
720}
721
722template<typename Session>
724{
725 return &m_async_io;
726}
727
728template<typename Session>
730{
731 return const_cast<Session_adapter*>(this)->core();
732}
733
734template<typename Session>
735flow::log::Logger* Session_adapter<Session>::get_logger() const
736{
737 return core()->get_logger();
738}
739
740template<typename Session>
741const flow::log::Component& Session_adapter<Session>::get_log_component() const
742{
743 return core()->get_log_component();
744}
745
746} // namespace ipc::session::sync_io
A documentation-only concept defining the local side of an IPC conversation (session) with another en...
Definition: session.hpp:217
unspecified Channel_obj
Each successful open_channel() and on-passive-open handler firing shall yield a concrete transport::C...
Definition: session.hpp:236
shared_ptr< typename transport::struc::schema::Metadata< Mdt_payload_obj >::Reader > Mdt_reader_ptr
Ref-counted handle to a capnp-generated Reader (and the payload it accesses) through which the user s...
Definition: session.hpp:266
typename Base::Session_obj Session_obj
Short-hand, for generic programming et al, for template parameter Session.
Internal-use workhorse containing common elements of Client_session_adapter and Server_session_adapte...
flow::async::Task_asio_err m_on_err_func
on_err_func from init_handlers() or ctor; .empty() until then, and once it has been invoked.
const flow::log::Component & get_log_component() const
See flow::log::Log_context.
util::Pipe_writer m_ready_writer_err
Write-end of IPC-pipe together with m_ready_reader_err.
util::sync_io::Asio_waitable_native_handle m_ev_wait_hndl_err
Descriptor waitable by outside event loop async-waits – storing the same Native_handle as (and thus b...
typename Session_obj::Channel_obj Channel_obj
Short-hand for session-openable Channel type.
typename Session_obj::Mdt_reader_ptr Mdt_reader_ptr
Short-hand for session-open metadata reader.
void init_pipe(util::Pipe_reader *reader, util::Pipe_writer *writer, util::sync_io::Asio_waitable_native_handle *ev_wait_hndl)
Utility that sets up an IPC-pipe in the given peer objects as well as loading a watcher-descriptor ob...
void on_ev_channel_open()
Signaled by the function returned by on_channel_func_sio(), it pops the next result from m_target_cha...
On_channel_func on_channel_func_sio()
Returns the proper on-passive-channel-open handler to set up on the underlying Session_obj (Client_se...
flow::async::Task_asio_err on_err_func_sio()
Returns the proper on-error handler to set up on the underlying Session_obj (Client_session: via ctor...
bool replace_event_wait_handles(const Create_ev_wait_hndl_func &create_ev_wait_hndl_func)
See, e.g., Client_session_adapter.
Error_code m_target_err_code_err
Result given to (or about to be given to) m_on_err_func.
void async_wait(Args &&... args)
Forwards to the util::sync_io::Event_wait_func saved in start_ops().
util::Pipe_reader m_ready_reader_err
Read-end of IPC-pipe used by *this to detect that the error-wait has completed.
flow::util::Mutex_non_recursive m_target_channel_open_q_mutex
Protects m_target_channel_open_q, accessed from user async-wait-reporter thread; and Session_obj work...
Session_adapter()
Forwards to the Session_obj default ctor.
util::sync_io::Asio_waitable_native_handle m_ev_wait_hndl_chan
Descriptor waitable by outside event loop async-waits – storing the same Native_handle as (and thus b...
Session_obj * core()
The adapted mutable Session_obj.
util::Pipe_reader m_ready_reader_chan
Read-end of IPC-pipe used by *this to detect that a channel-open-wait has completed.
Function< void(Channel_obj &&, Mdt_reader_ptr &&)> On_channel_func
Short-hand for passive-channel-open handler.
flow::log::Logger * get_logger() const
See flow::log::Log_context.
Session Session_obj
See, e.g., Client_session_adapter.
bool start_ops(Event_wait_func_t &&ev_wait_func)
See, e.g., Client_session_adapter.
Async_io_obj m_async_io
This guy does all the work. In our dtor this will be destroyed (hence thread stopped) first-thing.
util::sync_io::Event_wait_func m_ev_wait_func
Function (set forever in start_ops()) through which we invoke the outside event loop's async-wait fac...
Channel_open_result_q m_target_channel_open_q
Queue of On_channel_func handler arg sets received from async-I/O Session_obj – meaning the Session,...
std::queue< typename Channel_open_result::Ptr > Channel_open_result_q
Queue of Channel_open_result.
bool init_handlers(Task_err &&on_err_func_arg, On_passive_open_channel_handler &&on_passive_open_channel_func_arg)
Compilable only when Session_obj is a Server_session variant, forwards to its method of identical for...
util::Pipe_writer m_ready_writer_chan
Write-end of IPC-pipe together with m_ready_reader_chan.
flow::util::Task_engine m_nb_task_engine
The Task_engine for m_ready_*.
On_channel_func m_on_channel_func_or_empty
on_passive_open_channel_func_or_empty from init_handlers() or ctor (possibly .empty() if not supplied...
flow::util::Task_engine m_ev_hndl_task_engine_unused
The Task_engine for m_ev_wait_hndl_*, unless it is replaced via replace_event_wait_handles().
Dummy type for use as a template param to Channel when either the blobs pipe or handles pipe is disab...
Definition: channel.hpp:1137
Useful if using the sync_io pattern within a user event loop built on boost.asio (optionally with flo...
void assign(Native_handle hndl)
Loads value to be returned by native_handle().
sync_io-pattern counterparts to async-I/O-pattern object types in parent namespace ipc::session.
util::Native_handle Native_handle
Convenience alias for the commonly used type util::Native_handle.
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:...
flow::async::Task Task
Short-hand for polymorphic function (a-la std::function<>) that takes no arguments and returns nothin...
Definition: util_fwd.hpp:119
void pipe_produce(flow::log::Logger *logger_ptr, Pipe_writer *pipe)
Writes a byte to the given pipe writer.
Definition: util.cpp:60
void pipe_consume(flow::log::Logger *logger_ptr, Pipe_reader *pipe)
Reads a byte via the given pipe reader.
Definition: util.cpp:89
boost::asio::writable_pipe Pipe_writer
Short-hand for anonymous pipe write end.
Definition: util_fwd.hpp:35
boost::asio::readable_pipe Pipe_reader
Short-hand for anonymous pipe read end.
Definition: util_fwd.hpp:38
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
flow::Function< Signature > Function
Short-hand for polymorphic functor holder which is very common. This is essentially std::function.
Definition: common.hpp:317
An App that is used as a client in at least one client-server IPC split.
Definition: app.hpp:220
An App that is used as a server in at least one client-server IPC split.
Definition: app.hpp:241
Set of result arg values from a successful passive-channel-open from a Session_obj invoking On_channe...
Channel_obj m_channel
Result 1/2 about to be given to m_on_channel_func_or_empty.
Mdt_reader_ptr m_mdt_reader_ptr
Result 2/2 about to be given to m_on_channel_func_or_empty.
boost::movelib::unique_ptr< Channel_open_result > Ptr
Short-hand for pointer wrapper around a *this.
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.