cxxrpc.md 18.1 KB
Newer Older
Kenton Varda's avatar
Kenton Varda committed
1 2
---
layout: page
3
title: C++ RPC
Kenton Varda's avatar
Kenton Varda committed
4 5 6 7 8 9 10 11 12
---

# C++ RPC

The Cap'n Proto C++ RPC layer sits on top of the [serialization layer](cxx.html) and implements
the [RPC protocol](rpc.html).

## Current Status

13
As of version 0.4, Cap'n Proto's C++ RPC implementation is a [Level 1](rpc.html#protocol-features)
Kenton Varda's avatar
Kenton Varda committed
14 15 16 17 18
implementation.  Persistent capabilities, three-way introductions, and distributed equality are
not yet implemented.

## Sample Code

19
The [Calculator example](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples) implements
Kenton Varda's avatar
Kenton Varda committed
20 21 22 23 24 25 26
a fully-functional Cap'n Proto client and server.

## KJ Concurrency Framework

RPC naturally requires a notion of concurrency.  Unfortunately,
[all concurrency models suck](https://plus.google.com/u/0/+KentonVarda/posts/D95XKtB5DhK).

27
Cap'n Proto's RPC is based on the [KJ library](cxx.html#kj-library)'s event-driven concurrency
Kenton Varda's avatar
Kenton Varda committed
28 29 30 31 32 33 34 35 36 37 38 39 40 41
framework.  The core of the KJ asynchronous framework (events, promises, callbacks) is defined in
`kj/async.h`, with I/O interfaces (streams, sockets, networks) defined in `kj/async-io.h`.

### Event Loop Concurrency

KJ's concurrency model is based on event loops.  While multiple threads are allowed, each thread
must have its own event loop.  KJ discourages fine-grained interaction between threads as
synchronization is expensive and error-prone.  Instead, threads are encouraged to communicate
through Cap'n Proto RPC.

KJ's event loop model bears a lot of similarity to the Javascript concurrency model.  Experienced
Javascript hackers -- especially node.js hackers -- will feel right at home.

_As of version 0.4, the only supported way to communicate between threads is over pipes or
Kenton Varda's avatar
Kenton Varda committed
42 43
socketpairs.  This will be improved in future versions.  For now, just set up an RPC connection
over that socketpair.  :)_
Kenton Varda's avatar
Kenton Varda committed
44 45 46 47 48 49 50 51 52 53

### Promises

Function calls that do I/O must do so asynchronously, and must return a "promise" for the
result.  Promises -- also known as "futures" in some systems -- are placeholders for the results
of operations that have not yet completed.  When the operation completes, we say that the promise
"resolves" to a value, or is "fulfilled".  A promise can also be "rejected", which means an
exception occurred.

{% highlight c++ %}
Kenton Varda's avatar
Kenton Varda committed
54 55
// Example promise-based interfaces.

Kenton Varda's avatar
Kenton Varda committed
56 57 58 59 60 61 62
kj::Promise<kj::String> fetchHttp(kj::StringPtr url);
// Asynchronously fetches an HTTP document and returns
// the content as a string.

kj::Promise<void> sendEmail(kj::StringPtr address,
    kj::StringPtr title, kj::StringPtr body);
// Sends an e-mail to the given address with the given title
Kenton Varda's avatar
Kenton Varda committed
63 64
// and body.  The returned promise resolves (to nothing) when
// the message has been successfully sent.
Kenton Varda's avatar
Kenton Varda committed
65 66 67 68 69 70 71 72 73 74
{% endhighlight %}

As you will see, KJ promises are very similar to the evolving Javascript promise standard, and
much of the [wisdom around it](https://www.google.com/search?q=javascript+promises) can be directly
applied to KJ promises.

### Callbacks

If you want to do something with the result of a promise, you must first wait for it to complete.
This is normally done by registering a callback to execute on completion.  Luckily, C++11 just
Kenton Varda's avatar
Kenton Varda committed
75
introduced lambdas, which makes this far more pleasant than it would have been a few years ago!
Kenton Varda's avatar
Kenton Varda committed
76 77 78 79 80 81

{% highlight c++ %}
kj::Promise<kj::String> contentPromise =
    fetchHttp("http://example.com");

kj::Promise<int> lineCountPromise =
Jason Choy's avatar
Jason Choy committed
82
    contentPromise.then([](kj::String&& content) {
Kenton Varda's avatar
Kenton Varda committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
  return countChars(content, '\n');
});
{% endhighlight %}

The callback passed to `then()` takes the promised result as its parameter and returns a new value.
`then()` itself returns a new promise for that value which the callback will eventually return.
If the callback itself returns a promise, then `then()` actually returns a promise for the
resolution of the latter promise -- that is, `Promise<Promise<T>>` is automatically reduced to
`Promise<T>`.

Note that `then()` consumes the original promise:  you can only call `then()` once.  This is true
of all of the methods of `Promise`.  The only way to consume a promise in multiple places is to
first "fork" it with the `fork()` method, which we don't get into here.  Relatedly, promises
are linear types, which means they have move constructors but not copy constructors.

### Error Propagation

`then()` takes an optional second parameter for handling errors.  Think of this like a `catch`
block.

{% highlight c++ %}
kj::Promise<int> lineCountPromise =
105
    promise.then([](kj::String&& content) {
Kenton Varda's avatar
Kenton Varda committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
  return countChars(content, '\n');
}, [](kj::Exception&& exception) {
  // Error!  Pretend the document was empty.
  return 0;
});
{% endhighlight %}

Note that the KJ framework coerces all exceptions to `kj::Exception` -- the exception's description
(as returned by `what()`) will be retained, but any type-specific information is lost.  Under KJ
exception philosophy, exceptions always represent an error that should not occur under normal
operation, and the only purpose of exceptions is to make software fault-tolerant.  In particular,
the only reasonable ways to handle an exception are to try again, tell a human, and/or propagate
to the caller.  To that end, `kj::Exception` contains information useful for reporting purposes
and to help decide if trying again is reasonable, but typed exception hierarchies are not useful
and not supported.

It is recommended that Cap'n Proto code use the assertion macros in `kj/debug.h` to throw
exceptions rather than use the C++ `throw` keyword.  These macros make it easy to add useful
debug information to an exception and generally play nicely with the KJ framework.  In fact, you
can even use these macros -- and propagate exceptions through promises -- if you compile your code
with exceptions disabled.  See the headers for more information.

### Waiting

It is illegal for code running in an event callback to wait, since this would stall the event loop.
However, if you are the one responsible for starting the event loop in the first place, then KJ
makes it easy to say "run the event loop until this promise resolves, then return the result".

{% highlight c++ %}
kj::EventLoop loop;
kj::WaitScope waitScope(loop);

kj::Promise<kj::String> contentPromise =
    fetchHttp("http://example.com");

kj::String content = contentPromise.wait(waitScope);

int lineCount = countChars(content, '\n');
{% endhighlight %}

Using `wait()` is common in high-level client-side code.  On the other hand, it is almost never
used in servers.

### Cancellation

If you discard a `Promise` without calling any of its methods, the operation it was waiting for
is canceled, because the `Promise` itself owns that operation.  This means than any pending
callbacks simply won't be executed.  If you need explicit notification when a promise is canceled,
you can use its `attach()` method to attach an object with a destructor -- the destructor will be
called when the promise either completes or is canceled.

157 158 159
### Lazy Execution

Callbacks registered with `.then()` which aren't themselves asynchronous (i.e. they return a value,
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
not a promise) by default won't execute unless the result is actually used -- they are executed
"lazily". This allows the runtime to optimize by combining a series of .then() callbacks into one.

To force a `.then()` callback to execute as soon as its input is available, do one of the
following:

* Add it to a `kj::TaskSet` -- this is usually the best choice. You can cancel all tasks in the set
  by destroying the `TaskSet`.
* `.wait()` on it -- but this only works in a top-level wait scope, typically your program's main
  function.
* Call `.eagerlyEvaluate()` on it. This returns a new `Promise`. You can cancel the task by
  destroying this `Promise` (without otherwise consuming it).
* `.detach()` it. **WARNING:** `.detach()` is dangerous because there is no way to cancel a promise
  once it has been detached. This can make it impossible to safely tear down the execution
  environment, e.g. if the callback has captured references to other objects. It is therefore
  recommended to avoid `.detach()` except in carefully-controlled circumstances.
176

Kenton Varda's avatar
Kenton Varda committed
177 178
### Other Features

179
KJ supports a number of primitive operations that can be performed on promises.  The complete API
Kenton Varda's avatar
Kenton Varda committed
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
is documented directly in the `kj/async.h` header.  Additionally, see the `kj/async-io.h` header
for APIs for performing basic network I/O -- although Cap'n Proto RPC users typically won't need
to use these APIs directly.

## Generated Code

Imagine the following interface:

{% highlight capnp %}
interface Directory {
  create @0 (name :Text) -> (file :File);
  open @1 (name :Text) -> (file :File);
  remove @2 (name :Text);
}
{% endhighlight %}

`capnp compile` will generate code that looks like this (edited for readability):

{% highlight c++ %}
struct Directory {
  Directory() = delete;

  class Client;
  class Server;

  struct CreateParams;
  struct CreateResults;
  struct OpenParams;
  struct OpenResults;
  struct RemoveParams;
  struct RemoveResults;
  // Each of these is equivalent to what would be generated for
  // a Cap'n Proto struct with one field for each parameter /
  // result.
};

class Directory::Client
    : public virtual capnp::Capability::Client {
public:
  Client(std::nullptr_t);
  Client(kj::Own<Directory::Server> server);
  Client(kj::Promise<Client> promise);
  Client(kj::Exception exception);

  capnp::Request<CreateParams, CreateResults> createRequest();
  capnp::Request<OpenParams, OpenResults> openRequest();
  capnp::Request<RemoveParams, RemoveResults> removeRequest();
};

class Directory::Server
    : public virtual capnp::Capability::Server {
protected:
  typedef capnp::CallContext<CreateParams, CreateResults> CreateContext;
  typedef capnp::CallContext<OpenParams, OpenResults> OpenContext;
  typedef capnp::CallContext<RemoveParams, RemoveResults> RemoveContext;
  // Convenience typedefs.

  virtual kj::Promise<void> create(CreateContext context);
  virtual kj::Promise<void> open(OpenContext context);
  virtual kj::Promise<void> remove(RemoveContext context);
  // Methods for you to implement.
};
{% endhighlight %}

### Clients

The generated `Client` type represents a reference to a remote `Server`.  `Client`s are
pass-by-value types that use reference counting under the hood.  (Warning:  For performance
reasons, the reference counting used by `Client`s is not thread-safe, so you must not copy a
`Client` to another thread, unless you do it by means of an inter-thread RPC.)

A `Client` can be implicitly constructed from any of:

* A `kj::Own<Server>`, which takes ownership of the server object and creates a client that
  calls it.  (You can get a `kj::Own<T>` to a newly-allocated heap object using
  `kj::heap<T>(constructorParams)`; see `kj/memory.h`.)
* A `kj::Promise<Client>`, which creates a client whose methods first wait for the promise to
  resolve, then forward the call to the resulting client.
* A `kj::Exception`, which creates a client whose methods always throw that exception.
* `nullptr`, which creates a client whose methods always throw.  This is meant to be used to
  initialize variables that will be initialized to a real value later on.

For each interface method `foo()`, the `Client` has a method `fooRequest()` which creates a new
request to call `foo()`.  The returned `capnp::Request` object has methods equivalent to a
`Builder` for the parameter struct (`FooParams`), with the addition of a method `send()`.
`send()` sends the RPC and returns a `capnp::RemotePromise<FooResults>`.

This `RemotePromise` is equivalent to `kj::Promise<capnp::Response<FooResults>>`, but also has
methods that allow pipelining.  Namely:

* For each interface-typed result, it has a getter method which returns a `Client` of that type.
  Calling this client will send a pipelined call to the server.
* For each struct-typed result, it has a getter method which returns an object containing pipeline
  getters for that struct's fields.

In other words, the `RemotePromise` effectively implements a subset of the eventual results'
`Reader` interface -- one that only allows access to interfaces and sub-structs.

The `RemotePromise` eventually resolves to `capnp::Response<FooResults>`, which behaves like a
`Reader` for the result struct except that it also owns the result message.

{% highlight c++ %}
Directory::Client dir = ...;

// Create a new request for the `open()` method.
auto request = dir.openRequest();
request.setName("foo");

// Send the request.
auto promise = request.send();

// Make a pipelined request.
auto promise2 = promise.getFile().getSizeRequest().send();

// Wait for the full results.
auto promise3 = promise2.then(
    [](capnp::Response<File::GetSizeResults>&& response) {
  cout << "File size is: " << response.getSize() << endl;
});
{% endhighlight %}

301 302 303
For [generic methods](language.html#generic-methods), the `fooRequest()` method will be a template;
you must explicitly specify type parameters.

Kenton Varda's avatar
Kenton Varda committed
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
### Servers

The generated `Server` type is an abstract interface which may be subclassed to implement a
capability.  Each method takes a `context` argument and returns a `kj::Promise<void>` which
resolves when the call is finished.  The parameter and result structures are accessed through the
context -- `context.getParams()` returns a `Reader` for the parameters, and `context.getResults()`
returns a `Builder` for the results.  The context also has methods for controlling RPC logistics,
such as cancellation -- see `capnp::CallContext` in `capnp/capability.h` for details.

Accessing the results through the context (rather than by returning them) is unintuitive, but
necessary because the underlying RPC transport needs to have control over where the results are
allocated.  For example, a zero-copy shared memory transport would need to allocate the results in
the shared memory segment.  Hence, the method implementation cannot just create its own
`MessageBuilder`.

{% highlight c++ %}
class DirectoryImpl final: public Directory::Server {
public:
  kj::Promise<void> open(OpenContext context) override {
323
    auto iter = files.find(context.getParams().getName());
Kenton Varda's avatar
Kenton Varda committed
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

    // Throw an exception if not found.
    KJ_REQUIRE(iter != files.end(), "File not found.");

    context.getResults().setFile(iter->second);

    return kj::READY_NOW;
  }

  // Any method which we don't implement will simply throw
  // an exception by default.

private:
  std::map<kj::StringPtr, File::Client> files;
};
{% endhighlight %}

341 342 343 344 345
On the server side, [generic methods](language.html#generic-methods) are NOT templates. Instead,
the generated code is exactly as if all of the generic parameters were bound to `AnyPointer`. The
server generally does not get to know exactly what type the client requested; it must be designed
to be correct for any parameterization.

Kenton Varda's avatar
Kenton Varda committed
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
## Initializing RPC

Cap'n Proto makes it easy to start up an RPC client or server using the  "EZ RPC" classes,
defined in `capnp/ez-rpc.h`.  These classes get you up and running quickly, but they hide a lot
of details that power users will likely want to manipulate.  Check out the comments in `ez-rpc.h`
to understand exactly what you get and what you miss.  For the purpose of this overview, we'll
show you how to use EZ RPC to get started.

### Starting a client

A client should typically look like this:

{% highlight c++ %}
#include <capnp/ez-rpc.h>
#include "my-interface.capnp.h"
Kenton Varda's avatar
Kenton Varda committed
361
#include <iostream>
Kenton Varda's avatar
Kenton Varda committed
362 363 364 365 366 367 368 369 370 371 372 373 374

int main(int argc, const char* argv[]) {
  // We expect one argument specifying the server address.
  if (argc != 2) {
    std::cerr << "usage: " << argv[0] << " HOST[:PORT]" << std::endl;
    return 1;
  }

  // Set up the EzRpcClient, connecting to the server on port
  // 5923 unless a different port was specified by the user.
  capnp::EzRpcClient client(argv[1], 5923);
  auto& waitScope = client.getWaitScope();

375 376
  // Request the bootstrap capability from the server.
  MyInterface::Client cap = client.getMain<MyInterface>();
Kenton Varda's avatar
Kenton Varda committed
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392

  // Make a call to the capability.
  auto request = cap.fooRequest();
  request.setParam(123);
  auto promise = request.send();

  // Wait for the result.  This is the only line that blocks.
  auto response = promise.wait(waitScope);

  // All done.
  std::cout << response.getResult() << std::endl;
  return 0;
}
{% endhighlight %}

Note that for the connect address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6
393 394
addresses.  Additionally, a Unix domain socket can be specified as `unix:` followed by a path name,
and an abstract Unix domain socket can be specified as `unix-abstract:` followed by an identifier.
Kenton Varda's avatar
Kenton Varda committed
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++).
Kenton Varda's avatar
Kenton Varda committed
398 399 400 401 402 403 404 405

### Starting a server

A server might look something like this:

{% highlight c++ %}
#include <capnp/ez-rpc.h>
#include "my-interface-impl.h"
Kenton Varda's avatar
Kenton Varda committed
406
#include <iostream>
Kenton Varda's avatar
Kenton Varda committed
407 408 409 410 411 412 413 414 415 416 417

int main(int argc, const char* argv[]) {
  // We expect one argument specifying the address to which
  // to bind and accept connections.
  if (argc != 2) {
    std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]"
              << std::endl;
    return 1;
  }

  // Set up the EzRpcServer, binding to port 5923 unless a
418 419
  // different port was specified by the user.  Note that the
  // first parameter here can be any "Client" object or anything
Kenton Varda's avatar
Kenton Varda committed
420 421
  // that can implicitly cast to a "Client" object.  You can even
  // re-export a capability imported from another server.
422 423
  capnp::EzRpcServer server(kj::heap<MyInterfaceImpl>(), argv[1], 5923);
  auto& waitScope = server.getWaitScope();
Kenton Varda's avatar
Kenton Varda committed
424 425 426 427 428 429 430 431 432

  // Run forever, accepting connections and handling requests.
  kj::NEVER_DONE.wait(waitScope);
}
{% endhighlight %}

Note that for the bind address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6
addresses.  The special address `*` can be used to bind to the same port on all local IPv4 and
IPv6 interfaces.  Additionally, a Unix domain socket can be specified as `unix:` followed by a
433 434
path name, and an abstract Unix domain socket can be specified as `unix-abstract:` followed by
an identifier.
Kenton Varda's avatar
Kenton Varda committed
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 440 441 442

## Debugging

If you've written a server and you want to connect to it to issue some calls for debugging, perhaps
interactively, the easiest way to do it is to use [pycapnp](http://jparyani.github.io/pycapnp/).
Kenton Varda's avatar
Kenton Varda committed
443 444
We have decided not to add RPC functionality to the `capnp` command-line tool because pycapnp is
better than anything we might provide.