Mercurial > hg > sv-dependency-builds
comparison src/capnproto-git-20161025/doc/cxxrpc.md @ 48:9530b331f8c1
Add Cap'n Proto source
| author | Chris Cannam <cannam@all-day-breakfast.com> |
|---|---|
| date | Tue, 25 Oct 2016 11:17:01 +0100 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 47:d93140aac40b | 48:9530b331f8c1 |
|---|---|
| 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://plus.google.com/u/0/+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 ### Other Features | |
| 158 | |
| 159 KJ supports a number of primitive operations that can be performed on promises. The complete API | |
| 160 is documented directly in the `kj/async.h` header. Additionally, see the `kj/async-io.h` header | |
| 161 for APIs for performing basic network I/O -- although Cap'n Proto RPC users typically won't need | |
| 162 to use these APIs directly. | |
| 163 | |
| 164 ## Generated Code | |
| 165 | |
| 166 Imagine the following interface: | |
| 167 | |
| 168 {% highlight capnp %} | |
| 169 interface Directory { | |
| 170 create @0 (name :Text) -> (file :File); | |
| 171 open @1 (name :Text) -> (file :File); | |
| 172 remove @2 (name :Text); | |
| 173 } | |
| 174 {% endhighlight %} | |
| 175 | |
| 176 `capnp compile` will generate code that looks like this (edited for readability): | |
| 177 | |
| 178 {% highlight c++ %} | |
| 179 struct Directory { | |
| 180 Directory() = delete; | |
| 181 | |
| 182 class Client; | |
| 183 class Server; | |
| 184 | |
| 185 struct CreateParams; | |
| 186 struct CreateResults; | |
| 187 struct OpenParams; | |
| 188 struct OpenResults; | |
| 189 struct RemoveParams; | |
| 190 struct RemoveResults; | |
| 191 // Each of these is equivalent to what would be generated for | |
| 192 // a Cap'n Proto struct with one field for each parameter / | |
| 193 // result. | |
| 194 }; | |
| 195 | |
| 196 class Directory::Client | |
| 197 : public virtual capnp::Capability::Client { | |
| 198 public: | |
| 199 Client(std::nullptr_t); | |
| 200 Client(kj::Own<Directory::Server> server); | |
| 201 Client(kj::Promise<Client> promise); | |
| 202 Client(kj::Exception exception); | |
| 203 | |
| 204 capnp::Request<CreateParams, CreateResults> createRequest(); | |
| 205 capnp::Request<OpenParams, OpenResults> openRequest(); | |
| 206 capnp::Request<RemoveParams, RemoveResults> removeRequest(); | |
| 207 }; | |
| 208 | |
| 209 class Directory::Server | |
| 210 : public virtual capnp::Capability::Server { | |
| 211 protected: | |
| 212 typedef capnp::CallContext<CreateParams, CreateResults> CreateContext; | |
| 213 typedef capnp::CallContext<OpenParams, OpenResults> OpenContext; | |
| 214 typedef capnp::CallContext<RemoveParams, RemoveResults> RemoveContext; | |
| 215 // Convenience typedefs. | |
| 216 | |
| 217 virtual kj::Promise<void> create(CreateContext context); | |
| 218 virtual kj::Promise<void> open(OpenContext context); | |
| 219 virtual kj::Promise<void> remove(RemoveContext context); | |
| 220 // Methods for you to implement. | |
| 221 }; | |
| 222 {% endhighlight %} | |
| 223 | |
| 224 ### Clients | |
| 225 | |
| 226 The generated `Client` type represents a reference to a remote `Server`. `Client`s are | |
| 227 pass-by-value types that use reference counting under the hood. (Warning: For performance | |
| 228 reasons, the reference counting used by `Client`s is not thread-safe, so you must not copy a | |
| 229 `Client` to another thread, unless you do it by means of an inter-thread RPC.) | |
| 230 | |
| 231 A `Client` can be implicitly constructed from any of: | |
| 232 | |
| 233 * A `kj::Own<Server>`, which takes ownership of the server object and creates a client that | |
| 234 calls it. (You can get a `kj::Own<T>` to a newly-allocated heap object using | |
| 235 `kj::heap<T>(constructorParams)`; see `kj/memory.h`.) | |
| 236 * A `kj::Promise<Client>`, which creates a client whose methods first wait for the promise to | |
| 237 resolve, then forward the call to the resulting client. | |
| 238 * A `kj::Exception`, which creates a client whose methods always throw that exception. | |
| 239 * `nullptr`, which creates a client whose methods always throw. This is meant to be used to | |
| 240 initialize variables that will be initialized to a real value later on. | |
| 241 | |
| 242 For each interface method `foo()`, the `Client` has a method `fooRequest()` which creates a new | |
| 243 request to call `foo()`. The returned `capnp::Request` object has methods equivalent to a | |
| 244 `Builder` for the parameter struct (`FooParams`), with the addition of a method `send()`. | |
| 245 `send()` sends the RPC and returns a `capnp::RemotePromise<FooResults>`. | |
| 246 | |
| 247 This `RemotePromise` is equivalent to `kj::Promise<capnp::Response<FooResults>>`, but also has | |
| 248 methods that allow pipelining. Namely: | |
| 249 | |
| 250 * For each interface-typed result, it has a getter method which returns a `Client` of that type. | |
| 251 Calling this client will send a pipelined call to the server. | |
| 252 * For each struct-typed result, it has a getter method which returns an object containing pipeline | |
| 253 getters for that struct's fields. | |
| 254 | |
| 255 In other words, the `RemotePromise` effectively implements a subset of the eventual results' | |
| 256 `Reader` interface -- one that only allows access to interfaces and sub-structs. | |
| 257 | |
| 258 The `RemotePromise` eventually resolves to `capnp::Response<FooResults>`, which behaves like a | |
| 259 `Reader` for the result struct except that it also owns the result message. | |
| 260 | |
| 261 {% highlight c++ %} | |
| 262 Directory::Client dir = ...; | |
| 263 | |
| 264 // Create a new request for the `open()` method. | |
| 265 auto request = dir.openRequest(); | |
| 266 request.setName("foo"); | |
| 267 | |
| 268 // Send the request. | |
| 269 auto promise = request.send(); | |
| 270 | |
| 271 // Make a pipelined request. | |
| 272 auto promise2 = promise.getFile().getSizeRequest().send(); | |
| 273 | |
| 274 // Wait for the full results. | |
| 275 auto promise3 = promise2.then( | |
| 276 [](capnp::Response<File::GetSizeResults>&& response) { | |
| 277 cout << "File size is: " << response.getSize() << endl; | |
| 278 }); | |
| 279 {% endhighlight %} | |
| 280 | |
| 281 For [generic methods](language.html#generic-methods), the `fooRequest()` method will be a template; | |
| 282 you must explicitly specify type parameters. | |
| 283 | |
| 284 ### Servers | |
| 285 | |
| 286 The generated `Server` type is an abstract interface which may be subclassed to implement a | |
| 287 capability. Each method takes a `context` argument and returns a `kj::Promise<void>` which | |
| 288 resolves when the call is finished. The parameter and result structures are accessed through the | |
| 289 context -- `context.getParams()` returns a `Reader` for the parameters, and `context.getResults()` | |
| 290 returns a `Builder` for the results. The context also has methods for controlling RPC logistics, | |
| 291 such as cancellation -- see `capnp::CallContext` in `capnp/capability.h` for details. | |
| 292 | |
| 293 Accessing the results through the context (rather than by returning them) is unintuitive, but | |
| 294 necessary because the underlying RPC transport needs to have control over where the results are | |
| 295 allocated. For example, a zero-copy shared memory transport would need to allocate the results in | |
| 296 the shared memory segment. Hence, the method implementation cannot just create its own | |
| 297 `MessageBuilder`. | |
| 298 | |
| 299 {% highlight c++ %} | |
| 300 class DirectoryImpl final: public Directory::Server { | |
| 301 public: | |
| 302 kj::Promise<void> open(OpenContext context) override { | |
| 303 auto iter = files.find(context.getParams().getName()); | |
| 304 | |
| 305 // Throw an exception if not found. | |
| 306 KJ_REQUIRE(iter != files.end(), "File not found."); | |
| 307 | |
| 308 context.getResults().setFile(iter->second); | |
| 309 | |
| 310 return kj::READY_NOW; | |
| 311 } | |
| 312 | |
| 313 // Any method which we don't implement will simply throw | |
| 314 // an exception by default. | |
| 315 | |
| 316 private: | |
| 317 std::map<kj::StringPtr, File::Client> files; | |
| 318 }; | |
| 319 {% endhighlight %} | |
| 320 | |
| 321 On the server side, [generic methods](language.html#generic-methods) are NOT templates. Instead, | |
| 322 the generated code is exactly as if all of the generic parameters were bound to `AnyPointer`. The | |
| 323 server generally does not get to know exactly what type the client requested; it must be designed | |
| 324 to be correct for any parameterization. | |
| 325 | |
| 326 ## Initializing RPC | |
| 327 | |
| 328 Cap'n Proto makes it easy to start up an RPC client or server using the "EZ RPC" classes, | |
| 329 defined in `capnp/ez-rpc.h`. These classes get you up and running quickly, but they hide a lot | |
| 330 of details that power users will likely want to manipulate. Check out the comments in `ez-rpc.h` | |
| 331 to understand exactly what you get and what you miss. For the purpose of this overview, we'll | |
| 332 show you how to use EZ RPC to get started. | |
| 333 | |
| 334 ### Starting a client | |
| 335 | |
| 336 A client should typically look like this: | |
| 337 | |
| 338 {% highlight c++ %} | |
| 339 #include <capnp/ez-rpc.h> | |
| 340 #include "my-interface.capnp.h" | |
| 341 #include <iostream> | |
| 342 | |
| 343 int main(int argc, const char* argv[]) { | |
| 344 // We expect one argument specifying the server address. | |
| 345 if (argc != 2) { | |
| 346 std::cerr << "usage: " << argv[0] << " HOST[:PORT]" << std::endl; | |
| 347 return 1; | |
| 348 } | |
| 349 | |
| 350 // Set up the EzRpcClient, connecting to the server on port | |
| 351 // 5923 unless a different port was specified by the user. | |
| 352 capnp::EzRpcClient client(argv[1], 5923); | |
| 353 auto& waitScope = client.getWaitScope(); | |
| 354 | |
| 355 // Request the bootstrap capability from the server. | |
| 356 MyInterface::Client cap = client.getMain<MyInterface>(); | |
| 357 | |
| 358 // Make a call to the capability. | |
| 359 auto request = cap.fooRequest(); | |
| 360 request.setParam(123); | |
| 361 auto promise = request.send(); | |
| 362 | |
| 363 // Wait for the result. This is the only line that blocks. | |
| 364 auto response = promise.wait(waitScope); | |
| 365 | |
| 366 // All done. | |
| 367 std::cout << response.getResult() << std::endl; | |
| 368 return 0; | |
| 369 } | |
| 370 {% endhighlight %} | |
| 371 | |
| 372 Note that for the connect address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6 | |
| 373 addresses. Additionally, a Unix domain socket can be specified as `unix:` followed by a path name. | |
| 374 | |
| 375 For a more complete example, see the | |
| 376 [calculator client sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-client.c++). | |
| 377 | |
| 378 ### Starting a server | |
| 379 | |
| 380 A server might look something like this: | |
| 381 | |
| 382 {% highlight c++ %} | |
| 383 #include <capnp/ez-rpc.h> | |
| 384 #include "my-interface-impl.h" | |
| 385 #include <iostream> | |
| 386 | |
| 387 int main(int argc, const char* argv[]) { | |
| 388 // We expect one argument specifying the address to which | |
| 389 // to bind and accept connections. | |
| 390 if (argc != 2) { | |
| 391 std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]" | |
| 392 << std::endl; | |
| 393 return 1; | |
| 394 } | |
| 395 | |
| 396 // Set up the EzRpcServer, binding to port 5923 unless a | |
| 397 // different port was specified by the user. Note that the | |
| 398 // first parameter here can be any "Client" object or anything | |
| 399 // that can implicitly cast to a "Client" object. You can even | |
| 400 // re-export a capability imported from another server. | |
| 401 capnp::EzRpcServer server(kj::heap<MyInterfaceImpl>(), argv[1], 5923); | |
| 402 auto& waitScope = server.getWaitScope(); | |
| 403 | |
| 404 // Run forever, accepting connections and handling requests. | |
| 405 kj::NEVER_DONE.wait(waitScope); | |
| 406 } | |
| 407 {% endhighlight %} | |
| 408 | |
| 409 Note that for the bind address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6 | |
| 410 addresses. The special address `*` can be used to bind to the same port on all local IPv4 and | |
| 411 IPv6 interfaces. Additionally, a Unix domain socket can be specified as `unix:` followed by a | |
| 412 path name. | |
| 413 | |
| 414 For a more complete example, see the | |
| 415 [calculator server sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-server.c++). | |
| 416 | |
| 417 ## Debugging | |
| 418 | |
| 419 If you've written a server and you want to connect to it to issue some calls for debugging, perhaps | |
| 420 interactively, the easiest way to do it is to use [pycapnp](http://jparyani.github.io/pycapnp/). | |
| 421 We have decided not to add RPC functionality to the `capnp` command-line tool because pycapnp is | |
| 422 better than anything we might provide. |
