Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
session_vat_network.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/// @file
19#pragma once
20
24#include "ipc/transport/struc/shm/schema/detail/serialization.capnp.h"
27#include <boost/move/unique_ptr.hpp>
28#include <boost/shared_ptr.hpp>
29#include <capnp/any.h>
30#include <capnp/rpc-twoparty.h>
31#include <capnp/rpc.h>
32#include <capnp/rpc.capnp.h>
33#include <flow/log/log.hpp>
34#include <optional>
35
37{
38
39// Types.
40
41// rpc_fwd.hpp comment explains why these aliases are here and not, as is otherwise typical, there.
42
43/**
44 * Flow-IPC-styled alias for Session_vat_network and `TwoPartyVatNetwork`'s "connection," which is
45 * an interface for essentially an RPC-oriented stream of capnp messages -- a relatively thin layer on
46 * top of the `capnp::MessageStream` interface. The latter is a general stream of capnp messages.
47 * Incidentally `capnp::MessageStream` is something like capnp's version of our transport::struc::Channel.
48 *
49 * It is unlikely the typical RPC user needs to work with this; but if one extends the RPC system (such as
50 * we did with Session_vat_network et al), this might be helpful.
51 */
52using Rpc_conn = ::capnp::TwoPartyVatNetworkBase::Connection;
53
54/**
55 * Flow-IPC-styled alias for an out-message sent by an #Rpc_conn.
56 *
57 * It is unlikely the typical RPC user needs to work with this; but if one extends the RPC system (such as
58 * we did with Session_vat_network et al), this might be helpful.
59 */
60using Rpc_msg_out = ::capnp::OutgoingRpcMessage;
61
62/**
63 * Flow-IPC-styled alias for an in-message received by an #Rpc_conn.
64 *
65 * It is unlikely the typical RPC user needs to work with this; but if one extends the RPC system (such as
66 * we did with Session_vat_network et al), this might be helpful.
67 */
68using Rpc_msg_in = ::capnp::IncomingRpcMessage;
69
70/**
71 * Flow-IPC-styled alias for the node ID in a Session_vat_network or `TwoPartyVatNetwork`: namely an enumeration
72 * containing `SERVER` and `CLIENT`.
73 *
74 * Incidentally we remind you: Much like in ipc::Session the server-vs-client dichotomy carries no meaning, once
75 * a session is established (is in PEER state), similarly by-and-large this ID is just a node ID. A server
76 * can do client-like things and vice versa... etc.
77 *
78 * It is unlikely the typical RPC user needs to work with this; but if one extends the RPC system (such as
79 * we did with Session_vat_network et al), this might be helpful.
80 */
81using Vat_id = ::capnp::rpc::twoparty::VatId;
82
83/// Concrete `capnp::RpcSystem` to be used with Session_vat_network or `TwoPartyVatNetwork`.
84using Rpc_system = ::capnp::RpcSystem<Vat_id>;
85
86/// Non-template base for Session_vat_network (constants, types, and what-not).
88{
89 // Constants.
90
91 /**
92 * Reasonable default for `hndl_transport_limit_or_0` arg to Session_vat_network ctors, assuming one does
93 * not want to forbid the feature by passing zero.
94 *
95 * @internal
96 *
97 * @todo Session_vat_network::S_N_MAX_INCOMING_FDS is set to `10` as a reasonable default... so is it reasonable?
98 * I (ygoldfel) just picked something that felt like it would probably be more than enough in most conceivable
99 * uses of capnp-RPC's native handle (a/k/a FD in Unix a/k/a capability in capnp-RPC parlances) passing feature:
100 * really a capnp-interface impl can only feature a single handle via `getFd()`, and one guesses that even
101 * with all the promise-pipelining ever, a single internal `rpc::Message` is unlikely to combine more than 10
102 * of these... probably? On the other hand, why not just make this, like, 250-ish? Answer: Comments in
103 * capnp suggest this whole thing is a "security" thing, but I personally lack the background as to the concerns
104 * discussed there. The to-do is to reach and document a greater level certainty about this
105 * value (and change it if appropriate).
106 */
107 static constexpr unsigned int S_N_MAX_INCOMING_FDS = 10;
108
109 /// Reasonable default for Session_vat_network::streaming_flow_window_ki(). See the doc header for the latter.
110 static constexpr size_t S_STREAMING_FLOW_WINDOW_KI = 512;
111}; // struct Session_vat_network_base
112
113/**
114 * The core class of Flow-IPC's zero-copyification-of-capnp-RPC module, `Session_vat_network` implements
115 * `capnp::VatNetwork` and can be accurately described as `capnp::TwoPartyVatNetwork` but with zero-copy
116 * performance. That is: it behaves ~identically to that stalwart of vanilla capnp-RPC (`TwoPartyVatNetwork`),
117 * but every capnp-RPC message (including ones containing user's capnp-encoded data flying back and forth)
118 * is (under the hood) allocated in SHM and is *never* copied. Hence message size ceases to be a perf
119 * consideration: a large message costs about the same as a small one. (For small messages, which are cheap to copy
120 * anyway, expect perf similar to `TwoPartyVatNetwork`.) The one hard limitation: Session_vat_network cannot work
121 * over a network link (for obvious reasons). However, API-wise, the two alternatives are used identically after
122 * construction! So to the extent that user code can be written independently of whether networking is involved,
123 * one can write generic code that will work with either `capnp::TwoPartyVatNetwork` (required for networking) or
124 * Session_vat_network (zero-copy-enabled, for local IPC). It can even be a template parameter.
125 *
126 * @see ipc::transport::struc::shm::rpc namespace doc header for an overview of Flow-IPC capnp-RPC support, including
127 * how the present class relates to the others there.
128 *
129 * @note It is also possible to have our Session_vat_network act identically to the vanilla `TwoPartyVatNetwork`,
130 * such as for easy fallback, benchmarking, and so forth. See the "Bonus!" section below for the recipe.
131 *
132 * ### How to use this to do capnp-RPC? ###
133 * If you know capnp-RPC, then you probably already know about `VatNetwork` and `TwoPartyVatNetwork` and therefore
134 * already can basically answer that question.
135 *
136 * If you do not, or are rusty, we recommend reading the introduction pages
137 * in Cap'n Proto documentation (https://capnproto.org/rpc.html, https://capnproto.org/cxxrpc.html);
138 * perusing the Calculator sample; and once a bit comfortable coming back here. We do give a
139 * short recap here, but it is no substitute for the real thing (w/r/t giving one a capnp-RPC introduction).
140 *
141 * In short:
142 * - Wherever you were using `TwoPartyVatNetwork` -- as long as it does not involve crossing a machine boundary
143 * (networking) -- simply use Session_vat_network instead (and/or parameterize on which one you want in a given
144 * scenario at compile-time). The entire API is identical:
145 * - It implements `capnp::VatNetwork` and as such will slot-in to the standard capnp-RPC setup:
146 * `VatNetwork` + `capnp::RpcSystem` + `Client` + `Capability::Client`.
147 * - The rest of the API (stuff that is not `VatNetwork` virtual impls) is very small; essentially you
148 * can get a disconnect-promise via a particular method, same as with `TwoPartyVatNetwork`.
149 * - The only difference: (next paragraph)
150 * - Constructing a Session_vat_network has a somewhat different API. It makes use of ipc::session, and we
151 * provide a KJ-promise-based-event-loop-friendly interface for creating `ipc::session::Sessions`s, so that
152 * it is natural to use in the capnp-RPC-ish style of programming.
153 * - We provide higher-layer APIs to make your life easier.
154 * (In all cases the core of things is still Session_vat_network.)
155 * - Client_context and Context_server will ease creation of the necessary `Session_vat_network`s by
156 * starting ipc::session session/server and auto-completing the procedure yielding the `VatNetwork` for
157 * each capnp-RPC conversation. However you shall then still create the #Rpc_system yourself and start and
158 * design your event loop.
159 * - Ez_rpc_client and Ez_rpc_server are higher-layer still and are exactly analogous to
160 * `capnp::EzRpcClient` and `capnp::EzRpcServer` (but zero-copy-enabled). These will start and
161 * run the entire event loop as needed as well.
162 *
163 * The `-> stream` feature (https://capnproto.org/news/#multi-stream-flow-control) has a potential impact on
164 * shared RAM (SHM) use, and possibly on performance. The way the related *flow control window* is determined
165 * differs from vanilla (non-zero-copy) capnp-RPC's impl (as described in that link).
166 * - You may control and/or observe the relevant window knob via streaming_flow_window_ki() methods
167 * (accessor, mutator).
168 *
169 * ### Bonus! You can turn this into a plain, non-zero-copy `TwoPartyVatNetwork` ###
170 * This might not blow your mind completely, but it might be nice to make use of Flow-IPC's ipc::session
171 * mechanism for the initial connection with a `TwoPartyVatNetwork` itself. It might be useful for fallback,
172 * benchmarking, debugging, profiling too.
173 *
174 * To achieve this:
175 * - Still choose some SHM-provider (perhaps the one you'd use when benchmarking *with* zero-copy; otherwise
176 * any one of them) and therefore template parameters. Easier yet use an alias like:
177 * - `"ipc::session::shm::classic::Client_session::Vat_network"` / `"Server_session::Vat_network"`;
178 * - `"ipc::session::shm::arena_lend::jemalloc::Client_session::Vat_network"` / `"Server_session::Vat_network"`.
179 * - Pass-in `shm_enabled_session = nullptr` (or, if applicable, `shm_lnd_brw = shm_arena = nullptr`) constructor
180 * arg(s). Or:
181 * - The aforementioned higher-layer APIs (Client_context + Context_server; Ez_rpc_client + Ez_rpc_server)
182 * all provide access to this mode as well. See their docs.
183 *
184 * @todo rpc::Session_vat_network and probably some of the layers built on top of it should make
185 * `capnp::ReaderOptions` configurable -- analogously to the vanilla `capnp::TwoPartyVatNetwork`. (Be sure to
186 * apply this judiciously to the correct layer of serialization; and document thoughtfully, as the practical
187 * perf implications here are different from the vanilla/non-zero-copy use case.)
188 *
189 * Object lifetimes: A caveat
190 * --------------------------
191 * This section applies if you use `*this` directly; or if you use Client_context and Context_server which
192 * is easier. The `Ez_rpc_*` APIs generally relieve you of having to worry about it.
193 *
194 * A potentially surprising fact, especially to those new to KJ and/or capnp-RPC, is that while a `VatNetwork`
195 * (a `*this` in our context) is indeed a central player in implementing a capnp-RPC system -- and indeed its
196 * lifetime (construction, destruction) is explicitly controlled by its user -- its
197 * destruction does *not* quite mean the RPCing initiated through it is all the way finished. It
198 * produces objects -- #Rpc_msg_out and #Rpc_msg_in -- per outgoing/incoming capnp-encoded
199 * message which are owned by the KJ loop (`*kj_io` to our ctor), not by `*this`. These objects
200 * need some of the key resources that a `*this VatNetwork` also needs. In other words those objects must
201 * stay alive potentially *longer* than `*this` Session_vat_network that originally accepts them via ctor.
202 * Particularly when an RPC system is shutting down, there is typically a danger zone, wherein `VatNetwork`
203 * is gone, but the messages it has produced are still briefly around. To be more exact:
204 *
205 * ### The objects of concern ###
206 * The objects whose lifetime we must carefully control, so it does not end before all outstanding message-objects
207 * are gone:
208 * - (If using `shm_enabled_session` ctor form)
209 * - The SHM-enabled session object, from ipc::session: `*shm_enabled_session`.
210 * - Objects that `*shm_enabled_session` relies on:
211 * - (if not null) `Logger*` pointee;
212 * - the IPC-universe-describing session::App `struct`s.
213 * - (If using generic ctor form)
214 * - The SHM-session object `*shm_lnd_brw`: thing with `.lend_object()` and `.borrow_object()`.
215 * - The SHM-arena object `*shm_arena`: thing with `.construct()`.
216 * - Objects that those 2 objects rely on. It depends.
217 *
218 * ### What to do ###
219 * We take either `shm_enabled_session`, or `shm_lnd_brw` + `shm_arena`, by `shared_ptr` handle in ctor.
220 * Therefore you are required to wrap the input object(s) in `shared_ptr` handle(s). `*this` and `*this`-created
221 * message-objects will hold refs to the object(s) no longer than necessary; once all message-objects and `*this`
222 * are finished, the session/SHM-session/arena can also be destroyed (from our PoV).
223 *
224 * So that covers the ctor-taken objects themselves. It does not however, necessarily, cover the
225 * "objects that ... relies/rely on" (from the bullet list above). It is your responsibility to ensure those
226 * objects stay alive past `*this` (naturally) but also the message-objects. Luckily this should be fairly
227 * easy.
228 *
229 * *Guaranteed/easy way*:
230 * Ensure the relied-upon objects (`Logger`, `App`, ...) shall outlive loop `*kj_io` given to `*this` ctor.
231 *
232 * @note that the `Logger*` mentioned here is not (necessarily) the `Logger*` given to `*this` ctor. Here
233 * we mean the `Logger*` given to `*shm_enabled_session`/`*shm_lnd_brw`/`*shm_arena`.
234 *
235 * *Tighter/more involved way*:
236 * The above is easy, but conceivably one might use `*kj_io` for something else after being done with this
237 * round of capnp-RPC; perhaps you'd prefer to not keep the logger/whatever alive at that point. In that
238 * case:
239 *
240 * Let the event loop run until it has nothing left to do, for instance by using `WaitScope::poll()`, which runs
241 * whatever is ready without blocking; once that returns, and none of your implementation methods is still
242 * executing, nothing of Flow-IPC's remains that uses your `Logger`, `App`s, etc.
243 *
244 * ### This is annoying.... ###
245 * We know. We didn't want to write the above either. Unfortunately it's an inescapable consequence of
246 * the way objects in capnp-RPC-over-KJ-loop land interact. It is not a problem for vanilla capnp-RPC simply
247 * because:
248 * - it only needs heap memory which is always available (we need SHM and SHM-sessions);
249 * - it does not do pluggable logging.
250 *
251 * Fortunately, it's easy enough to deal with, if one knows about it.
252 * - Session stuff is kept alive by `shared_ptr`: so you need not worry about that.
253 * - `session::App`s are by their nature unchanging, so they're typically always around (at low cost).
254 * - `Logger*` can be left null (parity with vanilla-capnp-RPC) if desired. Or keep it around throughout as
255 * shown above.
256 *
257 * @internal
258 *
259 * Impl notes
260 * ----------
261 * ### General approach ###
262 * The goal here is to make a `capnp::VatNetwork` interface impl with the following characteristics.
263 * - It will keep any `rpc::Message`s -- including those carrying user-generated (over the course of their capnp-RPC
264 * work) capnp-payloads -- that the subsequent #Rpc_system needs to send -- in SHM instead of stuffing
265 * them into and out of an IPC byte-stream. That is: add zero-copy to capnp-RPC over a local IPC byte-stream pipe.
266 * - It will manage timely deallocation (and allocation) of such in-SHM areas.
267 * - It will be reasonably easy to integrate with Flow-IPC facilities in the following areas:
268 * - The byte-stream (which ultimately is typically just a pre-connected Unix-domain-socket stream) can be obtained
269 * from Flow-IPC, whether using ipc::transport::Native_socket_stream, ipc::transport::Channel, or
270 * ipc::session::Session.
271 * - The SHM support can be obtained from Flow-IPC, either when direct-using SHM arenas/lenders/borrowers or
272 * support for these in ipc::session::Session variants.
273 *
274 * Additionally, a caveat: `TwoPartyVatNetwork` has a method `onDisconnect()` which is not part of any formal interface
275 * (`VatNetwork`) to implement but, it appears to us, is effectively a requirement. So we provide that too.
276 *
277 * To understand how we do this (impl-wise), we feel it is generally sufficient to (1) be familiar with capnp-RPC,
278 * (2) be familiar with Flow-IPC at a reasonably in-depth level, and lastly (but not leastly) (3) grok
279 * `capnp::TwoPartyVatNetwork` implementation at a pretty deep level (but definitely not every detail; because
280 * as of this writing we have happily been able to reuse it as a black-box data member and graft-on the necessary
281 * zero-copy aspects without having to write our own variation of `TwoPartyVatNetwork` all the way through). Oh
282 * and, naturally, read the code.
283 *
284 * Here we will summarize the key points.
285 *
286 * There is some code -- very little by volume -- that is essentially lifted-ish from `TwoPartyVatNetwork`. This
287 * includes the on_disconnect() business; the decision for a `*this` to also *be* an #Rpc_conn
288 * (a/k/a `VatNetwork::Connection`), even though in general `VatNetwork`s one could produce multiple such
289 * `Connection`s in parallel in theory; some details following from this, such as accept() yielding either
290 * an immediately-fulfilled promise or a never-fulfilled one. These details are not very interesting. It isn't
291 * broken -- we do not fix it. These parts were not reusable either but not long, so this all feels okay.
292 *
293 * The interesting aspects begin in *how* we implement #Rpc_conn. The methods of most interest are
294 * newOutgoingMessage(), receiveIncomingMessage(), and newStream(); and `Rpc_conn` aside, the set of ctors we
295 * provide.
296 *
297 * However before getting into that consider the general strategy we adopt. The basic thing is that
298 * `TwoPartyVatNetwork` is in a sense almost enough by itself. The only "small" problem is:
299 * - When RPC-system tells it there's a newOutgoingMessage() it returns an object `X` that allocates
300 * a blank capnp-message in regular heap (`MallocMessageBuilder`); and when later it calls `X.send()`,
301 * that impl essentially copies that message into an IPC byte-stream.
302 * - Accordingly, on receipt, receiveIncomingMessage() and subsequent code will deal with that --
303 * again allocate in heap; copy that serialization into that location.
304 *
305 * Obviously we need, instead, for the allocation to happen in SHM; and then to read same in SHM on receipt.
306 * Flow-IPC provides the required `capnp::MessageBuilder` and `capnp::MessageReader` impls:
307 * shm::Capnp_message_builder, shm::Capnp_message_reader. The way one would use that in classic Flow-IPC style is:
308 * allocate in SHM; serialize in SHM; read in SHM; etc.; but when *transmitting* the message:
309 * - Use Capnp_message_builder::lend() to obtain a *very small* SHM-handle;
310 * - transmit *that SHM-handle instead of the actual message* over a byte-stream;
311 * - on the other side use Capnp_message_reader::borrow(), passing it the SHM-handle's contents;
312 * - now it can access the in-SHM actual-message serialization!
313 *
314 * The key point: There is still a byte-stream involved, and we are still IPC-transmitting things over it.
315 * Just, we are transmitting very small things instead of the actual messages. So while examining TwoPartyVatNetwork
316 * we asked ourselves: Can we rig it to do that part for us, while we add the required pre-processing and
317 * post-processing to properly generate these little payloads and understand them, respectively?
318 * The answer: Yes, totally. So that's what we are doing.
319 *
320 * Now when you read the code it should make sense; the details are tactical in nature.
321 *
322 * ### The constructors ###
323 * There was some question as what set of APIs to provide there. A related topic is how to template-parameterize
324 * the class. There are various ways one can hook-up to the rest of Flow-IPC. For example one can tailor those
325 * decisions to the idea that (A) one must use ipc::session end-to-end; or (b) not require that at all and instead
326 * supply each individual moving part (SHM-arena; SHM-lender/borrower; native socket handle for the byte-stream)
327 * individually; or something in-between.
328 *
329 * In short: We chose actually (B) as the baseline; while supplying one more ctor to make (A)
330 * also equally possible; and the template parameterization supports all of that. Arguably this flexibility makes
331 * things a bit confusing due to the degrees of freedom. However: By also supplying higher-level
332 * APIs Client_context and Context_server, as well as a range of aliases and documentation, we've reached a good
333 * balance. (Ez_rpc_client and Ez_rpc_server are higher-level still.)
334 *
335 * ### Future work ###
336 * I (ygoldfel) am reasonably confident that there is not a simpler version of this that somehow reuses existing
337 * capnp-RPC features in black-boxy fashion to an even greater extent. We've really kept everything
338 * `TwoPartyVatNetwork` did when transmitting capnp-encoded bytes -- just replacing the nature and size
339 * (smaller!) of what is being encoded, and of course setting up the in-SHM serialization of the actual big payloads
340 * (from `RpcSystem` and the user's schema/code).
341 *
342 * There is however, surely, an impl that has better performance by reimplementing more of what a two-party
343 * `VatNetwork` must implement. Perhaps in-SHM messages can be batched for fewer allocations; the byte-stream
344 * perf could be tuned for the smaller payloads; it is hard to say without some serious profiling and benchmarking.
345 * We could stop relying on KJ `AsyncIoStream`s and co.; and somehow make use of our own Native_socket_stream.
346 * (Note: "Could" does not mean "should" necessarily.) The `VatNetwork` interface is flexible enough for all
347 * of that and more. The most promising approach might be the following to-do:
348 *
349 * @todo In the ipc::transport::struc::Struct_builder-centered capnp-serialization module, a very perf-critical
350 * feature would be a serializer combining struc::Heap_fixed_builder and struc::shm::Builder, in that it
351 * would inline (in the for-IPC-transmission byte stream) smaller segment(s?) while SHM-storing larger ones
352 * (as shm::Builder does for all segments currently); integrate this work with both struc::Channel and
353 * capnp-RPC (rpc::Session_vat_network et al) structured-transport systems. shm::Builder and its
354 * `MessageBuilder`-implementing core shm::Capnp_message_builder would perhaps be extended to support this new --
355 * faster! -- mode, with the ability to fallback to its current SHM-always behavior (and perhaps
356 * to `Heap_fixed_builder`'s heap-always behavior if so desired). In designing this, there are two major questions
357 * to resolve first: 1, what is the fastest-behaving solution (where is the cut-over where SHM starts winning? etc.);
358 * 2, what is the best integration point between rpc::Session_vat_network and the new serializer. (Integration
359 * with struc::Channel is already through the Struct_builder + Struct_reader concepts, here implemented as
360 * shm::Builder and shm::Reader respectively. `Session_vat_network`'s possible integration methods are more
361 * of an open question. The ticket (https://github.com/Flow-IPC/ipc/issues/160) may contain detailed ideas.)
362 *
363 * A reminder: This capnp-RPC integration is an alternative to using struc::Channel which on the one hand is far
364 * less flexible semantically (it has request/response and message-type-demuxing... but that's it) but on the other
365 * hand much more predictable in terms of payload size and contents and timing. We digress; point is, one should
366 * keep in mind the alternative mechanism when profiling/learning/changing perf characteristics of this one; and
367 * vice versa. We could learn lessons that might apply to either.
368 */
369template<typename Shm_lender_borrower_t, typename Shm_arena_t>
371 public ::capnp::TwoPartyVatNetworkBase, // A/k/a VatNetwork<...> (pure interface).
373 public Rpc_conn,
374 public flow::log::Log_context,
376 private boost::noncopyable
377{
378public:
379 // Types.
380
381 /// Short-hand for `Shm_lender_borrower_t` template parameter.
382 using Shm_lender_borrower = Shm_lender_borrower_t;
383
384 /// Short-hand for `Shm_arena_t` template parameter.
385 using Shm_arena = Shm_arena_t;
386
387 /**
388 * Shared handle to a #Shm_lender_borrower: how a ctor takes it, and how `*this` and each capnp-message it
389 * (invisibly) generates store it. See class doc header "Object lifetimes" section for rationale.
390 */
391 using Shm_lender_borrower_ptr = boost::shared_ptr<Shm_lender_borrower>;
392
393 /// Shared handle to a #Shm_arena. See #Shm_lender_borrower_ptr.
394 using Shm_arena_ptr = boost::shared_ptr<Shm_arena>;
395
396 /**
397 * Alias for our base class which incidentally is the interface `VatNetwork<...>`.
398 * Incidentally `...` is a few not-exciting types that incidentally represent `TwoPartyVatNetwork`'s -- and
399 * our -- two-party-ness. I.e., there are two parties in the "network" in which we are participating; and
400 * hence the node ID is "I am server" or "I am client" (regardless of whether either one actually acts as such).
401 */
402 using Base = ::capnp::TwoPartyVatNetworkBase;
403
404 // Constructors/destructor.
405
406 /**
407 * Constructs the `VatNetwork`, making it ready for use in an #Rpc_system: mainstream (session) form.
408 *
409 * This ctor form is the most mainstream for Flow-IPC users, meaning it assumes one desires to use
410 * Flow-IPC's session system. More exotic/advanced situations may require the use of the other ctor form.
411 * - It assumes full integration with ipc::session. #Shm_lender_borrower must be a SHM-enabled
412 * ipc::session::Session variant: either `Server_session` (then the opposing guy would use `Client_session`)
413 * or `Client_session` (opposing = `Server_session`). Thus in particular for the available SHM-providers
414 * as of this writing, that'd be one of:
415 * - (SHM-classic) ipc::session::shm::Server_session, ipc::session::shm::Client_session;
416 * - (SHM-jemalloc) ipc::session::shm::arena_lend::jemalloc::Server_session,
417 * ipc::session::shm::arena_lend::jemalloc::Client_session.
418 * - #Shm_arena must be `Shm_lender_borrower::Arena`. `shm_enabled_session->session_shm()` shall automatically
419 * be used.
420 * - `bidir_transport` is provided as a raw native handle (in Unix parlance, FD). Do note that, as with all
421 * ctors, `*this` takes ownership of the transport and is responsible for closing it. `bidir_transport`
422 * is accordingly nullified by this ctor -- immediately and deterministically, even if the ctor then
423 * throws. Do note that `bidir_transport` must refer to a bidirectional
424 * transport. Realistically that means a pre-connected Unix domain socket of stream type.
425 * - Where does one get such a handle?
426 * - With Flow-IPC (and indeed higher-layer APIs -- Client_context, Context_server, et al -- do this):
427 * One side creates a pre-connected stream-socket pair (e.g., via
428 * `asio_local_stream_socket::local_ns::connect_pair()`); keeps one end for its own ctor call like this
429 * one; and sends the other end to the opposing side -- over a `Native_handle`-bearing channel, such as
430 * an ipc::session init-channel -- for its own such ctor call.
431 * - Otherwise: Use a technique of your choice: a native Unix-domain stream socket connect/accept; or
432 * a native form of the preceding bullet (pre-connected stream socket pair, transmit one FD via native
433 * interface such as Linux et al's `sendmsg()`, `recvmsg()` with `SCM_RIGHTS`); and so on.
434 * - `hndl_transport_limit_or_0 > 0` means native handles (a/k/a capabilities in capnp-RPC parlance) can
435 * be RPC-transmitted (i.e., your interface impls and clients can use `.getFd()` and yield something
436 * as opposed to nothing). In this case `bidir_transport` *must* refer to a Unix domain socket connection.
437 * If 0 then handle/capability transmission shall not occur.
438 *
439 * To the extent capnp internals can throw: This ctor can throw `kj::Exception`.
440 *
441 * @see streaming_flow_window_ki() which provides a perf- and RAM-use-relevant knob.
442 *
443 * ### Special mode ###
444 * Use null `shm_enabled_session` to have `*this` act identically to a regular, non-zero-copy-enabled
445 * `TwoPartyVatNetwork`. This may be useful for fallback, benchmarking, debugging, profiling.
446 * streaming_flow_window_ki() has no effect in this mode.
447 *
448 * ### Object lifetimes ###
449 * `*logger_ptr` (if not null) must stay alive while `*this` exists.
450 *
451 * `*shm_enabled_session` (if not null) shall stay alive potentially past `*this` by means of our taking
452 * the `shared_ptr` handle. However: Maintaining the objects it requires is however your responsibility;
453 * typically `Logger*` pointee (if not null) and session::App pointees.
454 *
455 * @see class doc header "Object lifetimes" section; it explains what to do/how/why.
456 *
457 * @internal
458 *
459 * We might be being paranoid above in saying that Session_vat_network ctors can throw; look into it and
460 * potentially update docs for those ctors; Server_context internal ctor; and (slightly)
461 * Context_server::accept() more-complex-signature overload.
462 *
463 * @endinternal
464 *
465 * @param logger_ptr
466 * Logger to use for logging subsequently (or null to definitely not log).
467 * @param kj_io
468 * A KJ event loop context.
469 * @param shm_enabled_session
470 * See above.
471 * This is the object with `.borrow_object()` and `.lend_object()` methods available for SHM-object
472 * cross-process transmission. Shared handle: see "Object lifetimes" above.
473 * @param bidir_transport
474 * See above. This shall be used to transmit small messages while holding the actual capnp-messages,
475 * which can be sizable-to-huge, in SHM sans copying.
476 * @param hndl_transport_limit_or_0
477 * See above. Note that the default shall enable handle/capability transmission (`.getFd()` use in
478 * your capnp-`interface` impl(s)) and choose a sensible actual handle-count limit. If you do not
479 * use `.getFd()` feature of the capnp-RPC system, it is best to set this to zero, it is said,
480 * for safety and possibly even security.
481 */
482 explicit Session_vat_network(flow::log::Logger* logger_ptr, kj::AsyncIoContext* kj_io,
483 Shm_lender_borrower_ptr&& shm_enabled_session,
484 Native_handle&& bidir_transport,
485 unsigned int hndl_transport_limit_or_0 = S_N_MAX_INCOMING_FDS);
486
487 /**
488 * Constructs the `VatNetwork`, making it ready for use in an #Rpc_system: advanced form.
489 *
490 * This ctor form is the most fundamental, meaning it provides the most flexibility by letting caller specify
491 * (along with the class-level template parameters) each knob separately.
492 * - It has no required dependency on ipc::session. #Shm_lender_borrower need not be an ipc::session::Session
493 * variant of any kind, though it can be.
494 * - The node ID (`srv_else_cli`) is specified by the caller. The opposing object must use the inverse value.
495 * - The #Shm_arena need not come from any ipc::session::Session; you might have even created it manually
496 * yourself (shm::classic::Pool_arena, shm::arena_lend::jemalloc::Ipc_arena).
497 * - `bidir_transport` is provided as a raw native handle (in Unix parlance, FD). Do note that, as with all
498 * ctors, `*this` takes ownership of the transport and is responsible for closing it. `bidir_transport`
499 * is accordingly nullified by this ctor -- immediately and deterministically, even if the ctor then
500 * throws. Do note that `bidir_transport` must refer to a bidirectional
501 * transport. Realistically that means a pre-connected Unix domain socket of stream type.
502 * - `hndl_transport_limit_or_0 > 0` means native handles (a/k/a capabilities in capnp-RPC parlance) can
503 * be RPC-transmitted (i.e., your interface impls and clients can use `.getFd()` and yield something
504 * as opposed to nothing). In this case `bidir_transport` *must* refer to a Unix domain socket connection.
505 * If 0 then handle/capability transmission shall not occur.
506 *
507 * A Flow-IPC user is likelier to use the other ctor, namely the one that assumes `Shm_lender_borrower`
508 * is an ipc::session::Session (SHM-enabled) variant. However exotic/advanced users might require the use
509 * of the present ctor instead.
510 *
511 * @see streaming_flow_window_ki() which provides a perf- and RAM-use-relevant knob.
512 *
513 * ### Special mode ###
514 * Use null `shm_lnd_brw` and `shm_arena` to have `*this` act identically to a regular, non-zero-copy-enabled
515 * `TwoPartyVatNetwork`. This may be useful for fallback, benchmarking, debugging, profiling.
516 * streaming_flow_window_ki() has no effect in this mode.
517 *
518 * ### Object lifetimes ###
519 * `*logger_ptr` (if not null) must stay alive while `*this` exists.
520 *
521 * `*shm_lnd_brw` (if not null) and `*shm_arena` shall stay alive potentially past `*this` by means of our taking
522 * the `shared_ptr` handle. However: Maintaining the objects it requires is however your responsibility.
523 *
524 * If one of `*shm_lnd_brw`, `*shm_arena` owns the other -- as an ipc::session::Session
525 * owns its `session_shm()` arena -- we recommend using a `shared_ptr` aliasing constructor: a #Shm_arena_ptr
526 * that points to the arena but shares the session's ownership. The other ctor uses this technique w/r/t
527 * `ipc::session::Session`-likes storing their `.session_shm()`-accessed arenas.
528 *
529 * @see class doc header "Object lifetimes" section; it explains what to do/how/why.
530 *
531 * @param logger_ptr
532 * Logger to use for logging subsequently (or null to not log for sure).
533 * @param kj_io
534 * A KJ event loop context.
535 * @param srv_else_cli
536 * See above.
537 * @param shm_lnd_brw
538 * See above; see class doc header `Shm_lender_borrower` template parameter explanation.
539 * This is the object with `.borrow_object()` and `.lend_object()` methods available for SHM-object
540 * cross-process transmission. Shared handle: see "Object lifetimes" above.
541 * @param shm_arena
542 * See above; see class doc header `Shm_arena` template parameter explanation.
543 * This is the object with `.construct<T>(...)` method available for SHM-object allocation/construction.
544 * Shared handle: see "Object lifetimes" above.
545 * @param bidir_transport
546 * See above. This shall be used to transmit small messages while holding the actual capnp-messages,
547 * which can be sizable-to-huge, in SHM sans copying.
548 * @param hndl_transport_limit_or_0
549 * See the other ctor forms.
550 */
551 explicit Session_vat_network(flow::log::Logger* logger_ptr, kj::AsyncIoContext* kj_io,
552 bool srv_else_cli,
553 Shm_lender_borrower_ptr&& shm_lnd_brw, Shm_arena_ptr&& shm_arena,
554 Native_handle&& bidir_transport,
555 unsigned int hndl_transport_limit_or_0 = S_N_MAX_INCOMING_FDS);
556
557 /// Boring virtual destructor. It logs.
558 virtual ~Session_vat_network();
559
560 // Methods.
561
562 /**
563 * The `-> stream` flow control window limit, in kebibytes, used by capnp-RPC w/r/t the sum of in-SHM
564 * `stream`ing traffic by all objects operating through `*this` at any point in time.
565 *
566 * This value is meaningful if and only if operating in zero-copy (SHM-using) mode. Thus if you used the
567 * "special mode" (in ctor, nullify args `shm_lnd_brw` or `shm_enabled_session`), it has no effect.
568 * We will then act identically to a vanilla `TwoPartyVatNetwork` with its normal flow control window computation.
569 *
570 * @see https://capnproto.org/news/#multi-stream-flow-control for an introduction to the `-> stream` feature.
571 * In our case, however, flow control applies to the RAM use of streaming objects; that RAM use
572 * = our use of shared memory (SHM) for streaming-involved bulk messages. Regarding the part of the text
573 * in that link describing how the kernel send buffer is used to compute a decent value for the window:
574 * that computation does not apply, in our case; the value returned here (at any given point in time,
575 * dynamically) will be used instead.
576 *
577 * @see the mutator overload, which can be used to change this value.
578 *
579 * A reasonable default value is used if one does not call the mutator:
580 * Session_vat_network_base::S_STREAMING_FLOW_WINDOW_KI.
581 *
582 * ### When to use the mutator ###
583 * This limit controls how much RAM at a time can be held by `*this` RPC engine in-flight -- meaning provided by
584 * sender-side user code, not yet consumed by receiver-side user code -- for `stream` payloads. In our
585 * case there is no networking involved, with very high bandwidth and very low latency; the effect of most sane
586 * values on (streaming) throughput should be low. A too-large value could have effect on RAM use; and in the
587 * presence of *possibly* limited SHM capacity it could even lead to out-of-arena-memory situation. When/if this
588 * is a factor (meaning memory is at a premium), typical uses of SHM are directly under the user's control:
589 * you know when and how much you're allocating (usually). In this case, though, capnp-RPC is deciding --
590 * only (*for this particular `stream` feature!*) -- how much it will allocate for you, at a time.
591 * Thus: If in the field you see RAM-use trouble as a result, or perhaps want to benchmark the window's effect on
592 * perf under load, the mutator overload streaming_flow_window_ki() may be useful.
593 *
594 * The default Session_vat_network_base::S_STREAMING_FLOW_WINDOW_KI is orders of magnitude smaller
595 * than *typical* SHM capacity available, hence *typically* you will not run out of SHM from `-> stream`ing.
596 * As of this writing the following SHM-providers come with Flow-IPC:
597 * - SHM-jemalloc: There is no per-arena limit; so it becomes a matter of general out-of-memory or hitting
598 * a SHM-use kernel limit if any.
599 * - SHM-classic: There is a per-arena limit. In `ipc::session`-created arenas a default is used which can
600 * be overridden with session::shm::classic::Session_server::pool_size_limit_mi() mutator. (If creating
601 * a shm::classic::Pool_arena directly, the size must be supplied via the create-mode ctor.) The default
602 * as of this writing is 2Gi, while `S_STREAMING_FLOW_WINDOW_KI` is 1/2 Mi (several orders' of magnitude
603 * difference).
604 *
605 * Ultimately, though, your mileage may vary, as environments and use-cases differ.
606 *
607 * @return See above.
608 */
609 size_t streaming_flow_window_ki() const;
610
611 /**
612 * Sets the value as returned by `streaming_flow_window_ki()` accessor. See its doc header; among other things it
613 * suggests when using the present mutator may be helpful.
614 *
615 * A reasonable default value is used if one does not call this:
616 * Session_vat_network_base::S_STREAMING_FLOW_WINDOW_KI.
617 *
618 * @param limit_ki
619 * The new value. It must be positive.
620 */
621 void streaming_flow_window_ki(size_t limit_ki);
622
623 /**
624 * Yields a promise that is fulfilled when all `Rpc_conn`s returned by connect() and accept() are allowed
625 * to be destroyed (the `Own<>`s drop their objects). So it fires when the # of such outstanding `Own<Rpc_conn>`s
626 * goes from 1 to 0.
627 *
628 * @return See above.
629 */
630 kj::Promise<void> on_disconnect();
631
632 /**
633 * Identical to on_disconnect().
634 *
635 * The odd styling of the name is due to the fact `capnp::TwoPartyVatNetwork` (outside Flow-IPC)
636 * has this guy too, and capnp-RPC-oriented user code may in reality expect this method to exist.
637 * In particular if something like `capnp::EzRpc*` wants to work properly with both a `*this`
638 * and a `TwoPartyVatNetwork` (perhaps via template parameterization), then it'll need to call
639 * a method named specifically `onDisconnect()`.
640 *
641 * @return See above.
642 */
643 kj::Promise<void> onDisconnect();
644
645 // VatNetwork impls.
646
647 /**
648 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
649 * as opposed to the user.
650 *
651 * @param ref
652 * See above.
653 * @return See above.
654 */
655 kj::Maybe<kj::Own<Rpc_conn>> connect(Vat_id::Reader ref) override;
656
657 /**
658 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
659 * as opposed to the user.
660 *
661 * @return See above.
662 */
663 kj::Promise<kj::Own<Rpc_conn>> accept() override;
664
665 // VatNetwork::Connection (Rpc_conn) impls.
666
667 /**
668 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
669 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
670 *
671 * @return See above.
672 */
673 kj::Own<::capnp::RpcFlowController> newStream() override;
674
675 /**
676 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
677 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
678 *
679 * @return See above.
680 */
681 Vat_id::Reader getPeerVatId() override;
682
683 /**
684 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
685 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
686 *
687 * @param seg0_word_sz
688 * See above.
689 * @return See above.
690 */
691 kj::Own<Rpc_msg_out> newOutgoingMessage(unsigned int seg0_word_sz) override;
692
693 /**
694 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
695 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
696 *
697 * @return See above.
698 */
699 kj::Promise<kj::Maybe<kj::Own<Rpc_msg_in>>> receiveIncomingMessage() override;
700
701 /**
702 * Implements `capnp::VatNetwork` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
703 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
704 *
705 * @return See above.
706 */
707 kj::Promise<void> shutdown() override;
708
709private:
710 // Types.
711
712 class Rpc_msg_out_impl;
713 class Rpc_msg_in_impl;
714
715 /**
716 * Disposer for `Own<Rpc_conn>` returned by `connect()` and `accept()` which (1) does not delete anything
717 * and (2) fulfills #m_disconnect_promise once all such `Own`s have dropped their `Rpc_conn`s.
718 *
719 * Why? Answer: See #m_disconnect_promise doc header. It isn't really central to Session_vat_network, so
720 * don't worry about it, unless you really have to.
721 */
723 public kj::Disposer // Interface.
724 {
725 // Data.
726
727 /// It is the fulfiller of Session_vat_network::m_disconnect_promise.
728 mutable kj::Own<kj::PromiseFulfiller<void>> m_fulfiller;
729
730 /**
731 * Number of times Session_vat_network::as_connection() executed minus the number of the returned
732 * `Own<Rpc_conn>`s to have dropped their `Rpc_conn`s.
733 */
734 mutable unsigned int m_ref_count;
735
736 // Methods.
737
738 /**
739 * Implements interface by noting that the tracked #Rpc_conn has one fewer outstanding handle; and if that
740 * means none remain then fulfills the `*this`-stored promise.
741 *
742 * The odd styling of the name is due to the interface being implemented.
743 */
744 void disposeImpl(void*) const override;
745 }; // struct Fulfiller_disposer
746
747 /// Convenience alias for how capnp-RPC passes-around handle-transport-capable byte streams.
748 using Kj_stream_of_blob_hndls_ptr = kj::Own<kj::AsyncCapabilityStream>;
749
750 /// Convenience alias for how capnp-RPC passes-around non-handle-transport-capable byte streams.
751 using Kj_stream_of_blobs_ptr = kj::Own<kj::AsyncIoStream>;
752
753 // Methods.
754
755 /**
756 * The core of connect() and accept(), used whenever those actually succeed: returns `*this` as an #Rpc_conn.
757 * @return See above.
758 */
759 kj::Own<Rpc_conn> as_connection();
760
761 /**
762 * Implements `capnp::RpcFlowController::WindowGetter` API. The odd styling of the name is due to the
763 * interface being implemented.
764 *
765 * @see newStream() body, where this is hooked into `*this` capnp-RPC engine, for background on why/what this is.
766 *
767 * @return See above.
768 */
769 size_t getWindow() override;
770
771 // Data.
772
773 /// The value returned by streaming_flow_window_ki() except in bytes.
775
776 /// KJ event loop context.
777 kj::AsyncIoContext* const m_kj_io;
778
779 /**
780 * Node ID: we are either designated server or client. This does not formally force us to take either role
781 * in terms of actual actions; though typically once the ID is assigned, the bootstrap interface is
782 * provided by the `true` guy, while the bootstrap client is the `false` guy. That is up to the user of
783 * `*this` though. This is just an ID essentially.
784 */
785 const bool m_srv_else_cli;
786
787 /**
788 * The user-supplied object with `.borrow_object()` and `.lend_object()` methods that enable the
789 * sharing of SHM-stored objects cross-process. See class doc header template parameter docs for details.
790 * Null if and only if in special mode (no zero-copy).
791 *
792 * @note We keep a shared-handle so that we can give it to `Rpc_msg_*`s we generate, as they may outlive us.
793 * See "Object lifetimes" in class doc header.
794 */
796
797 /**
798 * The user-supplied object with `.construct<T>()` method that enables the
799 * allocation/construction of SHM-stored objects cross-process.
800 * See class doc header template parameter docs for details.
801 * Null if and only if #m_shm_lnd_brw is null.
802 *
803 * @note We keep a shared-handle so that we can give it to `Rpc_msg_*`s we generate, as they may outlive us.
804 * See "Object lifetimes" in class doc header.
805 */
807
808 /**
809 * If in ctor `hndl_transport_limit_or_0 == 0`, this is null; else this is the low-level byte-stream
810 * used to transmit SHM-handles and potentially native-handles (a/k/a capabilities in capnp-RPC speak)
811 * to/from opposing Session_vat_network.
812 */
814
815 /**
816 * If in ctor `hndl_transport_limit_or_0 != 0`, this is null; else this is the low-level byte-stream
817 * used to transmit SHM-handles to/from opposing Session_vat_network.
818 */
820
821 /**
822 * The next layer on top of `m_kj_stream_of_blobs*`, this is the capnp-encoded-message (plus possibly
823 * native handle/"capability") stream used by #m_network.
824 *
825 * Null until early in the ctor body; then not null.
826 *
827 * @todo Session_vat_network::m_capnp_msg_stream should be `optional`, and it is fine with at least gcc,
828 * but clang gives an error; so for now made it `unique_ptr`; look into it sometime. It's not a huge deal
829 * as a `unique_ptr` though. Presumably it's something inside `BufferedMessageStream` triggering this;
830 * capnp-1.0.2; clang 13, 15, 16, 17; C++17 mode at least. The error is:
831 * "the parameter for this explicitly-defaulted copy constructor is const, but a member or base requires it
832 * to be non-const" -- referring to `optional` copy ctor.
833 */
834 boost::movelib::unique_ptr<::capnp::BufferedMessageStream> m_capnp_msg_stream;
835
836 /**
837 * The Big Kahuna of our impl, this is the `TwoPartyVatNetwork` we reuse to transmit messages back and forth,
838 * just as a vanilla `TwoPartyVatNetwork` would, except we pre/post-process stuff in such a way as to have
839 * it transmit only short SHM-handle-bearing messages (and possibly native handles/"capabilities"), while
840 * the actual sizable-to-huge `rpc::Message`s needed by #Rpc_system and friends are held (by us) in
841 * SHM and never copied.
842 *
843 * So roughly speaking:
844 * - We have #m_network do as much as possible.
845 * - The parts it cannot do (holding messages in SHM, reading them from there) we do ourselves
846 * (chiefly via our `Rpc_msg_in` and `Rpc_msg_out` impls Rpc_msg_in_impl and Rpc_msg_out_impl, respectively).
847 *
848 * Null until early in the ctor body; then not null.
849 */
850 std::optional<::capnp::TwoPartyVatNetwork> m_network;
851
852 /**
853 * Starting at 0 (unknown), this is both (1) the first-segment-size (in `capnp::word`s) for the next
854 * outgoing message and (2) the highest total size any outgoing message actually
855 * ended up being. Important: this is not about *user* messages (`rpc::Message`, sometimes containing actual
856 * payload from end user) but about our SHM-handle-bearing guys that actually go into/out of byte-stream
857 * transport... so it will both be small and almost the same each time.
858 */
860
861 /**
862 * This is #m_network essentially cast as an #Rpc_conn impl: see as_connection() body.
863 *
864 * ### Rationale ###
865 * Consider #Rpc_conn a/k/a `capnp::VatNetwork<...>::Connection`. Since we return via connect() + accept()
866 * (as_connection()) `*this`, we must implement #Rpc_conn abstract methods: chiefly
867 * newOutgoingMessage(), receiveIncomingMessage(), and a few other less central ones.
868 * Our particular impl is largely to forward this to #m_network (the `TwoPartyVatNetwork`).
869 * Since we happen to know `TwoPartyVatNetwork::connect()` and `accept()` also essentially return
870 * their `*this`, it is natural -- to achieve this forwarding -- to just call, e.g.,
871 * `m_network->newOutgoingMessage()`. Actually, though, that will not compile:
872 * `TwoPartyVatNetwork` inherits from `VatNetwork<...>`, in order to in fact implement abstract
873 * methods including `newOutgoingMessage()`... but it inherits them *privately*.
874 *
875 * This is, some would contend, a bit strange and counter-indicated by stylists: you are *allowed*
876 * to `private`ly subclass a `public`-method-bearing abstract interface, but it is arguably dodgy.
877 * I (ygoldfel) do somewhat get it, I think:
878 * - `TwoPartyVatNetwork` appears to return "new" connections but really *is* one connection object
879 * itself.
880 * - The latter fact is an internal implementation detail, formally.
881 * - So using that fact explicitly from the outside -- by trying to call one of the #Rpc_conn methods
882 * like newOutgoingMessage(), and/or by up-casting `TwoPartyVatNetwork*` to `Rpc_conn*` -- sort-of
883 * amounts to using an impl detail as an outside user. Private inheritance and private `virtual` method
884 * impls prevent that.
885 *
886 * In any case, we do need an #Rpc_conn for #m_network, so that we can do the aforementioned forwarding
887 * to its methods. Hence we use `TwoPartyVatNetwork::connect()` and/or `TwoPartyVatNetwork::accept()`
888 * to get at it (in ctor, ASAP).
889 *
890 * ### Corollary ###
891 * We happen to *not* follow `TwoPartyVatNetwork`'s lead: *our* #Rpc_conn impls such as
892 * newOutgoingMessage() *are* `public`, and we inherit from #Rpc_conn `public`ly. It really doesn't matter,
893 * as of this writing at least, but I (ygoldfel) just feel odd doing the whole thing wherein
894 * one impls `public: virtual ...() = 0` but makes the impl itself `private` but then also gives public access
895 * to it through as_connection(). Frankly I do not see the harm of making it explicitly public given the
896 * existence of as_connection(). What's the big "secret" after all?
897 */
898 kj::Own<Rpc_conn> m_conn;
899
900 /// Guards from allowing 2nd accept() (there is only 1 connection on server side in a 2-party network).
902
903 /**
904 * Fulfiller for the promise returned by accept() on the client side, or the
905 * second call on the server side. Never fulfilled, because there is only one connection; kept alive (not
906 * destroyed) so as to not *reject* that promise either. See accept() body.
907 */
908 kj::Own<kj::PromiseFulfiller<kj::Own<Rpc_conn>>> m_accept_fulfiller;
909
910 /**
911 * See on_disconnect(): the promise returned there, whose fulfillment indicates that for each successful call
912 * to connect() and accept(), each of which yielded an `Own<Rpc_conn>` (pointing to `*this`), that returned
913 * `Own<Rpc_conn>` has reached end of life.
914 *
915 * ### What? This `Fulfiller_disposer` and disconnect-promise business ###
916 * This "hack" (<= quoting the `TwoPartyVatNetwork` comment) can use some explaining. The explanation in
917 * `TwoPartyVatNetwork` is pithy and (for me, ygoldfel) eventually grokkable, but it took a bit of effort.
918 * In hopes of explaining it more explicitly to save time here goes:
919 *
920 * Same as inside `TwoPartyVatNetwork`: by definition in a two-party "universe" there can only be one connection
921 * at a time, so our `*this` is both a `VatNetwork` impl *and* an #Rpc_conn impl. But connect() and accept()
922 * by the `VatNetwork` interface must return `Own<Rpc_conn>` (similar to `unique_ptr<VatNetwork>`).
923 * At the same time it can't be a vanilla `Own<>` that, upon losing (not transferring) ownership (such as in its
924 * dtor), will `delete` the object. In our case that is nonsensical; we might even be on the stack and anyway
925 * we are also a Session_vat_network. Basically connect() and accept() does not create a new object in heap,
926 * so deleting it is nonsensical.
927 *
928 * So just for that reason, the disposer (like a deleter in STL smart-pointers) for that `Own` must simply not
929 * `delete`; Session_vat_network = #Rpc_conn and has its own proper lifetime.
930 *
931 * In addition, though, there is on_disconnect() which as noted earlier is supposed to return a `Promise` that
932 * fires once "all" previous issues connections have (usually by the `RpcSystem`) lost (not transferred)
933 * their `Rpc_conn` objects, indicating overall a "disconnect." So the aforementioned disposer (deleter) should
934 * firstly *not* `delete` anything; and secondly should decrement a refcount keeping track of how many times
935 * connect() or accept() successfully returned an `Own<Rpc_conn>` (to `*this`) minus which of those handles
936 * has dropped their object. Once the refcount is back to zero, they've all been dropped, hence promise shall
937 * be fulfilled.
938 *
939 * @see #m_disconnect_fulfiller which is where that logic takes place.
940 *
941 * @todo Maybe we can do the internal Session_vat_network::Fulfiller_disposer stuff with `shared_ptr` and/or
942 * `weak_ptr` or something? E.g., we did so in the case of Ez_rpc_kj_io (internal to `Ez_rpc_*` stuff).
943 * Just stylistically we prefer that sort of thing to `kj::Own` craziness. This would be pretty easy,
944 * and pithy at that, if we could return `shared_ptr<Rpc_conn>`, but the interface requires `Own<Rpc_conn>`.
945 * So maybe just leave it.
946 */
947 kj::ForkedPromise<void> m_disconnect_promise;
948
949 /**
950 * Disposer for all `Own<Rpc_conn>`s returned by connect() and accept().
951 * See #m_disconnect_promise doc header for explanation of all this.
952 */
954}; // class Session_vat_network
955
956/**
957 * @private
958 *
959 * Inner impl class in Session_vat_network, implementing `capnp::OutgoingRpcMessage`, which makes up roughly
960 * 50% of what Session_vat_network does on top of its contained `TwoPartyVatNetwork`. (The other ~50% =
961 * Rpc_msg_in_impl.) To wit:
962 * - It reuses `TwoPartyVatNetwork`'s impl of `Rpc_msg_out` to implement an outgoing byte stream but instead of
963 * loading `rpc::Message`s into it (and the heap), it loads little SHM-handles to those `rpc::Message`s.
964 * - It creates/registers these SHM-handles and hooks things up in such a way as to cause those `rpc::Message`s
965 * to be constructed in SHM instead of heap.
966 */
967template<typename Shm_lender_borrower_t, typename Shm_arena_t>
968class Session_vat_network<Shm_lender_borrower_t, Shm_arena_t>::Rpc_msg_out_impl : public Rpc_msg_out
969{
970public:
971 // Constructors/destructor.
972
973 /**
974 * Saves the `TwoPartyVatNetwork` `Rpc_msg_out` impl, as created by Session_vat_network::m_network, and the
975 * containing Session_vat_network.
976 *
977 * @param msg
978 * See above.
979 * @param seg0_word_sz
980 * Estimate for message size from the "user," so we can try to allocate in SHM efficiently.
981 * @param daddy
982 * See above.
983 */
984 Rpc_msg_out_impl(kj::Own<Rpc_msg_out>&& msg, size_t seg0_word_sz, Session_vat_network* daddy);
985
986 /// Boring virtual destructor.
988
989 // Methods.
990
991 /**
992 * Implements `Rpc_msg_out` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
993 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
994 *
995 * @return See above.
996 */
997 ::capnp::AnyPointer::Builder getBody() override;
998
999 /**
1000 * Implements `Rpc_msg_out` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1001 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1002 *
1003 * @param fds
1004 * See above.
1005 */
1006 void setFds(kj::Array<int> fds) override;
1007
1008 /**
1009 * Implements `Rpc_msg_out` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1010 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1011 */
1012 void send() override;
1013
1014 /**
1015 * Implements `Rpc_msg_out` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1016 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1017 *
1018 * @return See above.
1019 */
1020 size_t sizeInWords() override;
1021
1022private:
1023 // Data.
1024
1025 /**
1026 * The containing Session_vat_network.
1027 *
1028 * @warning Even though it is the "containing" Session_vat_network, it (`*m_daddy`!) may be destroyed
1029 * **before** `*this` Rpc_msg_out_impl is destroyed. send() needs `*m_daddy` to be alive,
1030 * and it will be. Constructor -- obviously ditto. Anything else that wants to access
1031 * `*m_daddy` must be careful. In particular: Dtor (if any) of an Rpc_msg_out_impl can definitely
1032 * execute, by way of the owner KJ loop, after `*m_daddy` is destroyed. Therefore, at least,
1033 * do not access `*m_daddy` (or anything, like a hypothetical also-saved `Logger*`, whose lifetime
1034 * is subordinate to it) from Rpc_msg_out_impl dtor (if any).
1035 */
1037
1038 /**
1039 * The vanilla `TwoPartyVatNetwork`-generated `Rpc_msg_out`, where we shall store capnp-encoded SHM-handle
1040 * from within #m_capnp_msg_in_shm.
1041 */
1042 kj::Own<Rpc_msg_out> m_msg;
1043
1044 /**
1045 * Copy of Session_vat_network::m_shm_lnd_brw: keeps the lender-borrower alive until `*this` is destroyed,
1046 * since that can happen after `*m_daddy` is destroyed. (See #m_daddy.) Declared before #m_capnp_msg_in_shm,
1047 * so that it outlives that (the SHM user).
1048 *
1049 * @note For some SHM-providers (SHM-jemalloc is one) the existence of a SHM-handle like that stored in
1050 * #m_capnp_msg_in_shm is itself sufficient to keep a SHM-arena and/or SHM-session objects alive.
1051 * For others that is not the case. We assume the general/safe case.
1052 *
1053 * ### Why `*this` can outlive `*m_daddy` ###
1054 * capnp-RPC keeps each in-progress call's context (which owns the call's incoming message; and, once the
1055 * Return is sent, the outgoing one too) alive via a promise it `detach()`es onto the KJ event loop --
1056 * it as of this writing does so to make calls uncancelable. Such a context is owned by the
1057 * event loop, not by the #Rpc_system; so it can be destroyed only when the loop next iterates or dies -- which
1058 * may be after the user has (in the natural order) destroyed the #Rpc_system, the Session_vat_network, and
1059 * the ipc::session::Session behind it. Its messages' in-SHM serializations must then still be safely
1060 * deallocatable/returnable; hence the session (and arena) is kept alive from here.
1061 *
1062 * Perf cost: a couple of atomic ref-count ops per message, negligible next to the IPC itself.
1063 */
1065
1066 /// Copy of Session_vat_network::m_shm_arena; see #m_shm_lnd_brw.
1068
1069 /**
1070 * The `capnp::MessageBuilder` that stores the capnp-serialization needed by the user of a `*this`
1071 * (which we happen to know is the `rpc::Message` schema) in SHM using the Flow-IPC SHM system.
1072 */
1074}; // class Session_vat_network::Rpc_msg_out_impl
1075
1076/**
1077 * @private
1078 *
1079 * Inner impl class in Session_vat_network, implementing `capnp::IncomingRpcMessage`, which makes up roughly
1080 * 50% of what Session_vat_network does on top of its contained `TwoPartyVatNetwork`. (The other ~50% =
1081 * Rpc_msg_out_impl.) To wit:
1082 * - It reuses `TwoPartyVatNetwork`'s impl of `Rpc_msg_in` to implement an incoming byte stream but instead of
1083 * getting `rpc::Message`s out of it (and the heap), it interprets it as the little SHM-handles
1084 * to those `rpc::Message`s.
1085 * - It obtains/registers these SHM-handles and hooks things up in such a way as to cause those `rpc::Message`s
1086 * to be read (by getBody() called) directly from SHM instead of heap.
1087 */
1088template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1089class Session_vat_network<Shm_lender_borrower_t, Shm_arena_t>::Rpc_msg_in_impl : public Rpc_msg_in
1090{
1091public:
1092 // Constructors/destructor.
1093
1094 /**
1095 * Saves the `TwoPartyVatNetwork` `Rpc_msg_in` impl, as created by Session_vat_network::m_network, and the
1096 * containing Session_vat_network.
1097 *
1098 * @param msg
1099 * See above.
1100 * @param daddy
1101 * See above.
1102 */
1103 Rpc_msg_in_impl(kj::Own<Rpc_msg_in>&& msg, Session_vat_network* daddy);
1104
1105 /// Boring virtual destructor.
1107
1108 // Methods.
1109
1110 /**
1111 * Implements `Rpc_msg_in` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1112 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1113 *
1114 * @return See above.
1115 */
1116 ::capnp::AnyPointer::Reader getBody() override;
1117
1118 /**
1119 * Implements `Rpc_msg_in` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1120 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1121 *
1122 * @return See above.
1123 */
1124 kj::ArrayPtr<kj::AutoCloseFd> getAttachedFds() override;
1125
1126 /**
1127 * Implements `Rpc_msg_in` API. Typically this is invoked by the capnp-RPC system (`RpcSystem` et al)
1128 * as opposed to the user. The odd styling of the name is due to the interface being implemented.
1129 *
1130 * @return See above.
1131 */
1132 size_t sizeInWords() override;
1133
1134private:
1135 // Data.
1136
1137 /**
1138 * The vanilla `TwoPartyVatNetwork`-generated `Rpc_msg_in`, storing the capnp-encoded SHM-handle
1139 * which #m_capnp_msg_in_shm interprets.
1140 */
1141 kj::Own<Rpc_msg_in> m_msg;
1142
1143 /**
1144 * Copy of Session_vat_network::m_shm_lnd_brw: keeps the lender-borrower -- the executor of `.borrow_object()`,
1145 * whose disposer the borrowed serialization's handle shall run -- alive until `*this` is destroyed, since
1146 * that can happen after the containing Session_vat_network is destroyed. See Rpc_msg_out_impl::m_shm_lnd_brw
1147 * doc header for the full story. Declared before #m_capnp_msg_in_shm, so that it outlives that.
1148 */
1150
1151 /**
1152 * The `capnp::MessageReader` that stores the capnp-serialization needed by the user of a `*this`
1153 * (which we happen to know is the `rpc::Message` schema) in SHM using the Flow-IPC SHM system.
1154 */
1156}; // class Rpc_msg_in_impl
1157
1158// Free functions: in *_fwd.hpp.
1159
1160// Template implementations.
1161
1162// Session_vat_network::Rpc_msg_out_impl implementations.
1163
1164template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1166 (kj::Own<Rpc_msg_out>&& msg, size_t seg0_word_sz, Session_vat_network* daddy) :
1167
1168 m_daddy(daddy),
1169 m_msg(std::move(msg)),
1170 m_shm_lnd_brw(m_daddy->m_shm_lnd_brw),
1171 m_shm_arena(m_daddy->m_shm_arena),
1172 // Create a new MessageBuilder -- that allocates in SHM! m_msg's internal MessageBuilder allocates in heap.
1173 m_capnp_msg_in_shm(m_daddy->get_logger(), m_shm_arena.get(),
1174 seg0_word_sz * sizeof(::capnp::word)) // Our Flow-IPC guy can take a hint too (in bytes).
1175{
1176 // Cool.
1177}
1178
1179template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1181
1182template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1185{
1186 using ::capnp::AnyPointer;
1187
1188 /* This is just what TwoPartyVatNetwork's Rpc_msg_out_impl equivalent does -- but via its MallocMessageBuilder,
1189 * whereas we use our in-SHM MessageBuilder from Flow-IPC. */
1190 return m_capnp_msg_in_shm.template getRoot<AnyPointer>();
1191}
1192
1193template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1195{
1196 using ::capnp::AnyPointer;
1197 namespace rpc = ::capnp::rpc;
1198
1199 /* This is straightforward, once one understands how the pieces fit together. TwoPartyVatNetwork here
1200 * would send off the true payload rpc::Message by basically calling MessageBuilder::getSegmentsForOutput()
1201 * and writing out the backing capnp-segments in series (1 or more of them depending on message size, etc.).
1202 * It also does fancier stuff internally -- trying to reduce # of syscalls by some buffering; things of that
1203 * nature. All of that is great for us, or at least shouldn't hurt; it's just that the payload should be
1204 * little SHM-handles (in this send(), *one* little SHM-handle) to those rpc::Message-backing capnp-segments --
1205 * which we have placed (via our work in our ctor and getBody()) in SHM. So now that message contents
1206 * are finalized, we can load the SHM-handle (as capnp payload), and off we go. (Actually this could've been
1207 * done in the ctor; then here we'd just forward to m_msg->send(). Either way seems fine perf-wise,
1208 * so I'd rather keep it all in one place in the code.)
1209 *
1210 * The only other aspect of this is the isShortLivedMsg field. We explain this near the top of
1211 * Session_vat_network delegated-to ctor. Thus:
1212 * Rpc_msg_in::isShortLivedRpcMessage(), as of this writing computing it as control msgs <=> not CALL/RETURN <=>
1213 * short-lived=yes, is how vanilla TwoPartyVatNetwork determines whether an incoming rpc::Message is short-lived --
1214 * directly inside the in-buffer. So we use the same computation but *before* the send; then on receipt check the
1215 * bool result. */
1216
1217 auto capnp_msg_in_heap_root
1218 = m_msg->getBody().initAs<schema::detail::CapnpRpcMsgTopSerialization>();
1219
1220 const auto capnp_msg_in_shm_root_rdr = getBody().asReader();
1221 if (!Rpc_msg_in::isShortLivedRpcMessage(capnp_msg_in_shm_root_rdr))
1222 {
1223 capnp_msg_in_heap_root.setIsShortLivedMsg(false);
1224 }
1225
1226 const bool ok = m_capnp_msg_in_shm.lend(&capnp_msg_in_heap_root, m_shm_lnd_brw.get());
1227 /* Now the message in SHM is safe from deallocation until both m_capnp_msg_in_shm is destroyed with *this,
1228 * *and* the receiver MessageReader (see Rpc_msg_in_impl) has had .borrow() called on it, and that MessageReader
1229 * is destroyed with its containing Rpc_msg_in_impl. (Corollary: if the SHM-handle never reaches the receiver --
1230 * the connection died with the little message still queued in m_msg's write queue -- then the in-SHM message
1231 * stays allocated until the session is torn down. Bounded by session lifetime; acceptable.) */
1232
1233 KJ_REQUIRE(ok,
1234 "Was asked to send a capnp-message by the RPC-system, but Capnp_message_builder::lend() "
1235 "yielded failure which means the Flow-IPC session just went down (on account of opposing "
1236 "process ending session, e.g., so it can exit), and this has been or will soon be "
1237 "reported via the Flow-IPC session on-error callback. Basically SHM cannot be used, so "
1238 "we cannot transmit a message with zero-copy over it. Stuff is going down. This is not "
1239 "usually some catastrophe; just the IPC conversation has finished.");
1240
1241 /* The byte-streamed message is ready (as is the real in-SHM message but never mind), so
1242 * we can update this. Note that it is essentially impossible we're using more than one segment; so
1243 * this m_msg->sizeInWords() call should be a fast-path access inside capnp arena code. */
1244 const auto msg_out_sz_words = m_msg->sizeInWords();
1245 assert(msg_out_sz_words != 0);
1246 if (msg_out_sz_words > m_daddy->m_msg_out_max_sz_words) // (Initially it is zero, so this will be true.)
1247 {
1248 /* This is rare (interesting; but not interesting enough for INFO). TRACE-log about it.
1249 * Normally we'd derive from Log_context and just FLOW_LOG_TRACE(),
1250 * but since this is so frequent, and TRACE/DATA logging is typically
1251 * disabled, we'd rather not spend the cycles on keeping a typically unused Logger* + Component; and instead
1252 * pay a little more in cycles when TRACE logging *is* enabled (if this `if` evaluates to `true`). Also
1253 * the code is a bit longer, and this comment is somewhat annoying to write and read... but c'est la vie. */
1254 if (const auto logger_ptr = m_daddy->get_logger())
1255 {
1256 const auto log_component = m_daddy->get_log_component();
1257 if (logger_ptr->should_log(flow::log::Sev::S_TRACE, log_component))
1258 {
1259 FLOW_LOG_SET_CONTEXT(logger_ptr, log_component);
1260 FLOW_LOG_TRACE_WITHOUT_CHECKING("Session_vat_network [" << *m_daddy << "]: Outgoing message send: "
1261 "next byte-streamed (little) message size updated: "
1262 "[" << m_daddy->m_msg_out_max_sz_words << "] words => "
1263 "[" << msg_out_sz_words << "] words.");
1264 }
1265 } // if (logger_ptr)
1266
1267 m_daddy->m_msg_out_max_sz_words = msg_out_sz_words;
1268 } // if (msg_out_sz_words > m_daddy->m_msg_out_max_sz_words)
1269
1270 // Same comment as just above w/r/t logging perf... it is not pretty but worth it.
1271 if (const auto logger_ptr = m_daddy->get_logger())
1272 {
1273 const auto log_component = m_daddy->get_log_component();
1274 if (logger_ptr->should_log(flow::log::Sev::S_TRACE, log_component))
1275 {
1276 const auto root_as_rpc_msg = capnp_msg_in_shm_root_rdr.template getAs<rpc::Message>();
1277 FLOW_LOG_SET_CONTEXT(logger_ptr, log_component);
1278 FLOW_LOG_TRACE_WITHOUT_CHECKING("Session_vat_network [" << *m_daddy << "]: Outgoing message send: "
1279 "message (possibly truncated) "
1280 "[" << ostreamable_capnp_brief(root_as_rpc_msg) << "]; "
1281 "size = [" << m_capnp_msg_in_shm.sizeInWords() << "] words x "
1282 "[" << sizeof(::capnp::word) << "] bytes/word; outer serialization (of "
1283 "little SHM-handle+ in heap/to copy into transport) = "
1284 "[" << msg_out_sz_words << "] words.");
1285 FLOW_LOG_DATA("Here is the complete message:"
1286 "\n" << ostreamable_capnp_full(root_as_rpc_msg));
1287 }
1288 } // if (logger_ptr)
1289
1290 m_msg->send(); // Now let it be smart about buffering m_msg a bit, or whatever it wants to do.
1291} // Session_vat_network::Rpc_msg_out_impl::send()
1292
1293template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1295{
1296 /* What to return here is not necessarily obvious (of course that is subjective). The API contract is
1297 * "Get the total size of the message, for flow control purposes." The question then is what "flow control"
1298 * in this particular context is. Naively one could assume it has to do with controlling back-pressure
1299 * w/r/t simply bytes traveling over the transport (whether -- in vanilla TwoPartyVatNetwork -- over the network
1300 * or IPC; just the latter for us). Without thinking about it too hard one would then simply
1301 * return m_msg->sizeInWords(); // This would return the size of our little SHM handle-containing message.
1302 * All cool -- that's what we're still transmitting; done! But no. To explain:
1303 *
1304 * (Firstly let's eliminate the over-the-network case from consideration. We are not doing that, so it'll be
1305 * easier and fully appropriate to ignore in the discussion. So: just local IPC.)
1306 *
1307 * This sizeInWords() does *not* apply to general traffic going through this connection, in the first place.
1308 * It applies only to a specific capnp-RPC user-facing feature: *streaming*. (This is completely independent of
1309 * AsyncIoStream or AsyncCapabilityStream... which is in fact the IPC-transport byte/FD-stream one could naively
1310 * guess per the paragraph above. Not that!) This is where in the schema an interface-method return type
1311 * is `-> stream`. As of this writing this isn't covered in the main docs at the capnp web site, but it is
1312 * introduced nicely in https://capnproto.org/news/#multi-stream-flow-control. Read that
1313 * as background, optionally, but the bottom line is: It allows one to set up, say, bulk uploads -- which will stream
1314 * at a controlled rate. This feature consists of two parts; part 1 is the syntactic niceness of expressing this
1315 * without having to set up the logic in one's own code; and part 2 -- which concerns us here -- is that by
1316 * expressing it in that syntax, it causes capnp-RPC's guts to engage *multi-stream flow control*, wherein it
1317 * takes a good stab at automatically controlling the rate at which it'll allow the `stream` payload to
1318 * proceed. So that's the flow control in question:
1319 * - A window-size (controlled by newStream(); see that guy).
1320 * - An accounting of how much of the window is currently in use. <- The relevant part here in sizeInWords().
1321 *
1322 * (Reminder: We're ignoring any networking aspect of this.) Without networking -- with a tiny latency and
1323 * very high bandwidth -- what's left to control is how much RAM is taken by (collective) streams.
1324 * (Incidentally: There is at most one ongoing stream per capability a/k/a interface-implementing object, and
1325 * 2+ objects don't share streams.) So, e.g., if I generate random bytes in chunks, stream them over capnp-RPC, and
1326 * the receiver then (say) writes them to tape as fast as it can, then I wouldn't want to keep generating
1327 * more random bytes until receiver-side has indicated it has written a bunch and can accept more bytes now.
1328 * Without back-pressure the accumulated-too-fast (and more and more so) bytes would exceed the available RAM.
1329 * So that's, ultimately, all it is.
1330 *
1331 * What we're returning here, then, is how much we'd contribute to that window. Both the window (see
1332 * newStream()) and the window-used (here) are about RAM use. For a vanilla TwoPartyVatNetwork the RAM use
1333 * is just the stuff traveling through the IPC-transport: first inside a MallocMessageBuilder around here (m_msg),
1334 * then in the kernel buffer(s), then in/near the MessageReader in the IncomingRpcMessage (a/k/a Rpc_msg_in).
1335 * So in that case `m_msg->sizeInWords()` is right. In our case, we avoid (almost) all that: the use of
1336 * the transport is (basically) negligible (~only SHM-handles go through it); instead we just plop user
1337 * (streamed) data in SHM. SHM is RAM; our area in it just doesn't get copied from one area to another to another
1338 * (zero-copy!). Thus: */
1339 return m_capnp_msg_in_shm.sizeInWords();
1340} // Session_vat_network::Rpc_msg_out_impl::sizeInWords()
1341
1342template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1344{
1345 m_msg->setFds(std::move(fds));
1346}
1347
1348// Session_vat_network::Rpc_msg_in_impl implementations.
1349
1350template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1352 (kj::Own<Rpc_msg_in>&& msg, Session_vat_network* daddy) :
1353
1354 m_msg(std::move(msg)),
1356 // Create a new MessageReader -- that reads in SHM! m_msg's internal MessageReader reads from heap.
1357 m_capnp_msg_in_shm(daddy->get_logger())
1358{
1359 namespace rpc = ::capnp::rpc;
1360
1361 Error_code err_code;
1362 m_capnp_msg_in_shm.borrow(m_msg->getBody().template getAs<schema::detail::CapnpRpcMsgTopSerialization>(),
1363 m_shm_lnd_brw.get(), &err_code);
1364 KJ_REQUIRE(!err_code,
1365 "Was asked to accept a capnp-in-message by the RPC-system, but Capnp_message_reader::borrow() "
1366 "yielded failure which means the Flow-IPC session just went down (on account of opposing "
1367 "process ending session, e.g., so it can exit), and this has been or will soon be "
1368 "reported via the Flow-IPC session on-error callback. Basically SHM cannot be used, so "
1369 "we cannot receive a message with zero-copy over it. Stuff is going down. This is not "
1370 "usually some catastrophe; just the IPC conversation has finished.");
1371
1372 /* Caution! As of capnp-1.0.2 calling MessageReader::sizeInWords() before e.g. getRoot() causes undefined
1373 * behavior (SEGV if you're lucky, endless looping if less lucky); seems the necessary structures are set up
1374 * in lazy fashion, and sizeInWords() does not trigger it.
1375 *
1376 * We want our sizeInWords() { m_capnp_msg_in_shm.sizeInWords() } to work immediately (as opposed to hang/crash
1377 * if called pre-getBody()), so "prime" it by calling m_capnp_msg_in_shm.getRoot() via getBody().
1378 * Perf impact: getBody() is ~guaranteed to be needed anyway by regular capnp-RPC use, so we're just priming its
1379 * lazy-setup earlier; it'd be done anyway otherwise but just later. Hence no (obvious) perf impact.
1380 *
1381 * Secondarily: If (<= not typical) we want to TRACE/DATA-log the pretty-print of *this contents just below,
1382 * then we'll need getBody()'s result anyway. */
1383 const auto root = getBody();
1384
1385 // (Re. logging: see comment in Rpc_msg_out_impl::send(); applies equally here.)
1386 const auto logger_ptr = daddy->get_logger();
1387 if (logger_ptr)
1388 {
1389 const auto log_component = daddy->get_log_component();
1390 if (logger_ptr->should_log(flow::log::Sev::S_TRACE, log_component))
1391 {
1392 FLOW_LOG_SET_CONTEXT(logger_ptr, log_component);
1393
1394 const auto root_as_rpc_msg = root.template getAs<rpc::Message>();
1395
1396 FLOW_LOG_TRACE_WITHOUT_CHECKING("Session_vat_network [" << *daddy << "]: Incoming message receive: "
1397 "message (possibly truncated) "
1398 "[" << ostreamable_capnp_brief(root_as_rpc_msg) << "]; "
1399 "size = [" << m_capnp_msg_in_shm.sizeInWords() << "] words x "
1400 "[" << sizeof(::capnp::word) << " bytes/word]; outer serialization (of "
1401 "little SHM-handle+ in heap/copied from transport) = "
1402 "[" << m_msg->sizeInWords() << "] words.");
1403 FLOW_LOG_DATA("Here is the complete message:"
1404 "\n" << ostreamable_capnp_full(root_as_rpc_msg));
1405 }
1406 } // if (logger_ptr)
1407} // Session_vat_network::Rpc_msg_in_impl::Rpc_msg_in_impl()
1408
1409template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1411
1412template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1415{
1416 return m_capnp_msg_in_shm.template getRoot<::capnp::AnyPointer>();
1417}
1418
1419template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1421{
1422 /* The impl here is, as you can see, identical to _out_impl::sizeInWords()'s. The reason for it is
1423 * half-obvious but without certain added context one could be misled about some related stuff. (I know... vague.)
1424 * To wit:
1425 *
1426 * It is, as explained inside Rpc_msg_out_impl::sizeInWords(), again about RAM; and therefore it is again --
1427 * in our case -- about how much space we take in SHM, with the in-heap part (m_msg.sizeInWords(); for that
1428 * matter also, say, `sizeof(*this)`) deemed negligible. The added context: As of this writing it has
1429 * nothing to do with the `-> stream` feature (whereas _out_impl's is exclusively about that). This time
1430 * it is counted against (instead of the streaming flow-control window/see newStream()) the limit
1431 * RpcSystem::get/setFlowLimit(), a simple security -- possibly safety -- feature to flood attacks wherein
1432 * there are many unresponded-to (remote procedure) calls in RAM.
1433 *
1434 * Still about RAM though; hence: */
1435 return m_capnp_msg_in_shm.sizeInWords();
1436}
1437
1438template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1439kj::ArrayPtr<kj::AutoCloseFd>
1441{
1442 return m_msg->getAttachedFds();
1443}
1444
1445// Session_vat_network proper implementations.
1446
1447template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1449 (flow::log::Logger* logger_ptr, kj::AsyncIoContext* kj_io,
1450 bool srv_else_cli,
1451 Shm_lender_borrower_ptr&& shm_lnd_brw, Shm_arena_ptr&& shm_arena,
1452 Native_handle&& bidir_transport, unsigned int hndl_transport_limit_or_0) :
1453
1454 flow::log::Log_context(logger_ptr, Log_component::S_RPC),
1456 m_kj_io(kj_io),
1457 m_srv_else_cli(srv_else_cli),
1458 m_shm_lnd_brw(std::move(shm_lnd_brw)),
1459 m_shm_arena(std::move(shm_arena)),
1461 m_accepted(false),
1462 m_disconnect_promise(nullptr)
1463{
1464 using ::capnp::MallocMessageBuilder;
1465 using ::capnp::BufferedMessageStream;
1466 using ::capnp::ReaderOptions;
1467 using ::capnp::AnyPointer;
1468 using kj::newPromiseAndFulfiller;
1469 using boost::movelib::make_unique;
1472
1473 /* First things first: deterministically consume (nullify) bidir_transport, into an auto-closer. From here on
1474 * -- including if something below throws -- the handle is never again the caller's to close: it is ours, until
1475 * KJ takes ownership in wrap[Unix]SocketFd() below. This is our advertised ctor contract. */
1476 Own_native_handle bidir_transport_hndl{std::move(bidir_transport)};
1477 assert(bidir_transport.null());
1478
1479 KJ_REQUIRE(bool(m_shm_lnd_brw) == bool(m_shm_arena),
1480 "Either specify neither or both; just one is weird.");
1481 /* (Bad args. A KJ_REQUIRE() seems reasonably KJ-style. See justification for its use over assert()
1482 * in streaming_flow_window_ki() knob mutator.) */
1483
1484 if (m_shm_lnd_brw)
1485 {
1486 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: Constructing Session_vat_network: "
1487 "node ID [srv_else_cli=" << m_srv_else_cli << "], "
1488 "session/SHM session/borrower-lender [" << *m_shm_lnd_brw << "], "
1489 "SHM arena [" << *m_shm_arena << "], "
1490 "bidirectional byte transport native-handle [" << bidir_transport_hndl.get() << "], "
1491 "in-FD count limit (0 = do not transmit native handles) = [" << hndl_transport_limit_or_0 << "].");
1492 }
1493 else
1494 {
1495 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: Constructing Session_vat_network: "
1496 "degenerate mode -- no zero-copy enabled -- will be identical to regular TwoPartyVatNetwork; "
1497 "node ID [srv_else_cli=" << m_srv_else_cli << "], "
1498 "bidirectional byte transport native-handle [" << bidir_transport_hndl.get() << "], "
1499 "in-FD count limit (0 = do not transmit native handles) = [" << hndl_transport_limit_or_0 << "].");
1500 }
1501
1502 /* TwoPartyVatNetwork m_network uses an optimization in its next-lower layer, namely BufferedMessageStream
1503 * which streams capnp-encoded messages (we prepare this guy: m_capnp_msg_stream), wherein if it considers
1504 * an in-message "short-lived" (guaranteed to be handled fully before the next in-message), then it can
1505 * somewhat-later capnp-read the message directly inside some incoming-data buffer space. Otherwise it must
1506 * be copied elsewhere first, so it can be read in peace without being overwritten by subsequent in-messages.
1507 * So vanilla TPVN will look inside the message, correctly assume it is an rpc::Message (which is the case for
1508 * all vanilla TPVNs), check its top-level union-which enum value (message type), and assume yes-short-lived
1509 * for most types except two: CALL/RETURN. Incidentally these types <=> it is a user-mutated message, meaning
1510 * it is their code setting fields through the usual capnp-generated API. Hence *not* these types <=> it is
1511 * a *control* message generated for internal purposes by capnp-RPC.
1512 *
1513 * This vanilla behavior won't work for us, as we leverage TPVN m_network
1514 * to transmit a byte stream of our little SHM-handles (to the real rpc::Message messages, which are
1515 * now in SHM in our case); so that default check of as-if-rpc::Message will result in garbage results and ultimately
1516 * in our experience an assert-fail regarding short-livedness (at best). Fortunately TPVN lets us provide
1517 * our own BufferedMessageStream (which, again, we do), where we can easily provide a different
1518 * is-short-lived callback (which is what follows here). We figure we have two reasonable choices:
1519 * -# Say no messages are short-lived (just return false). Copying our little SHM-handle-bearing messages
1520 * probably isn't too expensive*, and the alternative technique in the next bullet point does involve
1521 * making those messages a bit bigger and doing a little extra computation for each.
1522 * - `*` But that's on a per-message basis. In aggregate there could be non-trivial amounts of data in
1523 * them. Moreover: yes, our IPC-transmitted msgs are small... but so are their *control* `rpc::Message`s --
1524 * likely bigger but still small -- and indeed the messages they classify as short-lived *are* the control
1525 * messages, not the (potentially large) user (CALL/RETURN) `rpc::Message`s. So actually the situations
1526 * (vanilla TPVN versus our SHM-handle-transmitting alternative) are pretty similar in this sense. I.e.:
1527 * our "wire" representation of the would-be short-lived (<=> control) messages is somewhat smaller
1528 * than vanilla TPVN's, but the latter are still small already, and yet the capnp code considers it
1529 * worthwhile to optimize away potential copying thereof. Hence:
1530 * -# Simply mark, in the byte-streamed message itself (whose contents and interpretation we control
1531 * in *this), whether it is short-lived. Then the callback can just check that. As a result we'll
1532 * get the available optimization (don't copy-out control messages) for a low encoding/decoding cost.
1533 *
1534 * Going with 2: the added data and computation are not significant, and perhaps
1535 * the optimization is worth keeping even if it's for much smaller messages than in a vanilla TPVN.
1536 * @todo Look into it: benchmark perf. How much does it help for us, versus how much
1537 * does it help vanilla TPVN? Questions like that: could be interesting. */
1538
1539 BufferedMessageStream::IsShortLivedCallback is_short_lived_msg_func;
1540 if (m_shm_lnd_brw)
1541 {
1542 is_short_lived_msg_func = [](Capnp_msg_reader_interface& msg_in) -> bool
1543 {
1544 /* In Rpc_msg_out_impl we mark this bool appropriately, so just interpret it.
1545 * This code here is inspired by the impl of capnp::IncomingRpcMessage::getShortLivedCallback(). */
1546 return msg_in.getRoot<AnyPointer>().template getAs<schema::detail::CapnpRpcMsgTopSerialization>()
1547 .getIsShortLivedMsg();
1548 };
1549 }
1550 else // if (special mode where we do nothing useful)
1551 {
1552 is_short_lived_msg_func = Rpc_msg_in::getShortLivedCallback(); // Just do what TwoPartyVatNetwork would do.
1553 }
1554
1555 ReaderOptions network_reader_opts; // Default values.
1556 if (!m_shm_lnd_brw)
1557 {
1558 /* This is arguably a hack... and it would probably go away, if we handle the ReaderOptions-related to-do
1559 * in our class doc header. For now though: If not in "special mode" (where we would skip zero-copy)
1560 * we override the traversal-limit option for the in-SHM messages (see shm::Capnp_message_reader impl) to
1561 * make it essentially infinity. Well, in this "special mode" we want to have the same capabilities, just
1562 * without zero-copy. So make the same change at this higher layer in this case. */
1563 network_reader_opts.traversalLimitInWords = std::numeric_limits<uint64_t>::max() / sizeof(::capnp::word);
1564 }
1565
1566 if (hndl_transport_limit_or_0 == 0)
1567 {
1568 // Create non-FD-passing vanilla AsyncIoStream and use the TwoPartyVatNetwork ctor that takes such accordingly.
1569 m_kj_stream_of_blobs // (Per the TAKE_OWNERSHIP flag KJ owns the handle from this call on; so release ours here.)
1570 = m_kj_io->lowLevelProvider->wrapSocketFd(disowned_native_handle(std::move(bidir_transport_hndl)).m_native_handle,
1571 kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP);
1572 m_capnp_msg_stream = make_unique<BufferedMessageStream>(*m_kj_stream_of_blobs,
1573 std::move(is_short_lived_msg_func));
1575 m_srv_else_cli ? ::capnp::rpc::twoparty::Side::SERVER
1576 : ::capnp::rpc::twoparty::Side::CLIENT,
1577 network_reader_opts);
1578 }
1579 else
1580 {
1581 /* Create FD-passing AsyncCapabilityStream and use the TwoPartyVatNetwork ctor that takes such (plus in-FD limit)
1582 * accordingly. */
1583 m_kj_stream_of_blobs_hndls // (Release ours here, as above.)
1584 = m_kj_io->lowLevelProvider->wrapUnixSocketFd(disowned_native_handle(std::move(bidir_transport_hndl))
1585 .m_native_handle,
1586 kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP);
1587 m_capnp_msg_stream = make_unique<BufferedMessageStream>(*m_kj_stream_of_blobs_hndls,
1588 std::move(is_short_lived_msg_func));
1589 m_network.emplace(*m_capnp_msg_stream, hndl_transport_limit_or_0,
1590 m_srv_else_cli ? ::capnp::rpc::twoparty::Side::SERVER
1591 : ::capnp::rpc::twoparty::Side::CLIENT,
1592 network_reader_opts);
1593 }
1594
1595 /* One more thing left; we need to access m_network's connection, so we can forward to the various key
1596 * TwoPartyVatNetwork facilities (then we will pre/post-process some of the work for our purposes). Really
1597 * m_network *is* the Rpc_conn, but it `private`ly inherits from it, so... <please see full explanation
1598 * in m_conn doc header>. Bottom line, we need it, and the public way to access it is through
1599 * either m_network->connect() or m_network->accept(). It really doesn't matter which (we happen to know
1600 * the nature of how they're implemented, namely a glorified `return *this` despite all the Maybe<>s and Own<>s
1601 * and Promise<>s in the return value). But, we're either node-client or node-server -- and the opposing guy
1602 * is the other guy -- so we might as well use connect() and accept() respectively. (Actually connect() on
1603 * both sides should work too and is less code. I don't know. Arguably the following is more future-proof, if
1604 * TwoPartyVatNetwork changes or something.) */
1605
1606 {
1607 // This magic number used for a tiny optimization is stolen from TwoPartyVatNetwork insides.
1608 constexpr size_t VAT_ID_SZ = 4;
1609
1610 /* VatNetwork::connect() returns Maybe<Own<Connection>>, but TwoPartyVatNetwork's impl has no reason to fail,
1611 * as it just returns `*this` basically -- unless we called it to connect to not-the-server, which we won't.
1612 * So it should just work. We do have to assemble this annoying opposing-peer "vat ID" capnp node though. */
1613
1614 MallocMessageBuilder peer_vat_id{VAT_ID_SZ};
1615 peer_vat_id.initRoot<Vat_id>()
1616 .setSide(m_srv_else_cli ? ::capnp::rpc::twoparty::Side::CLIENT // We're server; "connect" to client.
1617 : ::capnp::rpc::twoparty::Side::SERVER); // Vice versa.
1618 auto conn_uptr_maybe = m_network->connect(peer_vat_id.getRoot<Vat_id>());
1619 KJ_IF_MAYBE(conn_uptr_ptr, conn_uptr_maybe)
1620 {
1621 m_conn = std::move(*conn_uptr_ptr);
1622 }
1623 else
1624 {
1625 FLOW_LOG_FATAL("Session_vat_network [" << *this << "]: "
1626 "There is no reason for TwoPartyVatNetwork::connect() to fail here; "
1627 "it should have essentially just returned its `*this`. Yet it failed. Bug? Aborting.");
1628 std::abort();
1629 }
1630 }
1631
1632 /* Okay, this part (and related bits) is fairly shamelessly copied from TwoPartyVatNetwork' insides. Nothing
1633 * wrong with it, though, and it's not otherwise reusable. See m_disconnect_promise doc header for explanation. */
1634 auto paf = newPromiseAndFulfiller<void>();
1635 m_disconnect_promise = paf.promise.fork();
1636 m_disconnect_fulfiller.m_fulfiller = kj::mv(paf.fulfiller);
1638} // Session_vat_network::Session_vat_network()
1639
1640template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1642 (flow::log::Logger* logger_ptr, kj::AsyncIoContext* kj_io,
1643 Shm_lender_borrower_ptr&& shm_enabled_session,
1644 Native_handle&& bidir_transport, unsigned int hndl_transport_limit_or_0) :
1645
1646 Session_vat_network(logger_ptr, kj_io,
1647 Shm_lender_borrower::S_IS_SRV_ELSE_CLI,
1648 std::move(shm_enabled_session),
1649 // Aliasing ctor: point to the arena, but share the session's ownership (the arena belongs to it).
1650 shm_enabled_session ? Shm_arena_ptr{shm_enabled_session, shm_enabled_session->session_shm()}
1651 : Shm_arena_ptr{}, // Special mode.
1652 std::move(bidir_transport), hndl_transport_limit_or_0)
1653{
1654 // Cool.
1655}
1656
1657template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1659{
1660 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1661 "Shutting down. Any RPC-system relying on us must have already also shut down; or there might be "
1662 "trouble.");
1663}
1664
1665template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1666kj::Own<Rpc_msg_out>
1668{
1669 using ::capnp::sizeInWords;
1670
1671 /* Pre-process by dropping in our own Rpc_msg_out impl (in terms of the vanilla one from TwoPartyVatNetwork).
1672 *
1673 * As for message/segment size guesses:
1674 * - For the byte-streamed (little, SHM-handle-bearing) message:
1675 * - See m_msg_out_max_sz_words doc header for background.
1676 * - Once a message has gone out, we know what to guess.
1677 * - The first time spitball an okay -- not tight -- value (it'll go down next time).
1678 * - Pass-on the "user" out-message size guess to our Rpc_msg_out impl, so it can try to efficiently
1679 * allocate in SHM. */
1680 return m_shm_lnd_brw
1681 ? kj::heap<Rpc_msg_out_impl>
1682 (m_conn->newOutgoingMessage((m_msg_out_max_sz_words == 0)
1683 ? ((Builder_base::S_MAX_SERIALIZATION_SEGMENT_SZ / sizeof(::capnp::word))
1684 + sizeInWords<schema::detail::CapnpRpcMsgTopSerialization>())
1685 : m_msg_out_max_sz_words),
1686 seg0_word_sz,
1687 this)
1688 : m_conn->newOutgoingMessage(seg0_word_sz); // <-- Special mode. Just do what TwoPartyVatNetwork would.
1689}
1690
1691template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1692kj::Promise<kj::Maybe<kj::Own<Rpc_msg_in>>>
1694{
1695 /* The normal TwoPartyVatNetwork::receiveIncomingMessage() returns a promise to async-read bits off wire
1696 * if possible/appropriate; essentially yielding on success a heap-stored capnp::MessageReader + 0+ native-handles;
1697 * and if so basically bundling them in its impl of Rpc_msg_in, which provides accessors via its `virtual` impl
1698 * methods like getBody(). That's all just fine for our purposes! Just that MessageReader will contain
1699 * merely an encoding of a handle-into-SHM, where the actual message is stored; so we ourselves implement
1700 * Rpc_msg_in; store the aforementioned vanilla Rpc_msg_in (with the handle) inside; and have our getBody() access
1701 * the in-SHM serialization (according to that SHM-handle) instead... mission accomplished. */
1702 auto vanilla_result = m_conn->receiveIncomingMessage();
1703 if (!m_shm_lnd_brw)
1704 {
1705 return vanilla_result; // Special mode! Just do the vanilla thing.
1706 }
1707 // else: Actually do something useful.
1708
1709 return vanilla_result.then([this](kj::Maybe<kj::Own<Rpc_msg_in>>&& msg_uptr_maybe)
1710 -> kj::Maybe<kj::Own<Rpc_msg_in>>
1711 {
1712 KJ_IF_MAYBE(msg_uptr_ptr, msg_uptr_maybe)
1713 {
1714 // Post-process by dropping in our own Rpc_msg_in impl.
1715 return kj::heap<Rpc_msg_in_impl>(kj::mv(*msg_uptr_ptr), this);
1716 }
1717 // else
1718 return nullptr;
1719 });
1720} // Session_vat_network::receiveIncomingMessage()
1721
1722template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1724{
1725 /* This impl is ~lifted from TwoPartyVatNetwork::connect(). What's interesting is that accept() is coded to
1726 * only work on SERVER; while connect() will work on either side (as long as the target `ref` is properly specified
1727 * to be the opposing peer's). One might expect connect() would work only on client side, yet this supports
1728 * both (e.g., both sides can connect()). This might just be an inconsistency (a bit of dead code of sorts),
1729 * or it might be a subtle necessity. I've seen some empirical evidence it is the latter (details omitted).
1730 * @todo Look into it (if only for a better understanding). */
1731 if (ref.getSide() == (m_srv_else_cli ? ::capnp::rpc::twoparty::Side::SERVER
1732 : ::capnp::rpc::twoparty::Side::CLIENT))
1733 {
1734 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1735 "Session_vat_network::connect() invoked (perhaps by a capnp RPC-system) "
1736 "targeting our own side: failure. This appears, empirically, to be normal "
1737 "behavior by RpcSystem-et-al, hence this message is not a WARNING.");
1738 return nullptr; // Trying to connect to self: nope.
1739 }
1740 // else
1741 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1742 "Session_vat_network::connect() invoked (perhaps by a capnp RPC-system) "
1743 "properly targeting the other side: success.");
1744 return as_connection();
1745}
1746
1747template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1749{
1750 /* This is, like connect(), essentially copy/pasted from TwoPartyVatNetwork. How it is used (capnp-1.0.2):
1751 * every RpcSystem -- client-side (makeRpcClient()) or server-side -- starts an accept loop in its ctor:
1752 * `network.accept().then([](conn) { <register conn>; <loop again>; })`. It is strictly sequential: the next
1753 * accept() is issued only once the previous one's promise resolves. Hence:
1754 * - Server node: 1st accept() yields our one and only connection; the 2nd accept() yields a promise that never
1755 * resolves; hence a 3rd is never issued. The loop simply waits forever, as intended in a two-party world.
1756 * - Client node: the 1st accept() already yields the never-resolving promise; same eternal wait. (So a client-side
1757 * accept() call is not a misuse but the normal course of events.)
1758 * The never-resolving promise is thus the two-party idiom for "there will be no more connections; do not error out
1759 * the accept loop either." */
1760
1761 if (m_srv_else_cli && (!m_accepted))
1762 {
1763 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1764 "Session_vat_network::accept() invoked (perhaps by a capnp RPC-system) "
1765 "1st time for node with ID = [srv_else_cli=1]: success.");
1766 m_accepted = true;
1767 return as_connection(); // Pre-fulfilled Promise.
1768 }
1769 // else
1770
1771 if (m_srv_else_cli) // && m_accepted
1772 {
1773 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1774 "Session_vat_network::accept() invoked (perhaps by a capnp RPC-system) "
1775 "in node with ID = [srv_else_cli=1] after earlier having done so already; "
1776 "returning eternal promise. This appears, empirically, to be normal "
1777 "behavior by RpcSystem-et-al, hence this message is not a WARNING.");
1778 }
1779 else // if (cli)
1780 {
1781 FLOW_LOG_INFO("Session_vat_network [" << *this << "]: "
1782 "Session_vat_network::accept() invoked (perhaps by a capnp RPC-system) "
1783 "in node with ID = [srv_else_cli=0]: returning eternal promise.");
1784 }
1785
1786 /* Create a promise that will never be fulfilled. We must keep the fulfiller alive: destroying it would *reject*
1787 * the promise (broken-promise exception), which would land in the RpcSystem accept loop's error handler.
1788 * Per the above this branch executes at most once per *this (the loop awaits this promise forever), so the
1789 * assignment below never overwrites a live fulfiller. (Were it to somehow happen anyway, the effect would be that
1790 * rejection -- logged by RpcSystem as an error -- not undefined behavior.) */
1791 auto paf = kj::newPromiseAndFulfiller<kj::Own<Rpc_conn>>();
1792 m_accept_fulfiller = std::move(paf.fulfiller);
1793 return std::move(paf.promise);
1794} // Session_vat_network::accept()
1795
1796template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1798{
1799 assert(((m_streaming_flow_window_sz % 1024) == 0)
1800 && "How did m_streaming_flow_window_sz get set to a non-multiple of Ki? Maintenance drift bug?");
1801 return m_streaming_flow_window_sz / 1024;
1802}
1803
1804template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1806{
1807 KJ_REQUIRE(limit_ki > 0,
1808 "limit_ki=0 is forbidden by contract");
1809 /* (Bad arg. We said "It must be positive" without specifying enforcement. A KJ_REQUIRE() seems reasonably KJ-style.
1810 * The Flow convention for plainly-absurd in-args in user-facing APIs is a mere assert() -- for better or worse,
1811 * a defense is out of scope here -- but here KJ_REQUIRE() works fine, and we are in the land of KJ
1812 * event loops/promises/exceptions, so consistency-wise it fits.) */
1813
1814 FLOW_LOG_TRACE("Session_vat_network [" << *this << "]: User setting streaming flow control window to "
1815 "[" << limit_ki << "Ki]; replacing current setting [" << streaming_flow_window_ki() << "Ki].");
1816
1817 m_streaming_flow_window_sz = (limit_ki * 1024);
1818}
1819
1820template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1822{
1823 return m_streaming_flow_window_sz;
1824}
1825
1826template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1828{
1829 using ::capnp::RpcFlowController;
1830
1831 /* Regarding the true-clause of the following ternary:
1832 * See streaming_flow_window_ki() doc header(s) for high-level background; and Rpc_msg_out::sizeInWords()
1833 * body for a slightly deeper dive. Given all that: in the main (non-special/fallback) mode, at any
1834 * given point the window limit shall be streaming_flow_window_ki(), WindowGetter::getWindow() impl. */
1835
1836 return m_shm_lnd_brw ? RpcFlowController::newVariableWindowController(*this)
1837 : m_conn->newStream(); // Special mode: do what TwoPartyVatNetwork does.
1838}
1839
1840template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1842{
1843 return m_conn->getPeerVatId(); // Really it's just SERVER or CLIENT originally depending on m_srv_else_cli.
1844}
1845
1846template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1848{
1849 return m_conn->shutdown();
1850}
1851
1852template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1854{
1855 /* If this goes from 0->1, then a returned guy like us going 1->0 triggers disposer m_disconnect_fulfiller to run.
1856 * Thus on_disconnect() works. And/or see m_disconnect_promise doc header. */
1857 ++m_disconnect_fulfiller.m_ref_count;
1858
1859 return kj::Own<Rpc_conn>(this, m_disconnect_fulfiller);
1860}
1861
1862template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1864{
1865 return m_disconnect_promise.addBranch();
1866}
1867
1868template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1870{
1871 return on_disconnect();
1872}
1873
1874// Odds and ends.
1875
1876/// @cond
1877// -^- Doxygen, please ignore the following. Doxygen 1.9.4 doesn't recognize this/gives weird error. @todo Fix.
1878
1879template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1880void
1883{
1884 if ((--m_ref_count) == 0)
1885 {
1886 m_fulfiller->fulfill();
1887 }
1888}
1889
1890// -v- Doxygen, please stop ignoring.
1891/// @endcond
1892
1893template<typename Shm_lender_borrower_t, typename Shm_arena_t>
1895{
1896 return os << '@' << &val;
1897}
1898
1899} // namespace ipc::transport::struc::shm::rpc
static constexpr size_t S_MAX_SERIALIZATION_SEGMENT_SZ
A message of this size will be able to completely contain the (single) segment emitted by Builder::em...
Definition: serializer.hpp:50
A capnp::MessageBuilder used by shm::Builder and for general capnp users: similar to a MallocMessageB...
A capnp::MessageReader used by shm::Reader and for general capnp users: able to access capnp-serializ...
Inner impl class in Session_vat_network, implementing capnp::IncomingRpcMessage, which makes up rough...
kj::Own< Rpc_msg_in > m_msg
The vanilla TwoPartyVatNetwork-generated Rpc_msg_in, storing the capnp-encoded SHM-handle which m_cap...
kj::ArrayPtr< kj::AutoCloseFd > getAttachedFds() override
Implements Rpc_msg_in API.
::capnp::AnyPointer::Reader getBody() override
Implements Rpc_msg_in API.
Rpc_msg_in_impl(kj::Own< Rpc_msg_in > &&msg, Session_vat_network *daddy)
Saves the TwoPartyVatNetwork Rpc_msg_in impl, as created by Session_vat_network::m_network,...
const Shm_lender_borrower_ptr m_shm_lnd_brw
Copy of Session_vat_network::m_shm_lnd_brw: keeps the lender-borrower – the executor of ....
Capnp_message_reader< Shm_arena > m_capnp_msg_in_shm
The capnp::MessageReader that stores the capnp-serialization needed by the user of a *this (which we ...
Inner impl class in Session_vat_network, implementing capnp::OutgoingRpcMessage, which makes up rough...
const Shm_lender_borrower_ptr m_shm_lnd_brw
Copy of Session_vat_network::m_shm_lnd_brw: keeps the lender-borrower alive until *this is destroyed,...
Rpc_msg_out_impl(kj::Own< Rpc_msg_out > &&msg, size_t seg0_word_sz, Session_vat_network *daddy)
Saves the TwoPartyVatNetwork Rpc_msg_out impl, as created by Session_vat_network::m_network,...
kj::Own< Rpc_msg_out > m_msg
The vanilla TwoPartyVatNetwork-generated Rpc_msg_out, where we shall store capnp-encoded SHM-handle f...
Capnp_message_builder< Shm_arena > m_capnp_msg_in_shm
The capnp::MessageBuilder that stores the capnp-serialization needed by the user of a *this (which we...
const Shm_arena_ptr m_shm_arena
Copy of Session_vat_network::m_shm_arena; see m_shm_lnd_brw.
::capnp::AnyPointer::Builder getBody() override
Implements Rpc_msg_out API.
void setFds(kj::Array< int > fds) override
Implements Rpc_msg_out API.
Session_vat_network *const m_daddy
The containing Session_vat_network.
The core class of Flow-IPC's zero-copyification-of-capnp-RPC module, Session_vat_network implements c...
kj::Promise< void > on_disconnect()
Yields a promise that is fulfilled when all Rpc_conns returned by connect() and accept() are allowed ...
Shm_arena_t Shm_arena
Short-hand for Shm_arena_t template parameter.
Shm_lender_borrower_t Shm_lender_borrower
Short-hand for Shm_lender_borrower_t template parameter.
boost::shared_ptr< Shm_arena > Shm_arena_ptr
Shared handle to a Shm_arena. See Shm_lender_borrower_ptr.
size_t streaming_flow_window_ki() const
The -> stream flow control window limit, in kebibytes, used by capnp-RPC w/r/t the sum of in-SHM stre...
kj::Promise< kj::Maybe< kj::Own< Rpc_msg_in > > > receiveIncomingMessage() override
Implements capnp::VatNetwork API.
Kj_stream_of_blob_hndls_ptr m_kj_stream_of_blobs_hndls
If in ctor hndl_transport_limit_or_0 == 0, this is null; else this is the low-level byte-stream used ...
kj::ForkedPromise< void > m_disconnect_promise
See on_disconnect(): the promise returned there, whose fulfillment indicates that for each successful...
Session_vat_network(flow::log::Logger *logger_ptr, kj::AsyncIoContext *kj_io, Shm_lender_borrower_ptr &&shm_enabled_session, Native_handle &&bidir_transport, unsigned int hndl_transport_limit_or_0=S_N_MAX_INCOMING_FDS)
Constructs the VatNetwork, making it ready for use in an Rpc_system: mainstream (session) form.
kj::Promise< void > onDisconnect()
Identical to on_disconnect().
kj::Promise< void > shutdown() override
Implements capnp::VatNetwork API.
boost::movelib::unique_ptr<::capnp::BufferedMessageStream > m_capnp_msg_stream
The next layer on top of m_kj_stream_of_blobs*, this is the capnp-encoded-message (plus possibly nati...
size_t getWindow() override
Implements capnp::RpcFlowController::WindowGetter API.
boost::shared_ptr< Shm_lender_borrower > Shm_lender_borrower_ptr
Shared handle to a Shm_lender_borrower: how a ctor takes it, and how *this and each capnp-message it ...
kj::Own< Rpc_conn > as_connection()
The core of connect() and accept(), used whenever those actually succeed: returns *this as an Rpc_con...
kj::Promise< kj::Own< Rpc_conn > > accept() override
Implements capnp::VatNetwork API.
kj::Own< kj::PromiseFulfiller< kj::Own< Rpc_conn > > > m_accept_fulfiller
Fulfiller for the promise returned by accept() on the client side, or the second call on the server s...
const bool m_srv_else_cli
Node ID: we are either designated server or client.
virtual ~Session_vat_network()
Boring virtual destructor. It logs.
::capnp::TwoPartyVatNetworkBase Base
Alias for our base class which incidentally is the interface VatNetwork<...>.
kj::Own< Rpc_conn > m_conn
This is m_network essentially cast as an Rpc_conn impl: see as_connection() body.
std::optional<::capnp::TwoPartyVatNetwork > m_network
The Big Kahuna of our impl, this is the TwoPartyVatNetwork we reuse to transmit messages back and for...
const Shm_arena_ptr m_shm_arena
The user-supplied object with .construct<T>() method that enables the allocation/construction of SHM-...
Vat_id::Reader getPeerVatId() override
Implements capnp::VatNetwork API.
const Shm_lender_borrower_ptr m_shm_lnd_brw
The user-supplied object with .borrow_object() and .lend_object() methods that enable the sharing of ...
kj::AsyncIoContext *const m_kj_io
KJ event loop context.
kj::Own< Rpc_msg_out > newOutgoingMessage(unsigned int seg0_word_sz) override
Implements capnp::VatNetwork API.
kj::Own<::capnp::RpcFlowController > newStream() override
Implements capnp::VatNetwork API.
Kj_stream_of_blobs_ptr m_kj_stream_of_blobs
If in ctor hndl_transport_limit_or_0 != 0, this is null; else this is the low-level byte-stream used ...
kj::Maybe< kj::Own< Rpc_conn > > connect(Vat_id::Reader ref) override
Implements capnp::VatNetwork API.
kj::Own< kj::AsyncCapabilityStream > Kj_stream_of_blob_hndls_ptr
Convenience alias for how capnp-RPC passes-around handle-transport-capable byte streams.
bool m_accepted
Guards from allowing 2nd accept() (there is only 1 connection on server side in a 2-party network).
Fulfiller_disposer m_disconnect_fulfiller
Disposer for all Own<Rpc_conn>s returned by connect() and accept().
kj::Own< kj::AsyncIoStream > Kj_stream_of_blobs_ptr
Convenience alias for how capnp-RPC passes-around non-handle-transport-capable byte streams.
size_t m_streaming_flow_window_sz
The value returned by streaming_flow_window_ki() except in bytes.
size_t m_msg_out_max_sz_words
Starting at 0 (unknown), this is both (1) the first-segment-size (in capnp::words) for the next outgo...
Small group of miscellaneous utilities to ease work with capnp (Cap'n Proto), joining its capnp names...
Builder< ipc::shm::classic::Pool_arena > Builder
Convenience alias: transport::struc::shm::Builder that works with boost.ipc.shm pools from ipc::shm::...
Definition: classic_fwd.hpp:36
Reader< ipc::shm::classic::Pool_arena > Reader
Convenience alias: transport::struc::shm::Reader that works with boost.ipc.shm pools from ipc::shm::c...
Definition: classic_fwd.hpp:39
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.
Ostreamable_capnp_brief< Capnp_reader > ostreamable_capnp_brief(const Capnp_reader &capnp_reader)
Convenience function that returns an object passable to ostream<< to print a one-line/potentially-tru...
Definition: util.hpp:71
Ostreamable_capnp_full< Capnp_reader > ostreamable_capnp_full(const Capnp_reader &capnp_reader)
Convenience function that returns an object passable to ostream<< to print a multi-line/pretty-printe...
Definition: util.hpp:77
::capnp::MessageReader Capnp_msg_reader_interface
Alias for capnp's MessageReader interface. Rationale: as part of our API, we use our identifier style...
Definition: struc_fwd.hpp:221
Native_handle disowned_native_handle(Own_native_handle &&src) noexcept
Returns Native_handle constructed by relieving an existing auto-closing Own_native_handle of its owne...
boost::scope::unique_resource< Native_handle, Native_handle::Closer, Native_handle > Own_native_handle
A unique_resource wrapper for Native_handle useful to avoid descriptor leaks and double-closing; can ...
Log_component
The flow::log::Component payload enumeration containing various log components used by Flow-IPC inter...
Definition: common.hpp:338
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
Disposer for Own<Rpc_conn> returned by connect() and accept() which (1) does not delete anything and ...
kj::Own< kj::PromiseFulfiller< void > > m_fulfiller
It is the fulfiller of Session_vat_network::m_disconnect_promise.
void disposeImpl(void *) const override
Implements interface by noting that the tracked Rpc_conn has one fewer outstanding handle; and if tha...
unsigned int m_ref_count
Number of times Session_vat_network::as_connection() executed minus the number of the returned Own<Rp...
Non-template base for Session_vat_network (constants, types, and what-not).
static constexpr size_t S_STREAMING_FLOW_WINDOW_KI
Reasonable default for Session_vat_network::streaming_flow_window_ki(). See the doc header for the la...
static constexpr unsigned int S_N_MAX_INCOMING_FDS
Reasonable default for hndl_transport_limit_or_0 arg to Session_vat_network ctors,...
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
handle_t m_native_handle
The native handle (possibly equal to S_NULL_HANDLE), the exact payload of this Native_handle.