capnproto

FORK: Cap'n Proto serialization/RPC system - core tools and C++ library
git clone https://git.neptards.moe/neptards/capnproto.git
Log | Files | Refs | README | LICENSE

cxxrpc.md (18583B)


      1 ---
      2 layout: page
      3 title: C++ RPC
      4 ---
      5 
      6 # C++ RPC
      7 
      8 The Cap'n Proto C++ RPC layer sits on top of the [serialization layer](cxx.html) and implements
      9 the [RPC protocol](rpc.html).
     10 
     11 ## Current Status
     12 
     13 As of version 0.4, Cap'n Proto's C++ RPC implementation is a [Level 1](rpc.html#protocol-features)
     14 implementation.  Persistent capabilities, three-way introductions, and distributed equality are
     15 not yet implemented.
     16 
     17 ## Sample Code
     18 
     19 The [Calculator example](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples) implements
     20 a fully-functional Cap'n Proto client and server.
     21 
     22 ## KJ Concurrency Framework
     23 
     24 RPC naturally requires a notion of concurrency.  Unfortunately,
     25 [all concurrency models suck](https://web.archive.org/web/20170718202612/https://plus.google.com/+KentonVarda/posts/D95XKtB5DhK).
     26 
     27 Cap'n Proto's RPC is based on the [KJ library](cxx.html#kj-library)'s event-driven concurrency
     28 framework.  The core of the KJ asynchronous framework (events, promises, callbacks) is defined in
     29 `kj/async.h`, with I/O interfaces (streams, sockets, networks) defined in `kj/async-io.h`.
     30 
     31 ### Event Loop Concurrency
     32 
     33 KJ's concurrency model is based on event loops.  While multiple threads are allowed, each thread
     34 must have its own event loop.  KJ discourages fine-grained interaction between threads as
     35 synchronization is expensive and error-prone.  Instead, threads are encouraged to communicate
     36 through Cap'n Proto RPC.
     37 
     38 KJ's event loop model bears a lot of similarity to the JavaScript concurrency model.  Experienced
     39 JavaScript hackers -- especially node.js hackers -- will feel right at home.
     40 
     41 _As of version 0.4, the only supported way to communicate between threads is over pipes or
     42 socketpairs.  This will be improved in future versions.  For now, just set up an RPC connection
     43 over that socketpair.  :)_
     44 
     45 ### Promises
     46 
     47 Function calls that do I/O must do so asynchronously, and must return a "promise" for the
     48 result.  Promises -- also known as "futures" in some systems -- are placeholders for the results
     49 of operations that have not yet completed.  When the operation completes, we say that the promise
     50 "resolves" to a value, or is "fulfilled".  A promise can also be "rejected", which means an
     51 exception occurred.
     52 
     53 {% highlight c++ %}
     54 // Example promise-based interfaces.
     55 
     56 kj::Promise<kj::String> fetchHttp(kj::StringPtr url);
     57 // Asynchronously fetches an HTTP document and returns
     58 // the content as a string.
     59 
     60 kj::Promise<void> sendEmail(kj::StringPtr address,
     61     kj::StringPtr title, kj::StringPtr body);
     62 // Sends an e-mail to the given address with the given title
     63 // and body.  The returned promise resolves (to nothing) when
     64 // the message has been successfully sent.
     65 {% endhighlight %}
     66 
     67 As you will see, KJ promises are very similar to the evolving JavaScript promise standard, and
     68 much of the [wisdom around it](https://www.google.com/search?q=javascript+promises) can be directly
     69 applied to KJ promises.
     70 
     71 ### Callbacks
     72 
     73 If you want to do something with the result of a promise, you must first wait for it to complete.
     74 This is normally done by registering a callback to execute on completion.  Luckily, C++11 just
     75 introduced lambdas, which makes this far more pleasant than it would have been a few years ago!
     76 
     77 {% highlight c++ %}
     78 kj::Promise<kj::String> contentPromise =
     79     fetchHttp("http://example.com");
     80 
     81 kj::Promise<int> lineCountPromise =
     82     contentPromise.then([](kj::String&& content) {
     83   return countChars(content, '\n');
     84 });
     85 {% endhighlight %}
     86 
     87 The callback passed to `then()` takes the promised result as its parameter and returns a new value.
     88 `then()` itself returns a new promise for that value which the callback will eventually return.
     89 If the callback itself returns a promise, then `then()` actually returns a promise for the
     90 resolution of the latter promise -- that is, `Promise<Promise<T>>` is automatically reduced to
     91 `Promise<T>`.
     92 
     93 Note that `then()` consumes the original promise:  you can only call `then()` once.  This is true
     94 of all of the methods of `Promise`.  The only way to consume a promise in multiple places is to
     95 first "fork" it with the `fork()` method, which we don't get into here.  Relatedly, promises
     96 are linear types, which means they have move constructors but not copy constructors.
     97 
     98 ### Error Propagation
     99 
    100 `then()` takes an optional second parameter for handling errors.  Think of this like a `catch`
    101 block.
    102 
    103 {% highlight c++ %}
    104 kj::Promise<int> lineCountPromise =
    105     promise.then([](kj::String&& content) {
    106   return countChars(content, '\n');
    107 }, [](kj::Exception&& exception) {
    108   // Error!  Pretend the document was empty.
    109   return 0;
    110 });
    111 {% endhighlight %}
    112 
    113 Note that the KJ framework coerces all exceptions to `kj::Exception` -- the exception's description
    114 (as returned by `what()`) will be retained, but any type-specific information is lost.  Under KJ
    115 exception philosophy, exceptions always represent an error that should not occur under normal
    116 operation, and the only purpose of exceptions is to make software fault-tolerant.  In particular,
    117 the only reasonable ways to handle an exception are to try again, tell a human, and/or propagate
    118 to the caller.  To that end, `kj::Exception` contains information useful for reporting purposes
    119 and to help decide if trying again is reasonable, but typed exception hierarchies are not useful
    120 and not supported.
    121 
    122 It is recommended that Cap'n Proto code use the assertion macros in `kj/debug.h` to throw
    123 exceptions rather than use the C++ `throw` keyword.  These macros make it easy to add useful
    124 debug information to an exception and generally play nicely with the KJ framework.  In fact, you
    125 can even use these macros -- and propagate exceptions through promises -- if you compile your code
    126 with exceptions disabled.  See the headers for more information.
    127 
    128 ### Waiting
    129 
    130 It is illegal for code running in an event callback to wait, since this would stall the event loop.
    131 However, if you are the one responsible for starting the event loop in the first place, then KJ
    132 makes it easy to say "run the event loop until this promise resolves, then return the result".
    133 
    134 {% highlight c++ %}
    135 kj::EventLoop loop;
    136 kj::WaitScope waitScope(loop);
    137 
    138 kj::Promise<kj::String> contentPromise =
    139     fetchHttp("http://example.com");
    140 
    141 kj::String content = contentPromise.wait(waitScope);
    142 
    143 int lineCount = countChars(content, '\n');
    144 {% endhighlight %}
    145 
    146 Using `wait()` is common in high-level client-side code.  On the other hand, it is almost never
    147 used in servers.
    148 
    149 ### Cancellation
    150 
    151 If you discard a `Promise` without calling any of its methods, the operation it was waiting for
    152 is canceled, because the `Promise` itself owns that operation.  This means than any pending
    153 callbacks simply won't be executed.  If you need explicit notification when a promise is canceled,
    154 you can use its `attach()` method to attach an object with a destructor -- the destructor will be
    155 called when the promise either completes or is canceled.
    156 
    157 ### Lazy Execution
    158 
    159 Callbacks registered with `.then()` which aren't themselves asynchronous (i.e. they return a value,
    160 not a promise) by default won't execute unless the result is actually used -- they are executed
    161 "lazily". This allows the runtime to optimize by combining a series of .then() callbacks into one.
    162 
    163 To force a `.then()` callback to execute as soon as its input is available, do one of the
    164 following:
    165 
    166 * Add it to a `kj::TaskSet` -- this is usually the best choice. You can cancel all tasks in the set
    167   by destroying the `TaskSet`.
    168 * `.wait()` on it -- but this only works in a top-level wait scope, typically your program's main
    169   function.
    170 * Call `.eagerlyEvaluate()` on it. This returns a new `Promise`. You can cancel the task by
    171   destroying this `Promise` (without otherwise consuming it).
    172 * `.detach()` it. **WARNING:** `.detach()` is dangerous because there is no way to cancel a promise
    173   once it has been detached. This can make it impossible to safely tear down the execution
    174   environment, e.g. if the callback has captured references to other objects. It is therefore
    175   recommended to avoid `.detach()` except in carefully-controlled circumstances.
    176 
    177 ### Other Features
    178 
    179 KJ supports a number of primitive operations that can be performed on promises.  The complete API
    180 is documented directly in the `kj/async.h` header.  Additionally, see the `kj/async-io.h` header
    181 for APIs for performing basic network I/O -- although Cap'n Proto RPC users typically won't need
    182 to use these APIs directly.
    183 
    184 ## Generated Code
    185 
    186 Imagine the following interface:
    187 
    188 {% highlight capnp %}
    189 interface Directory {
    190   create @0 (name :Text) -> (file :File);
    191   open @1 (name :Text) -> (file :File);
    192   remove @2 (name :Text);
    193 }
    194 {% endhighlight %}
    195 
    196 `capnp compile` will generate code that looks like this (edited for readability):
    197 
    198 {% highlight c++ %}
    199 struct Directory {
    200   Directory() = delete;
    201 
    202   class Client;
    203   class Server;
    204 
    205   struct CreateParams;
    206   struct CreateResults;
    207   struct OpenParams;
    208   struct OpenResults;
    209   struct RemoveParams;
    210   struct RemoveResults;
    211   // Each of these is equivalent to what would be generated for
    212   // a Cap'n Proto struct with one field for each parameter /
    213   // result.
    214 };
    215 
    216 class Directory::Client
    217     : public virtual capnp::Capability::Client {
    218 public:
    219   Client(std::nullptr_t);
    220   Client(kj::Own<Directory::Server> server);
    221   Client(kj::Promise<Client> promise);
    222   Client(kj::Exception exception);
    223 
    224   capnp::Request<CreateParams, CreateResults> createRequest();
    225   capnp::Request<OpenParams, OpenResults> openRequest();
    226   capnp::Request<RemoveParams, RemoveResults> removeRequest();
    227 };
    228 
    229 class Directory::Server
    230     : public virtual capnp::Capability::Server {
    231 protected:
    232   typedef capnp::CallContext<CreateParams, CreateResults> CreateContext;
    233   typedef capnp::CallContext<OpenParams, OpenResults> OpenContext;
    234   typedef capnp::CallContext<RemoveParams, RemoveResults> RemoveContext;
    235   // Convenience typedefs.
    236 
    237   virtual kj::Promise<void> create(CreateContext context);
    238   virtual kj::Promise<void> open(OpenContext context);
    239   virtual kj::Promise<void> remove(RemoveContext context);
    240   // Methods for you to implement.
    241 };
    242 {% endhighlight %}
    243 
    244 ### Clients
    245 
    246 The generated `Client` type represents a reference to a remote `Server`.  `Client`s are
    247 pass-by-value types that use reference counting under the hood.  (Warning:  For performance
    248 reasons, the reference counting used by `Client`s is not thread-safe, so you must not copy a
    249 `Client` to another thread, unless you do it by means of an inter-thread RPC.)
    250 
    251 A `Client` can be implicitly constructed from any of:
    252 
    253 * A `kj::Own<Server>`, which takes ownership of the server object and creates a client that
    254   calls it.  (You can get a `kj::Own<T>` to a newly-allocated heap object using
    255   `kj::heap<T>(constructorParams)`; see `kj/memory.h`.)
    256 * A `kj::Promise<Client>`, which creates a client whose methods first wait for the promise to
    257   resolve, then forward the call to the resulting client.
    258 * A `kj::Exception`, which creates a client whose methods always throw that exception.
    259 * `nullptr`, which creates a client whose methods always throw.  This is meant to be used to
    260   initialize variables that will be initialized to a real value later on.
    261 
    262 For each interface method `foo()`, the `Client` has a method `fooRequest()` which creates a new
    263 request to call `foo()`.  The returned `capnp::Request` object has methods equivalent to a
    264 `Builder` for the parameter struct (`FooParams`), with the addition of a method `send()`.
    265 `send()` sends the RPC and returns a `capnp::RemotePromise<FooResults>`.
    266 
    267 This `RemotePromise` is equivalent to `kj::Promise<capnp::Response<FooResults>>`, but also has
    268 methods that allow pipelining.  Namely:
    269 
    270 * For each interface-typed result, it has a getter method which returns a `Client` of that type.
    271   Calling this client will send a pipelined call to the server.
    272 * For each struct-typed result, it has a getter method which returns an object containing pipeline
    273   getters for that struct's fields.
    274 
    275 In other words, the `RemotePromise` effectively implements a subset of the eventual results'
    276 `Reader` interface -- one that only allows access to interfaces and sub-structs.
    277 
    278 The `RemotePromise` eventually resolves to `capnp::Response<FooResults>`, which behaves like a
    279 `Reader` for the result struct except that it also owns the result message.
    280 
    281 {% highlight c++ %}
    282 Directory::Client dir = ...;
    283 
    284 // Create a new request for the `open()` method.
    285 auto request = dir.openRequest();
    286 request.setName("foo");
    287 
    288 // Send the request.
    289 auto promise = request.send();
    290 
    291 // Make a pipelined request.
    292 auto promise2 = promise.getFile().getSizeRequest().send();
    293 
    294 // Wait for the full results.
    295 auto promise3 = promise2.then(
    296     [](capnp::Response<File::GetSizeResults>&& response) {
    297   cout << "File size is: " << response.getSize() << endl;
    298 });
    299 {% endhighlight %}
    300 
    301 For [generic methods](language.html#generic-methods), the `fooRequest()` method will be a template;
    302 you must explicitly specify type parameters.
    303 
    304 ### Servers
    305 
    306 The generated `Server` type is an abstract interface which may be subclassed to implement a
    307 capability.  Each method takes a `context` argument and returns a `kj::Promise<void>` which
    308 resolves when the call is finished.  The parameter and result structures are accessed through the
    309 context -- `context.getParams()` returns a `Reader` for the parameters, and `context.getResults()`
    310 returns a `Builder` for the results.  The context also has methods for controlling RPC logistics,
    311 such as cancellation -- see `capnp::CallContext` in `capnp/capability.h` for details.
    312 
    313 Accessing the results through the context (rather than by returning them) is unintuitive, but
    314 necessary because the underlying RPC transport needs to have control over where the results are
    315 allocated.  For example, a zero-copy shared memory transport would need to allocate the results in
    316 the shared memory segment.  Hence, the method implementation cannot just create its own
    317 `MessageBuilder`.
    318 
    319 {% highlight c++ %}
    320 class DirectoryImpl final: public Directory::Server {
    321 public:
    322   kj::Promise<void> open(OpenContext context) override {
    323     auto iter = files.find(context.getParams().getName());
    324 
    325     // Throw an exception if not found.
    326     KJ_REQUIRE(iter != files.end(), "File not found.");
    327 
    328     context.getResults().setFile(iter->second);
    329 
    330     return kj::READY_NOW;
    331   }
    332 
    333   // Any method which we don't implement will simply throw
    334   // an exception by default.
    335 
    336 private:
    337   std::map<kj::StringPtr, File::Client> files;
    338 };
    339 {% endhighlight %}
    340 
    341 On the server side, [generic methods](language.html#generic-methods) are NOT templates. Instead,
    342 the generated code is exactly as if all of the generic parameters were bound to `AnyPointer`. The
    343 server generally does not get to know exactly what type the client requested; it must be designed
    344 to be correct for any parameterization.
    345 
    346 ## Initializing RPC
    347 
    348 Cap'n Proto makes it easy to start up an RPC client or server using the  "EZ RPC" classes,
    349 defined in `capnp/ez-rpc.h`.  These classes get you up and running quickly, but they hide a lot
    350 of details that power users will likely want to manipulate.  Check out the comments in `ez-rpc.h`
    351 to understand exactly what you get and what you miss.  For the purpose of this overview, we'll
    352 show you how to use EZ RPC to get started.
    353 
    354 ### Starting a client
    355 
    356 A client should typically look like this:
    357 
    358 {% highlight c++ %}
    359 #include <capnp/ez-rpc.h>
    360 #include "my-interface.capnp.h"
    361 #include <iostream>
    362 
    363 int main(int argc, const char* argv[]) {
    364   // We expect one argument specifying the server address.
    365   if (argc != 2) {
    366     std::cerr << "usage: " << argv[0] << " HOST[:PORT]" << std::endl;
    367     return 1;
    368   }
    369 
    370   // Set up the EzRpcClient, connecting to the server on port
    371   // 5923 unless a different port was specified by the user.
    372   capnp::EzRpcClient client(argv[1], 5923);
    373   auto& waitScope = client.getWaitScope();
    374 
    375   // Request the bootstrap capability from the server.
    376   MyInterface::Client cap = client.getMain<MyInterface>();
    377 
    378   // Make a call to the capability.
    379   auto request = cap.fooRequest();
    380   request.setParam(123);
    381   auto promise = request.send();
    382 
    383   // Wait for the result.  This is the only line that blocks.
    384   auto response = promise.wait(waitScope);
    385 
    386   // All done.
    387   std::cout << response.getResult() << std::endl;
    388   return 0;
    389 }
    390 {% endhighlight %}
    391 
    392 Note that for the connect address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6
    393 addresses.  Additionally, a Unix domain socket can be specified as `unix:` followed by a path name,
    394 and an abstract Unix domain socket can be specified as `unix-abstract:` followed by an identifier.
    395 
    396 For a more complete example, see the
    397 [calculator client sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-client.c++).
    398 
    399 ### Starting a server
    400 
    401 A server might look something like this:
    402 
    403 {% highlight c++ %}
    404 #include <capnp/ez-rpc.h>
    405 #include "my-interface-impl.h"
    406 #include <iostream>
    407 
    408 int main(int argc, const char* argv[]) {
    409   // We expect one argument specifying the address to which
    410   // to bind and accept connections.
    411   if (argc != 2) {
    412     std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]"
    413               << std::endl;
    414     return 1;
    415   }
    416 
    417   // Set up the EzRpcServer, binding to port 5923 unless a
    418   // different port was specified by the user.  Note that the
    419   // first parameter here can be any "Client" object or anything
    420   // that can implicitly cast to a "Client" object.  You can even
    421   // re-export a capability imported from another server.
    422   capnp::EzRpcServer server(kj::heap<MyInterfaceImpl>(), argv[1], 5923);
    423   auto& waitScope = server.getWaitScope();
    424 
    425   // Run forever, accepting connections and handling requests.
    426   kj::NEVER_DONE.wait(waitScope);
    427 }
    428 {% endhighlight %}
    429 
    430 Note that for the bind address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6
    431 addresses.  The special address `*` can be used to bind to the same port on all local IPv4 and
    432 IPv6 interfaces.  Additionally, a Unix domain socket can be specified as `unix:` followed by a
    433 path name, and an abstract Unix domain socket can be specified as `unix-abstract:` followed by
    434 an identifier.
    435 
    436 For a more complete example, see the
    437 [calculator server sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-server.c++).
    438 
    439 ## Debugging
    440 
    441 If you've written a server and you want to connect to it to issue some calls for debugging, perhaps
    442 interactively, the easiest way to do it is to use [pycapnp](http://jparyani.github.io/pycapnp/).
    443 We have decided not to add RPC functionality to the `capnp` command-line tool because pycapnp is
    444 better than anything we might provide.