annotate src/capnproto-0.6.0/doc/cxxrpc.md @ 148:b4bfdf10c4b3

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