Mercurial > hg > sv-dependency-builds
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/capnproto-git-20161025/doc/cxxrpc.md Tue Oct 25 11:17:01 2016 +0100 @@ -0,0 +1,422 @@ +--- +layout: page +title: C++ RPC +--- + +# 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 + +As of version 0.4, Cap'n Proto's C++ RPC implementation is a [Level 1](rpc.html#protocol-features) +implementation. Persistent capabilities, three-way introductions, and distributed equality are +not yet implemented. + +## Sample Code + +The [Calculator example](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples) implements +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). + +Cap'n Proto's RPC is based on the [KJ library](cxx.html#kj-library)'s event-driven concurrency +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 +socketpairs. This will be improved in future versions. For now, just set up an RPC connection +over that socketpair. :)_ + +### 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++ %} +// Example promise-based interfaces. + +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 +// and body. The returned promise resolves (to nothing) when +// the message has been successfully sent. +{% 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 +introduced lambdas, which makes this far more pleasant than it would have been a few years ago! + +{% highlight c++ %} +kj::Promise<kj::String> contentPromise = + fetchHttp("http://example.com"); + +kj::Promise<int> lineCountPromise = + contentPromise.then([](kj::String&& content) { + 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 = + promise.then([](kj::String&& content) { + 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. + +### Other Features + +KJ supports a number of primitive operations that can be performed on promises. The complete API +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 %} + +For [generic methods](language.html#generic-methods), the `fooRequest()` method will be a template; +you must explicitly specify type parameters. + +### 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 { + auto iter = files.find(context.getParams().getName()); + + // 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 %} + +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. + +## 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" +#include <iostream> + +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(); + + // Request the bootstrap capability from the server. + MyInterface::Client cap = client.getMain<MyInterface>(); + + // 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 +addresses. Additionally, a Unix domain socket can be specified as `unix:` followed by a path name. + +For a more complete example, see the +[calculator client sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-client.c++). + +### Starting a server + +A server might look something like this: + +{% highlight c++ %} +#include <capnp/ez-rpc.h> +#include "my-interface-impl.h" +#include <iostream> + +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 + // different port was specified by the user. Note that the + // first parameter here can be any "Client" object or anything + // that can implicitly cast to a "Client" object. You can even + // re-export a capability imported from another server. + capnp::EzRpcServer server(kj::heap<MyInterfaceImpl>(), argv[1], 5923); + auto& waitScope = server.getWaitScope(); + + // 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 +path name. + +For a more complete example, see the +[calculator server sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-server.c++). + +## 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/). +We have decided not to add RPC functionality to the `capnp` command-line tool because pycapnp is +better than anything we might provide.