annotate src/capnproto-git-20161025/doc/cxxrpc.md @ 83:ae30d91d2ffe

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