Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
ez_rpc.hpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
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// Attention: Please see Addendum at the end of this source code file.
19
20/// @file
21#pragma once
22
27#include "ipc/util/util_fwd.hpp"
28#include <flow/log/log.hpp>
29#include <kj/async.h>
30#include <boost/move/unique_ptr.hpp>
31#include <boost/array.hpp>
32
34{
35
36// Types.
37
38/**
39 * Much like `capnp::EzRpcClient` but uses Flow-IPC zero-copy machinery. That, at its core, uses Session_vat_network
40 * instead of `capnp::TwoPartyVatNetwork`.
41 *
42 * @see ipc::transport::struc::shm::rpc namespace doc header for an overview of Flow-IPC capnp-RPC support, including
43 * how the present class relates to the others there.
44 *
45 * Looking at it from the angle of the rest of the present sub-namespace ipc::transport::struc::shm::rpc:
46 * This, really, is a quite-thin layer around Client_context. What it adds is it starts (or shares) the KJ
47 * event loop for this thread (instead of taking a pointer to one the user has prepared). Plus it assumes
48 * the encouraged capnp-RPC pattern wherein the session-client obtains a bootstrap `interface` impl which is
49 * originally implemented on the opposing side (where it is passed-to an Ez_rpc_server or equivalent).
50 *
51 * On this side a simple use of Ez_rpc_client might look like:
52 *
53 * ~~~
54 * // # .capnp schema:
55 * // interface Adder { add @0 (left :Int32, right :Int32) -> (value :Int32); }
56 * // ...
57 * ipc::transport::struc::shm::rpc::
58 * Ez_rpc_client<Session> client{nullptr,
59 * CLI_APPS.find(CLI_NAME)->second, // Our App::m_name is CLI_NAME.
60 * SRV_APPS.find(SRV_NAME)->second}; // Opposing App::m_name is SRV_NAME.
61 * // Connected instantly (in that ctor). Do RPC-work.
62 * Adder::Client adder = client.get_main<Adder>();
63 * auto request = adder.addRequest();
64 * request.setLeft(12);
65 * request.setRight(34);
66 * auto response = request.send().wait(*(client.get_wait_scope())); // The (very short) wait for IPC occurs here.
67 * assert(response.getValue() == 46);
68 * ~~~
69 *
70 * @see Ez_rpc_server (including for server-side snippet completing the above).
71 * @see Client_context for a somewhat lower-layer access point into similar functionality.
72 *
73 * ### KJ event loop info ###
74 * Ez_rpc_client + Ez_rpc_server automatically set up a `kj::EventLoop` and make it current for the
75 * thread. Only one KJ event-loop can exist per thread, so you cannot use these interfaces
76 * if you wish to set up your own event loop. (However, you can safely create multiple
77 * Ez_rpc_client and Ez_rpc_server objects in a single thread; they will make sure to make no more
78 * than one KJ event-loop transparently to you.)
79 *
80 * This is identical to the policies of `capnp::EzRpcClient` and `capnp::EzRpcServer`.
81 * We just wanted a similar thing available, with Flow-IPC zero-copy (perf benefit) and
82 * ipc::session::App niceties (safety, portability benefit).
83 *
84 * @tparam Client_session_t
85 * See Client_context analogous template parameter's doc header.
86 */
87template<typename Client_session_t>
89 private boost::noncopyable
90{
91public:
92 // Types.
93
94 /// Convenience alias for template arg.
95 using Client_session_obj = Client_session_t;
96
97 /// Convenience alias for relevant Client_context type.
99
100 // Constructors/destructor.
101
102 /**
103 * Constructs us by immediately (and non-blockingly, synchronously) establishing the capnp-RPC session.
104 * E.g., obtain the bootstrap interface handle via get_main() immediately on return from this ctor.
105 *
106 * @warning `cli_app_ref` and `srv_app_ref` must remain alive past this ctor: their *addresses*
107 * are stored and accessed at various points later. As usual, unless null, same for `*logger_ptr`.
108 * If `*this` is the only Ez_rpc_client *or* Ez_rpc_server operating at all times through its life
109 * (which is typical but not mandatory), then those objects need only stay alive until `*this` is gone.
110 * @warning Otherwise, in more exotic setups: Those objects (`cli_app_ref`, etc.) must stay alive until `*this`'s
111 * auto-created KJ-loop is gone. That KJ-loop shall disappear no later the last `Ez_rpc_*` object
112 * to have been destroyed, such that its lifetime overlapped `*this`'s. Thus you should keep
113 * those objects alive past lifetimes of `*this` and of any this-thread-sharing `Ez_rpc_*` object
114 * that overlapped with `*this` chronologically.
115 *
116 * @internal
117 *
118 * The source of that stuff about lifetimes past `*this` is explained in Session_vat_network "Object lifetimes"
119 * section. We maintain our own auto-created KJ-loop, and the "Object lifetimes"-recommended
120 * "Guaranteed/easy way" to keep stuff alive (<=> keep them alive past KJ-loop) is great for us... but that
121 * KJ-loop is shared with any concurrent `Ez_rpc_*` guys (Ez_rpc_kj_io mechanism).
122 *
123 * @endinternal
124 *
125 * ### Error conditions ###
126 * This may throw; in fact it will throw, if it is unable to establish a session -- most likely because
127 * no session-server is up at the moment. In particular see
128 * Client_context::sync_connect() if interested in specific possible errors (#Error_code values).
129 *
130 * @param logger_ptr
131 * Logger to use for logging subsequently. (You may use null to forego this completely.)
132 * @param cli_app_ref
133 * Properties of this client application. The address is copied; the object is not copied.
134 * @param srv_app_ref
135 * Properties of the opposing server application. The address is copied; the object is not copied.
136 * @param enable_hndl_transport
137 * `true` means native handles (a/k/a capabilities in capnp-RPC parlance) can
138 * be RPC-transmitted (i.e., your interface impls and clients can use `.getFd()` and yield something
139 * as opposed to nothing). `false` means the opposite; in particular any native-handles arriving via
140 * capnp-RPC shall be disregarded.
141 * If you do not use `.getFd()` feature of the capnp-RPC system, it is best to set this to `false`,
142 * it is said, for safety and possibly even security.
143 * @param sans_shm_transport
144 * If `false` (default) then zero-copy transport (internally achieved by using SHM) shall be in effect.
145 * If `true` this shall instead regress to act identically to `capnp::EzRpcClient` in this regard.
146 * This may be useful for benchmarking/debugging/profiling, or if one has some other compelling
147 * (perf or otherwise) reason to do vanilla non-zero-copy IPC underneath.
148 * The opposing Ez_rpc_server, or equivalent, must be configured to use the same mode as chosen
149 * here, when a connection from session::Client_app `cli_app_ref` comes-in.
150 */
151 explicit Ez_rpc_client(flow::log::Logger* logger_ptr,
152 const session::Client_app& cli_app_ref, const session::Server_app& srv_app_ref,
153 bool enable_hndl_transport = true,
154 bool sans_shm_transport = false);
155
156 // Methods.
157
158 /**
159 * Obtain bootstrap interface handle (per other get_main() overload) type-interpreted as implementing
160 * the given capnp-`interface`. This is the entry-point to capnp-RPC work from the session-client
161 * end. On the opposing side one thus provides the impl.
162 *
163 * @note We have omitted the deprecated feature of `EzRpcClient` involving multiple interfaces being exported by
164 * name (`.importCap()`, etc.). Only the encouraged pattern of using a single bootstrap interface
165 * is available; to wit through get_main().
166 *
167 * ### Possible hidden mess-over ###
168 * Each call issues a fresh bootstrap request and yields a distinct capability handle (a promise-capability
169 * until its first call's response resolves it). Therefore recommend calling this once and keeping/copying
170 * the returned handle rather than calling `get_main()` per use. This is only a
171 * small inefficiency for ordinary calls; but for `-> stream` calls it matters: The flow-control rate
172 * (see Session_vat_network::streaming_flow_window_ki()) is accounted per capability handle, so streaming
173 * over handles from separate get_main() calls means separately accounted rates, not one, resulting in
174 * potentially inappropriate flow control.
175 *
176 * This applies to `capnp::EzRpcClient::getMain()` as well incidentally.
177 *
178 * @tparam Type
179 * For example see our class doc header.
180 * @return See above.
181 */
182 template<typename Type>
183 typename Type::Client get_main();
184
185 /**
186 * Obtain bootstrap interface handle. This is the entry-point to capnp-RPC work from the session-client
187 * end. On the opposing side one thus provides the impl.
188 *
189 * @see other overload that casts it to a particular type.
190 *
191 * ### Possible hidden mess-over ###
192 * Please see the other overload's doc header.
193 *
194 * @return See above.
195 */
196 ::capnp::Capability::Client get_main();
197
198 /**
199 * Returns reference to immutable core Client_context.
200 * @return See above.
201 */
202 const Client_context_obj& rpc_context() const;
203
204 /**
205 * Returns pointer to mutable core Client_context. `*this` is intended as a simple-to-use default, so
206 * anything beyond that -- e.g., knobs available through `rpc_context()->vat_network()` -- is accessible
207 * via the core object itself here.
208 *
209 * @return See above.
210 */
212
213 /**
214 * Get the wait-scope for this thread's KJ event-loop which allows one to synchronously wait on promises.
215 * @return See above.
216 */
217 kj::WaitScope* get_wait_scope();
218
219 /**
220 * Get the underlying `AsyncIoProvider` set up for this thread. This is useful, if you want
221 * to do some non-RPC I/O in asynchronous fashion.
222 * @return See above.
223 */
224 kj::AsyncIoProvider* get_io_provider();
225
226 /**
227 * Get the underlying `LowLevelAsyncIoProvider` set up for this thread. This is useful, if you want
228 * to do some less-portable non-RPC I/O in asynchronous fashion.
229 * @return See above.
230 */
231 kj::LowLevelAsyncIoProvider* get_low_level_io_provider();
232
233private:
234 // Data.
235
236 /// Handle to this thread's KJ event-loop (shared between all other Ez_rpc_client and Ez_rpc_server instances).
237 Ez_rpc_kj_io::Ptr m_this_thread_kj_io;
238
239 /// This really does the vast majority of the work, doing Client_context::sync_connect() right in ctor.
241
242 /// Conventionally-client-side #Rpc_system created using the zero-copy `VatNetwork` inside #m_rpc_ctx.
243 std::optional<Rpc_system> m_rpc_sys;
244}; // class Ez_rpc_client
245
246/**
247 * Much like `capnp::EzRpcServer` but uses Flow-IPC zero-copy machinery. That, at its core, uses Session_vat_network
248 * instead of `capnp::TwoPartyVatNetwork`.
249 *
250 * @see ipc::transport::struc::shm::rpc namespace doc header for an overview of Flow-IPC capnp-RPC support, including
251 * how the present class relates to the others there.
252 *
253 * Please grok the simpler Ez_rpc_client doc header first; then come back here.
254 *
255 * Looking at it from the angle of the rest of the present sub-namespace ipc::transport::struc::shm::rpc:
256 * This, really, is a fairly thin layer around Context_server, though it does add a significant (albeit simple) piece
257 * of logic: it self-perpetuates the accept loop, accepting and automatically serving any incoming connections
258 * from client. Therefore it is ready to go from the start; simply perform an infinite wait off `.get_wait_scope()`.
259 *
260 * Do note that, since we might potentially be accepting sessions with 2+ different applications, as described
261 * by 2+ session::Client_app names in session::Server_app::m_allowed_client_apps (given to ctor),
262 * certain key decisions can be made, for each budding capnp-RPC session, based on *which* app is the one connecting.
263 * In particular the `main_interface_func` arg to our ctor is how one provides a decider of the
264 * `capnp::Capability::Client` bootstrap capnp-`interface` impl: `main_interface_func(the_client_app)` shall
265 * return the appropriate impl.
266 *
267 * On this side a simple use of Ez_rpc_server might look like the following (please see the client snippet in
268 * Ez_rpc_client doc header). (Here we assume only a single interface impl regardless of which app connects.)
269 *
270 * ~~~
271 * // # .capnp schema:
272 * // interface Adder { add @0 (left :Int32, right :Int32) -> (value :Int32); }
273 * // ...
274 * class AdderImpl : public Adder::Server
275 * {
276 * public:
277 * kj::Promise<void> add(AddContext context) override
278 * {
279 * auto params = context.getParams();
280 * context.getResults().setValue(params.getLeft() + params.getRight());
281 * return kj::READY_NOW;
282 * }
283 * };
284 * // ...
285 * ipc::transport::struc::shm::rpc::
286 * Ez_rpc_server<Server> server{nullptr,
287 * [](auto&&...) -> auto { return kj::heap<AdderImpl>(); },
288 * SRV_APPS.find(SRV_NAME)->second, // Our App::m_name is SRV_NAME.
289 * CLI_APPS}; // All client `App`s described.
290 * kj::NEVER_DONE.wait(*(server.get_wait_scope())); // Service anyone who connects, forever.
291 * ~~~
292 *
293 * @see Ez_rpc_client
294 * @see Context_server for a somewhat lower-layer access point into similar functionality as well as the ability
295 * to craft a custom accept loop (e.g., some version of what a `*this` does; or one that processes one concurrent
296 * session, repeatedly; or one that processes one session and then exits).
297 *
298 * @tparam Session_server_t
299 * See Client_context analogous template parameter's doc header.
300 */
301template<typename Session_server_t>
303 public flow::log::Log_context,
304 public kj::TaskSet::ErrorHandler,
305 private boost::noncopyable
306{
307public:
308 // Types.
309
310 /// Convenience alias for template arg.
311 using Session_server_obj = Session_server_t;
312
313 /// Convenience alias for relevant Context_server type.
315
316 // Constructors/destructor.
317
318 /**
319 * Constructs us by immediately beginning background listen. In order to kick off actual (automatic) handling
320 * please do: `kj::NEVER_DONE.wait(*(S.get_wait_scope()))`, where `S` is `*this`.
321 *
322 * This ctor variant shall attempt to do zero-copy transport for each incoming session, period.
323 * If you'd like to do otherwise for at least some incoming sessions use the other ctor
324 * which features the relevant `transport_method_func` arg.
325 *
326 * @warning `srv_app_ref` and `cli_app_master_set_ref` -- and the `Client_app`s to which the latter
327 * (transitively) refers -- must remain alive past this ctor: their *addresses*
328 * are stored and accessed at various points later. As usual, unless null, same for `*logger_ptr`.
329 * If `*this` is the only Ez_rpc_client *or* Ez_rpc_server operating at all times through its life
330 * (which is typical but not mandatory), then those objects need only stay alive until `*this` is gone.
331 * @warning Otherwise, in more exotic setups: Those objects (`srv_app_ref`, etc.) must stay alive until `*this`'s
332 * auto-created KJ-loop is gone. That KJ-loop shall disappear no later the last `Ez_rpc_*` object
333 * to have been destroyed, such that its lifetime overlapped `*this`'s. Thus you should keep
334 * those objects alive past lifetimes of `*this` and of any this-thread-sharing `Ez_rpc_*` object
335 * that overlapped with `*this` chronologically.
336 *
337 * @internal
338 *
339 * The source of that stuff about lifetimes past `*this` is explained in Session_vat_network "Object lifetimes"
340 * section. We maintain our own auto-created KJ-loop, and the "Object lifetimes"-recommended
341 * "Guaranteed/easy way" to keep stuff alive (<=> keep them alive past KJ-loop) is great for us... but that
342 * KJ-loop is shared with any concurrent `Ez_rpc_*` guys (Ez_rpc_kj_io mechanism).
343 *
344 * @endinternal
345 *
346 * ### Error conditions ###
347 * This may throw; in fact it will throw, if it is unable to establish a session-server.
348 * In particular see Context_server ctor doc header if interested in specific possible errors (#Error_code values).
349 *
350 * @tparam Main_interface_func
351 * Function type matching signature
352 * `capnp::Capability::Client F(const session::Client_app& app)`, where
353 * `app` identifies the opposing application.
354 * @param logger_ptr
355 * Logger to use for logging subsequently. (You may use null to forego this completely.)
356 * @param main_interface_func
357 * Invoked from the KJ event loop thread during each session's setup, the returned
358 * `capnp::Capability::Client` shall be used as the bootstrap capnp-`interface` impl accessible by
359 * the opposing client.
360 * @param srv_app_ref
361 * Properties of this server application. The address is copied; the object is not copied.
362 * Among other things this lists identification info about which opposing applications are allowed to
363 * speak with us.
364 * @param cli_app_master_set_ref
365 * The set of all known `Client_app`s. The address is copied; the object is not copied.
366 * Technically, from our PoV, it need only list the `Client_app`s whose names are
367 * in `srv_app_ref.m_allowed_client_apps`. Refer to session::App doc header for best practices on
368 * maintaining this master list.
369 * @param enable_hndl_transport
370 * See Ez_rpc_client ctor.
371 */
372 template<typename Main_interface_func>
373 explicit Ez_rpc_server(flow::log::Logger* logger_ptr, Main_interface_func&& main_interface_func,
374 const session::Server_app& srv_app_ref,
375 const session::Client_app::Master_set& cli_app_master_set_ref,
376 bool enable_hndl_transport = true);
377
378 /**
379 * Identical to the other constructor but allows one to decide, for each given budding capnp-RPC session,
380 * whether to disable zero-copy transport. That is, if it is so decided, underlying messages
381 * are sent in the mode compatible with vanilla capnp-RPC.
382 * In order for this to occur, some knobs must be set on each side: on this side, in this call.
383 * See `transport_method_func` arg below.
384 *
385 * Starting with the former:
386 * - Asynchronously, in the KJ event loop thread, during the session-establishment process,
387 * Flow-IPC shall call your function `transport_method_func()`.
388 * - It will give it 1 basic piece of info that might be useful in making the decision.
389 * - It shall expect the function to return `true` to, indeed, disable zero-copy transport; or `false`
390 * to proceed with zero-copy (as the other ctor would cause to happen always).
391 * - Then it will do so (for that particular session).
392 *
393 * @warning regarding `srv_app_ref`, `cli_app_master_set_ref` lifetime:
394 * See warning in other ctor's doc header.
395 *
396 * @tparam Transport_method_func
397 * Function type matching signature
398 * `bool F(const session::Client_app& app)`, where `app` identifies the opposing application.
399 * @tparam Main_interface_func
400 * See other ctor.
401 * @param logger_ptr
402 * See other ctor.
403 * @param main_interface_func
404 * See other ctor.
405 * @param srv_app_ref
406 * See other ctor.
407 * @param cli_app_master_set_ref
408 * See other ctor.
409 * @param enable_hndl_transport
410 * See other ctor.
411 * @param transport_method_func
412 * Invoked in the KJ event loop thread, the returned `bool` shall determine whether to use zero-copy
413 * transmission (internally using SHM) or not: `false` and `true` respectively.
414 * The opposing Ez_rpc_client ctor must have had arg `sans_shm_transport` assigned the same value
415 * (or equivalent logic, if you're using something other than Ez_rpc_client).
416 */
417 template<typename Main_interface_func, typename Transport_method_func>
418 explicit Ez_rpc_server(flow::log::Logger* logger_ptr, Main_interface_func&& main_interface_func,
419 const session::Server_app& srv_app_ref,
420 const session::Client_app::Master_set& cli_app_master_set_ref,
421 bool enable_hndl_transport,
422 Transport_method_func&& transport_method_func);
423
424 // Methods.
425
426 /**
427 * Get the wait-scope for this thread's KJ event-loop which allows one to synchronously wait on promises.
428 * @return See above.
429 */
430 kj::WaitScope* get_wait_scope();
431
432 /**
433 * Returns reference to immutable core Context_server.
434 * @return See above.
435 */
436 const Context_server_obj& context_server() const;
437
438 /**
439 * Returns pointer to mutable core Context_server. `*this` is intended as a simple-to-use default, so
440 * anything beyond that -- e.g., Context_server::streaming_flow_window_default_ki() which applies to each
441 * capnp-RPC session we subsequently accept -- is accessible via the core object itself here.
442 *
443 * Note that the KJ event loop does not turn until you wait on it (e.g., `kj::NEVER_DONE.wait()`), so
444 * anything set through here immediately after construction shall apply to every session accepted.
445 *
446 * @return See above.
447 */
449
450 /**
451 * Get the underlying `AsyncIoProvider` set up for this thread. This is useful, if you want
452 * to do some non-RPC I/O in asynchronous fashion.
453 * @return See above.
454 */
455 kj::AsyncIoProvider* get_io_provider();
456
457 /**
458 * Get the underlying `LowLevelAsyncIoProvider` set up for this thread. This is useful, if you want
459 * to do some less-portable non-RPC I/O in asynchronous fashion.
460 * @return See above.
461 */
462 kj::LowLevelAsyncIoProvider* get_low_level_io_provider();
463
464 /**
465 * Implements `kj::TaskSet::ErrorHandler` API. In our case aborts. Do not call directly (as a user).
466 *
467 * @param exc
468 * See `kj::TaskSet`.
469 */
470 void taskFailed(kj::Exception&& exc) override;
471
472private:
473 // Methods.
474
475 /**
476 * The asynchronously-infinitely-self-perpetuating loop that accepts and then services any
477 * incoming session-open requests. Automatically each one is bootstrapped according to #m_transport_method_func
478 * which came from the user; so the user's further input is not necessary.
479 *
480 * @param enable_hndl_transport
481 * See ctor.
482 */
483 void accept_loop(bool enable_hndl_transport);
484
485 // Data.
486
487 /// The computer of the bootstrap capnp-`interface` depending on which `Client_app` is connecting.
488 flow::Function<::capnp::Capability::Client (const session::Client_app&)> m_main_interface_func;
489
490 /**
491 * The decider of whether to use zero-copy transport (`false`) or not (`true`) depending on which `Client_app`
492 * is connecting.
493 */
495
496 /// Handle to this thread's KJ event-loop (shared between all other Ez_rpc_client and Ez_rpc_server instances).
497 Ez_rpc_kj_io::Ptr m_this_thread_kj_io;
498
499 /// The big daddy that produces `Server_context`s as clients connect to us.
501
502 /// Similar to Context_server::m_tasks.
503 kj::TaskSet m_tasks;
504}; // class Ez_rpc_server
505
506// Template implementations.
507
508// Ez_rpc_client template implementations.
509
510template<typename Client_session_t>
512 const session::Client_app& cli_app_ref,
513 const session::Server_app& srv_app_ref,
514 bool enable_hndl_transport, bool sans_shm_transport) :
515 m_this_thread_kj_io(Ez_rpc_kj_io::this_thread_obj()),
516
517 // We are so simple, that we don't even keep our own Log_context -- or log -- just let m_rpc_ctx do it.
518 m_rpc_ctx(logger_ptr, &m_this_thread_kj_io->m_kj_io, cli_app_ref, srv_app_ref, enable_hndl_transport)
519{
520 using ::capnp::makeRpcClient;
521
522 if (sans_shm_transport)
523 {
525 }
526 else
527 {
529 }
530 m_rpc_sys.emplace(makeRpcClient(*(m_rpc_ctx.vat_network())));
531}
532
533template<typename Client_session_t>
534template<typename Type>
536{
537 return get_main().template castAs<Type>();
538}
539
540template<typename Client_session_t>
541::capnp::Capability::Client Ez_rpc_client<Client_session_t>::get_main()
542{
543 /* This code in EzRpcClient was curiously optimized; I think maybe get_main() might be called frequently.
544 * So we left that in, even if the specifics are a tiny bit different (more Flow-ish and Flow-IPC-ish).
545 *
546 * (Incidentally per our own doc header, in the context of streaming (at least) it is best not to call this
547 * frequently but rather save a handle.) */
548
549 using util::Blob_mutable;
550 using Word = ::capnp::word;
551 using boost::array;
552
553 // This magic number used for a tiny optimization is stolen from TwoPartyVatNetwork insides.
554 constexpr size_t VAT_ID_SZ_WORDS = 4;
555
556 array<Word, VAT_ID_SZ_WORDS> scratch;
557 Capped_sz_capnp_message_builder message{Blob_mutable{scratch.data(), VAT_ID_SZ_WORDS * sizeof(Word)},
558 true}; // Gotta zero it for capnp; array<> lacks ctor and does not.
559 auto host_id = message.getRoot<Vat_id>();
560 host_id.setSide(::capnp::rpc::twoparty::Side::SERVER);
561
562 return m_rpc_sys->bootstrap(host_id);
563}
564
565template<typename Client_session_t>
567{
568 return &m_this_thread_kj_io->m_kj_io.waitScope;
569}
570
571template<typename Client_session_t>
573{
574 return m_this_thread_kj_io->m_kj_io.provider;
575}
576
577template<typename Client_session_t>
579{
580 return m_this_thread_kj_io->m_kj_io.lowLevelProvider;
581}
582
583template<typename Client_session_t>
585{
586 return m_rpc_ctx;
587}
588
589template<typename Client_session_t>
591{
592 return &m_rpc_ctx;
593}
594
595template<typename Client_session_t>
596std::ostream& operator<<(std::ostream& os, const Ez_rpc_client<Client_session_t>& val)
597{
598 return os << "[cli_ctx[" << val.rpc_context() << "]]@" << &val;
599}
600
601// Ez_rpc_server template implementations.
602
603template<typename Session_server_t>
604template<typename Main_interface_func>
605Ez_rpc_server<Session_server_t>::Ez_rpc_server(flow::log::Logger* logger_ptr, Main_interface_func&& main_interface_func,
606 const session::Server_app& srv_app_ref,
607 const session::Client_app::Master_set& cli_app_master_set_ref,
608 bool enable_hndl_transport) :
609 Ez_rpc_server(logger_ptr, std::move(main_interface_func), srv_app_ref, cli_app_master_set_ref,
610 enable_hndl_transport,
611 [](auto&&...) { return false; })
612{
613 // Yay.
614}
615
616template<typename Session_server_t>
617template<typename Main_interface_func, typename Transport_method_func>
618Ez_rpc_server<Session_server_t>::Ez_rpc_server(flow::log::Logger* logger_ptr, Main_interface_func&& main_interface_func,
619 const session::Server_app& srv_app_ref,
620 const session::Client_app::Master_set& cli_app_master_set_ref,
621 bool enable_hndl_transport,
622 Transport_method_func&& transport_method_func) :
623 flow::log::Log_context(logger_ptr, Log_component::S_RPC),
624 m_main_interface_func(std::move(main_interface_func)),
625 m_transport_method_func(std::move(transport_method_func)),
626 m_this_thread_kj_io(Ez_rpc_kj_io::this_thread_obj()),
627 m_ctx_srv(get_logger(), &m_this_thread_kj_io->m_kj_io, srv_app_ref, cli_app_master_set_ref),
628 m_tasks(*this)
629{
630 FLOW_LOG_INFO("Ez_rpc_srv [" << *this << "]: Started Ez_rpc_server. Will listen for/service concurrent clients.");
631
632 accept_loop(enable_hndl_transport);
633}
634
635template<typename Session_server_t>
636void Ez_rpc_server<Session_server_t>::accept_loop(bool enable_hndl_transport)
637{
638 using ::capnp::makeRpcServer;
639 using kj::heapString;
640 using Transport_method_func = decltype(m_transport_method_func);
641 using Server_context_obj = typename Context_server_obj::Server_context_obj;
642 using Server_context_ptr = typename Server_context_obj::Ptr;
643
644 // This is just so we can do the .attach() thing below. The attached thing just must be movable (which u_ptr is).
645 struct Rpc_bundle
646 {
647 Server_context_ptr m_rpc_ctx;
648 Rpc_system m_rpc_sys;
649 };
650 using Rpc_bundle_ptr = boost::movelib::unique_ptr<Rpc_bundle>;
651
652 /* Each connection is like:
653 * - start accept;
654 * - (async wait);
655 * - complete it, so it's ready for requests;
656 * - (keep servicing any requests);
657 * - detect other side disconnected, so delete the resources we have set up (the Rpc_bundle).
658 * So we do all that here, the (async) parts being done on the event loop via the KJ promise system. */
659
660 m_tasks.add(m_ctx_srv.accept(enable_hndl_transport,
661 [](auto&&...) { return 0; },
662 Transport_method_func(m_transport_method_func)) // Copy it before move eats it.
663 .then([this, enable_hndl_transport]
664 (auto&& accept_result) mutable
665 {
666 accept_loop(enable_hndl_transport); // Go again (not synchronously; no earlier than this function returns).
667
668 /* accept_result.m_context is the Server_context -- with the session::Session and capnp::VatNetwork ready to go!
669 * For a working RPC setup just need to make the Rpc_system (capnp::makeRpcServer()) and load the bootstrap
670 * interface into that. */
671
672 const auto& cli_app = *(accept_result.m_context->session()->client_app());
673 FLOW_LOG_INFO("Ez_rpc_srv [" << *this << "]: Accepted session from client (app [" << cli_app << "]); "
674 "creating RPC-system; and resuming listening for more.");
675
676 auto& network = *(accept_result.m_context->vat_network());
677 auto main_interface = m_main_interface_func(cli_app);
678 Rpc_bundle_ptr rpc_bundle{new Rpc_bundle{ std::move(accept_result.m_context),
679 makeRpcServer(network, std::move(main_interface)) }};
680
681 // That'll all just work by itself now; so just need to set up the last bullet above (delete on disconnect).
682
683 /* Arrange to destroy the Rpc_bundle (the 2 things in there), when all references are gone, or when
684 * `*this` is destroyed (which will destroy the TaskSet m_tasks). */
685 m_tasks.add(rpc_bundle->m_rpc_ctx->vat_network()->on_disconnect().attach(std::move(rpc_bundle)));
686 },
687 [this, enable_hndl_transport](auto&& kj_exception) mutable
688 {
689 /* This is somewhat unusual, in that we are (1) explicitly handling the rejection of the
690 * m_ctx_srv.accept()-returned promise (meaning, the accept procedure started but failed), instead
691 * of just letting it throw and thus usually stopping the whole loop; and (2) we do not re-throw it
692 * but just log. Why? Answer: See Server_context::accept() doc header "Error conditions" section.
693 * In short this is simply not generally fatal for anything but this connect attempt itself; so if
694 * we're gonna do something by default it should be log and proceed normally.
695 * @todo Consider making this configurable. Though note they can always control it themselves by
696 * making a custom loop using Server_context and their own promise chains; Ez_rpc_server's conceit is
697 * to be a simple-to-use default, almost (hopefully not quite) like a demo. */
698
699 accept_loop(enable_hndl_transport);
700
701 FLOW_LOG_WARNING("Ez_rpc_srv [" << *this << "]: Session-connect started but failed with error; ignoring and "
702 "resuming listening; error was: [" << heapString(kj_exception.getDescription()).cStr() << "].");
703 }));
704} // Ez_rpc_server::accept_loop()
705
706template<typename Session_server_t>
708{
709 kj::throwFatalException(std::move(exc));
710}
711
712template<typename Session_server_t>
714{
715 return &m_this_thread_kj_io->m_kj_io.waitScope;
716}
717
718template<typename Session_server_t>
720{
721 return m_this_thread_kj_io->m_kj_io.provider;
722}
723
724template<typename Session_server_t>
726{
727 return m_this_thread_kj_io->m_kj_io.lowLevelProvider;
728}
729
730template<typename Session_server_t>
733{
734 return m_ctx_srv;
735}
736
737template<typename Session_server_t>
739{
740 return &m_ctx_srv;
741}
742
743template<typename Session_server_t>
744std::ostream& operator<<(std::ostream& os, const Ez_rpc_server<Session_server_t>& val)
745{
746 return os << "[ctx_srv[" << val.context_server() << "]]@" << &val;
747}
748
749} // namespace ipc::transport::struc::shm::rpc
750
751/* Addendum: The source code in this file is based on a small portion of Cap'n Proto,
752 * version 1.0.2, namely parts of the files `ez-rpc.h` and `ez-rpc.c++`. There are
753 * substantial differences, nor was there a way to reuse those works in black-box
754 * fashion, but the inspiration and some of the execution is rooted in those files.
755 * This is the highest layer of capnp-RPC integration we provide, and it is optional
756 * to use; the core of the work is in lower layers which are original work; e.g.,
757 * Session_vat_network. The license header from the Cap'n Proto source files follows. */
758
759// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
760// Licensed under the MIT License:
761//
762// Permission is hereby granted, free of charge, to any person obtaining a copy
763// of this software and associated documentation files (the "Software"), to deal
764// in the Software without restriction, including without limitation the rights
765// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
766// copies of the Software, and to permit persons to whom the Software is
767// furnished to do so, subject to the following conditions:
768//
769// The above copyright notice and this permission notice shall be included in
770// all copies or substantial portions of the Software.
771//
772// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
773// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
774// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
775// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
776// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
777// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
778// THE SOFTWARE.
Simple utility: a capnp MessageBuilder operating over a single, contiguous, user-supplied area in vir...
Definition: msg.hpp:68
bool sync_connect(typename Session_obj::Channels *init_channels_by_cli_req_pre_sized=nullptr, typename Session_obj::Channels *init_channels_by_srv_req=nullptr)
To be invoked successfully at most once, it synchronously and non-blockingly attempts to connect to a...
bool sync_connect_sans_shm_transport(typename Session_obj::Channels *init_channels_by_cli_req_pre_sized=nullptr, typename Session_obj::Channels *init_channels_by_srv_req=nullptr)
Identical to sync_connect() with same signature but disables zero-copy transport.
Vat_network * vat_network()
Returns pointer to Session_vat_network established for use in your rpc::Rpc_system; or null if no syn...
Much like capnp::EzRpcClient but uses Flow-IPC zero-copy machinery.
Definition: ez_rpc.hpp:90
Client_session_t Client_session_obj
Convenience alias for template arg.
Definition: ez_rpc.hpp:95
Ez_rpc_client(flow::log::Logger *logger_ptr, const session::Client_app &cli_app_ref, const session::Server_app &srv_app_ref, bool enable_hndl_transport=true, bool sans_shm_transport=false)
Constructs us by immediately (and non-blockingly, synchronously) establishing the capnp-RPC session.
Definition: ez_rpc.hpp:511
Ez_rpc_kj_io::Ptr m_this_thread_kj_io
Handle to this thread's KJ event-loop (shared between all other Ez_rpc_client and Ez_rpc_server insta...
Definition: ez_rpc.hpp:237
const Client_context_obj & rpc_context() const
Returns reference to immutable core Client_context.
Definition: ez_rpc.hpp:584
kj::AsyncIoProvider * get_io_provider()
Get the underlying AsyncIoProvider set up for this thread.
Definition: ez_rpc.hpp:572
kj::LowLevelAsyncIoProvider * get_low_level_io_provider()
Get the underlying LowLevelAsyncIoProvider set up for this thread.
Definition: ez_rpc.hpp:578
Client_context_obj m_rpc_ctx
This really does the vast majority of the work, doing Client_context::sync_connect() right in ctor.
Definition: ez_rpc.hpp:240
std::optional< Rpc_system > m_rpc_sys
Conventionally-client-side Rpc_system created using the zero-copy VatNetwork inside m_rpc_ctx.
Definition: ez_rpc.hpp:243
Type::Client get_main()
Obtain bootstrap interface handle (per other get_main() overload) type-interpreted as implementing th...
Definition: ez_rpc.hpp:535
kj::WaitScope * get_wait_scope()
Get the wait-scope for this thread's KJ event-loop which allows one to synchronously wait on promises...
Definition: ez_rpc.hpp:566
Internal-use thread-local singleton-style access to a kj::AsyncIoContext which, for each given thread...
Much like capnp::EzRpcServer but uses Flow-IPC zero-copy machinery.
Definition: ez_rpc.hpp:306
Ez_rpc_server(flow::log::Logger *logger_ptr, Main_interface_func &&main_interface_func, const session::Server_app &srv_app_ref, const session::Client_app::Master_set &cli_app_master_set_ref, bool enable_hndl_transport=true)
Constructs us by immediately beginning background listen.
Definition: ez_rpc.hpp:605
kj::TaskSet m_tasks
Similar to Context_server::m_tasks.
Definition: ez_rpc.hpp:503
Context_server_obj m_ctx_srv
The big daddy that produces Server_contexts as clients connect to us.
Definition: ez_rpc.hpp:500
Session_server_t Session_server_obj
Convenience alias for template arg.
Definition: ez_rpc.hpp:311
const Context_server_obj & context_server() const
Returns reference to immutable core Context_server.
Definition: ez_rpc.hpp:732
kj::LowLevelAsyncIoProvider * get_low_level_io_provider()
Get the underlying LowLevelAsyncIoProvider set up for this thread.
Definition: ez_rpc.hpp:725
void taskFailed(kj::Exception &&exc) override
Implements kj::TaskSet::ErrorHandler API.
Definition: ez_rpc.hpp:707
void accept_loop(bool enable_hndl_transport)
The asynchronously-infinitely-self-perpetuating loop that accepts and then services any incoming sess...
Definition: ez_rpc.hpp:636
flow::Function< bool(const session::Client_app &)> m_transport_method_func
The decider of whether to use zero-copy transport (false) or not (true) depending on which Client_app...
Definition: ez_rpc.hpp:494
kj::WaitScope * get_wait_scope()
Get the wait-scope for this thread's KJ event-loop which allows one to synchronously wait on promises...
Definition: ez_rpc.hpp:713
kj::AsyncIoProvider * get_io_provider()
Get the underlying AsyncIoProvider set up for this thread.
Definition: ez_rpc.hpp:719
flow::Function<::capnp::Capability::Client(const session::Client_app &)> m_main_interface_func
The computer of the bootstrap capnp-interface depending on which Client_app is connecting.
Definition: ez_rpc.hpp:488
Ez_rpc_kj_io::Ptr m_this_thread_kj_io
Handle to this thread's KJ event-loop (shared between all other Ez_rpc_client and Ez_rpc_server insta...
Definition: ez_rpc.hpp:497
The mirror of an opposing PEER-state Client_context, as generated via successful Context_server::acce...
Provides Flow-IPC's integration with Cap'n Proto's Remote Procedure Call (RPC) layer,...
::capnp::rpc::twoparty::VatId Vat_id
Flow-IPC-styled alias for the node ID in a Session_vat_network or TwoPartyVatNetwork: namely an enume...
std::ostream & operator<<(std::ostream &os, const Client_context< Client_session_t > &val)
Prints string representation of the given Client_context to the given ostream.
::capnp::RpcSystem< Vat_id > Rpc_system
Concrete capnp::RpcSystem to be used with Session_vat_network or TwoPartyVatNetwork.
boost::asio::mutable_buffer Blob_mutable
Short-hand for a mutable blob somewhere in memory, stored as exactly a void* and a size_t.
Definition: util_fwd.hpp:137
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
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
boost::unordered_map< std::string, Client_app > Master_set
Type for storing master repository of all Client_apps. See App doc header for discussion.
Definition: app.hpp:227
An App that is used as a server in at least one client-server IPC split.
Definition: app.hpp:241