cannam@62: --- cannam@62: layout: page cannam@62: title: C++ RPC cannam@62: --- cannam@62: cannam@62: # C++ RPC cannam@62: cannam@62: The Cap'n Proto C++ RPC layer sits on top of the [serialization layer](cxx.html) and implements cannam@62: the [RPC protocol](rpc.html). cannam@62: cannam@62: ## Current Status cannam@62: cannam@62: As of version 0.4, Cap'n Proto's C++ RPC implementation is a [Level 1](rpc.html#protocol-features) cannam@62: implementation. Persistent capabilities, three-way introductions, and distributed equality are cannam@62: not yet implemented. cannam@62: cannam@62: ## Sample Code cannam@62: cannam@62: The [Calculator example](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples) implements cannam@62: a fully-functional Cap'n Proto client and server. cannam@62: cannam@62: ## KJ Concurrency Framework cannam@62: cannam@62: RPC naturally requires a notion of concurrency. Unfortunately, cannam@62: [all concurrency models suck](https://plus.google.com/u/0/+KentonVarda/posts/D95XKtB5DhK). cannam@62: cannam@62: Cap'n Proto's RPC is based on the [KJ library](cxx.html#kj-library)'s event-driven concurrency cannam@62: framework. The core of the KJ asynchronous framework (events, promises, callbacks) is defined in cannam@62: `kj/async.h`, with I/O interfaces (streams, sockets, networks) defined in `kj/async-io.h`. cannam@62: cannam@62: ### Event Loop Concurrency cannam@62: cannam@62: KJ's concurrency model is based on event loops. While multiple threads are allowed, each thread cannam@62: must have its own event loop. KJ discourages fine-grained interaction between threads as cannam@62: synchronization is expensive and error-prone. Instead, threads are encouraged to communicate cannam@62: through Cap'n Proto RPC. cannam@62: cannam@62: KJ's event loop model bears a lot of similarity to the Javascript concurrency model. Experienced cannam@62: Javascript hackers -- especially node.js hackers -- will feel right at home. cannam@62: cannam@62: _As of version 0.4, the only supported way to communicate between threads is over pipes or cannam@62: socketpairs. This will be improved in future versions. For now, just set up an RPC connection cannam@62: over that socketpair. :)_ cannam@62: cannam@62: ### Promises cannam@62: cannam@62: Function calls that do I/O must do so asynchronously, and must return a "promise" for the cannam@62: result. Promises -- also known as "futures" in some systems -- are placeholders for the results cannam@62: of operations that have not yet completed. When the operation completes, we say that the promise cannam@62: "resolves" to a value, or is "fulfilled". A promise can also be "rejected", which means an cannam@62: exception occurred. cannam@62: cannam@62: {% highlight c++ %} cannam@62: // Example promise-based interfaces. cannam@62: cannam@62: kj::Promise fetchHttp(kj::StringPtr url); cannam@62: // Asynchronously fetches an HTTP document and returns cannam@62: // the content as a string. cannam@62: cannam@62: kj::Promise sendEmail(kj::StringPtr address, cannam@62: kj::StringPtr title, kj::StringPtr body); cannam@62: // Sends an e-mail to the given address with the given title cannam@62: // and body. The returned promise resolves (to nothing) when cannam@62: // the message has been successfully sent. cannam@62: {% endhighlight %} cannam@62: cannam@62: As you will see, KJ promises are very similar to the evolving Javascript promise standard, and cannam@62: much of the [wisdom around it](https://www.google.com/search?q=javascript+promises) can be directly cannam@62: applied to KJ promises. cannam@62: cannam@62: ### Callbacks cannam@62: cannam@62: If you want to do something with the result of a promise, you must first wait for it to complete. cannam@62: This is normally done by registering a callback to execute on completion. Luckily, C++11 just cannam@62: introduced lambdas, which makes this far more pleasant than it would have been a few years ago! cannam@62: cannam@62: {% highlight c++ %} cannam@62: kj::Promise contentPromise = cannam@62: fetchHttp("http://example.com"); cannam@62: cannam@62: kj::Promise lineCountPromise = cannam@62: contentPromise.then([](kj::String&& content) { cannam@62: return countChars(content, '\n'); cannam@62: }); cannam@62: {% endhighlight %} cannam@62: cannam@62: The callback passed to `then()` takes the promised result as its parameter and returns a new value. cannam@62: `then()` itself returns a new promise for that value which the callback will eventually return. cannam@62: If the callback itself returns a promise, then `then()` actually returns a promise for the cannam@62: resolution of the latter promise -- that is, `Promise>` is automatically reduced to cannam@62: `Promise`. cannam@62: cannam@62: Note that `then()` consumes the original promise: you can only call `then()` once. This is true cannam@62: of all of the methods of `Promise`. The only way to consume a promise in multiple places is to cannam@62: first "fork" it with the `fork()` method, which we don't get into here. Relatedly, promises cannam@62: are linear types, which means they have move constructors but not copy constructors. cannam@62: cannam@62: ### Error Propagation cannam@62: cannam@62: `then()` takes an optional second parameter for handling errors. Think of this like a `catch` cannam@62: block. cannam@62: cannam@62: {% highlight c++ %} cannam@62: kj::Promise lineCountPromise = cannam@62: promise.then([](kj::String&& content) { cannam@62: return countChars(content, '\n'); cannam@62: }, [](kj::Exception&& exception) { cannam@62: // Error! Pretend the document was empty. cannam@62: return 0; cannam@62: }); cannam@62: {% endhighlight %} cannam@62: cannam@62: Note that the KJ framework coerces all exceptions to `kj::Exception` -- the exception's description cannam@62: (as returned by `what()`) will be retained, but any type-specific information is lost. Under KJ cannam@62: exception philosophy, exceptions always represent an error that should not occur under normal cannam@62: operation, and the only purpose of exceptions is to make software fault-tolerant. In particular, cannam@62: the only reasonable ways to handle an exception are to try again, tell a human, and/or propagate cannam@62: to the caller. To that end, `kj::Exception` contains information useful for reporting purposes cannam@62: and to help decide if trying again is reasonable, but typed exception hierarchies are not useful cannam@62: and not supported. cannam@62: cannam@62: It is recommended that Cap'n Proto code use the assertion macros in `kj/debug.h` to throw cannam@62: exceptions rather than use the C++ `throw` keyword. These macros make it easy to add useful cannam@62: debug information to an exception and generally play nicely with the KJ framework. In fact, you cannam@62: can even use these macros -- and propagate exceptions through promises -- if you compile your code cannam@62: with exceptions disabled. See the headers for more information. cannam@62: cannam@62: ### Waiting cannam@62: cannam@62: It is illegal for code running in an event callback to wait, since this would stall the event loop. cannam@62: However, if you are the one responsible for starting the event loop in the first place, then KJ cannam@62: makes it easy to say "run the event loop until this promise resolves, then return the result". cannam@62: cannam@62: {% highlight c++ %} cannam@62: kj::EventLoop loop; cannam@62: kj::WaitScope waitScope(loop); cannam@62: cannam@62: kj::Promise contentPromise = cannam@62: fetchHttp("http://example.com"); cannam@62: cannam@62: kj::String content = contentPromise.wait(waitScope); cannam@62: cannam@62: int lineCount = countChars(content, '\n'); cannam@62: {% endhighlight %} cannam@62: cannam@62: Using `wait()` is common in high-level client-side code. On the other hand, it is almost never cannam@62: used in servers. cannam@62: cannam@62: ### Cancellation cannam@62: cannam@62: If you discard a `Promise` without calling any of its methods, the operation it was waiting for cannam@62: is canceled, because the `Promise` itself owns that operation. This means than any pending cannam@62: callbacks simply won't be executed. If you need explicit notification when a promise is canceled, cannam@62: you can use its `attach()` method to attach an object with a destructor -- the destructor will be cannam@62: called when the promise either completes or is canceled. cannam@62: cannam@62: ### Lazy Execution cannam@62: cannam@62: Callbacks registered with `.then()` which aren't themselves asynchronous (i.e. they return a value, cannam@62: not a promise) by default won't execute unless the result is actually used -- they are executed cannam@62: "lazily". This allows the runtime to optimize by combining a series of .then() callbacks into one. cannam@62: cannam@62: To force a `.then()` callback to execute as soon as its input is available, do one of the cannam@62: following: cannam@62: cannam@62: * Add it to a `kj::TaskSet` -- this is usually the best choice. You can cancel all tasks in the set cannam@62: by destroying the `TaskSet`. cannam@62: * `.wait()` on it -- but this only works in a top-level wait scope, typically your program's main cannam@62: function. cannam@62: * Call `.eagerlyEvaluate()` on it. This returns a new `Promise`. You can cancel the task by cannam@62: destroying this `Promise` (without otherwise consuming it). cannam@62: * `.detach()` it. **WARNING:** `.detach()` is dangerous because there is no way to cancel a promise cannam@62: once it has been detached. This can make it impossible to safely tear down the execution cannam@62: environment, e.g. if the callback has captured references to other objects. It is therefore cannam@62: recommended to avoid `.detach()` except in carefully-controlled circumstances. cannam@62: cannam@62: ### Other Features cannam@62: cannam@62: KJ supports a number of primitive operations that can be performed on promises. The complete API cannam@62: is documented directly in the `kj/async.h` header. Additionally, see the `kj/async-io.h` header cannam@62: for APIs for performing basic network I/O -- although Cap'n Proto RPC users typically won't need cannam@62: to use these APIs directly. cannam@62: cannam@62: ## Generated Code cannam@62: cannam@62: Imagine the following interface: cannam@62: cannam@62: {% highlight capnp %} cannam@62: interface Directory { cannam@62: create @0 (name :Text) -> (file :File); cannam@62: open @1 (name :Text) -> (file :File); cannam@62: remove @2 (name :Text); cannam@62: } cannam@62: {% endhighlight %} cannam@62: cannam@62: `capnp compile` will generate code that looks like this (edited for readability): cannam@62: cannam@62: {% highlight c++ %} cannam@62: struct Directory { cannam@62: Directory() = delete; cannam@62: cannam@62: class Client; cannam@62: class Server; cannam@62: cannam@62: struct CreateParams; cannam@62: struct CreateResults; cannam@62: struct OpenParams; cannam@62: struct OpenResults; cannam@62: struct RemoveParams; cannam@62: struct RemoveResults; cannam@62: // Each of these is equivalent to what would be generated for cannam@62: // a Cap'n Proto struct with one field for each parameter / cannam@62: // result. cannam@62: }; cannam@62: cannam@62: class Directory::Client cannam@62: : public virtual capnp::Capability::Client { cannam@62: public: cannam@62: Client(std::nullptr_t); cannam@62: Client(kj::Own server); cannam@62: Client(kj::Promise promise); cannam@62: Client(kj::Exception exception); cannam@62: cannam@62: capnp::Request createRequest(); cannam@62: capnp::Request openRequest(); cannam@62: capnp::Request removeRequest(); cannam@62: }; cannam@62: cannam@62: class Directory::Server cannam@62: : public virtual capnp::Capability::Server { cannam@62: protected: cannam@62: typedef capnp::CallContext CreateContext; cannam@62: typedef capnp::CallContext OpenContext; cannam@62: typedef capnp::CallContext RemoveContext; cannam@62: // Convenience typedefs. cannam@62: cannam@62: virtual kj::Promise create(CreateContext context); cannam@62: virtual kj::Promise open(OpenContext context); cannam@62: virtual kj::Promise remove(RemoveContext context); cannam@62: // Methods for you to implement. cannam@62: }; cannam@62: {% endhighlight %} cannam@62: cannam@62: ### Clients cannam@62: cannam@62: The generated `Client` type represents a reference to a remote `Server`. `Client`s are cannam@62: pass-by-value types that use reference counting under the hood. (Warning: For performance cannam@62: reasons, the reference counting used by `Client`s is not thread-safe, so you must not copy a cannam@62: `Client` to another thread, unless you do it by means of an inter-thread RPC.) cannam@62: cannam@62: A `Client` can be implicitly constructed from any of: cannam@62: cannam@62: * A `kj::Own`, which takes ownership of the server object and creates a client that cannam@62: calls it. (You can get a `kj::Own` to a newly-allocated heap object using cannam@62: `kj::heap(constructorParams)`; see `kj/memory.h`.) cannam@62: * A `kj::Promise`, which creates a client whose methods first wait for the promise to cannam@62: resolve, then forward the call to the resulting client. cannam@62: * A `kj::Exception`, which creates a client whose methods always throw that exception. cannam@62: * `nullptr`, which creates a client whose methods always throw. This is meant to be used to cannam@62: initialize variables that will be initialized to a real value later on. cannam@62: cannam@62: For each interface method `foo()`, the `Client` has a method `fooRequest()` which creates a new cannam@62: request to call `foo()`. The returned `capnp::Request` object has methods equivalent to a cannam@62: `Builder` for the parameter struct (`FooParams`), with the addition of a method `send()`. cannam@62: `send()` sends the RPC and returns a `capnp::RemotePromise`. cannam@62: cannam@62: This `RemotePromise` is equivalent to `kj::Promise>`, but also has cannam@62: methods that allow pipelining. Namely: cannam@62: cannam@62: * For each interface-typed result, it has a getter method which returns a `Client` of that type. cannam@62: Calling this client will send a pipelined call to the server. cannam@62: * For each struct-typed result, it has a getter method which returns an object containing pipeline cannam@62: getters for that struct's fields. cannam@62: cannam@62: In other words, the `RemotePromise` effectively implements a subset of the eventual results' cannam@62: `Reader` interface -- one that only allows access to interfaces and sub-structs. cannam@62: cannam@62: The `RemotePromise` eventually resolves to `capnp::Response`, which behaves like a cannam@62: `Reader` for the result struct except that it also owns the result message. cannam@62: cannam@62: {% highlight c++ %} cannam@62: Directory::Client dir = ...; cannam@62: cannam@62: // Create a new request for the `open()` method. cannam@62: auto request = dir.openRequest(); cannam@62: request.setName("foo"); cannam@62: cannam@62: // Send the request. cannam@62: auto promise = request.send(); cannam@62: cannam@62: // Make a pipelined request. cannam@62: auto promise2 = promise.getFile().getSizeRequest().send(); cannam@62: cannam@62: // Wait for the full results. cannam@62: auto promise3 = promise2.then( cannam@62: [](capnp::Response&& response) { cannam@62: cout << "File size is: " << response.getSize() << endl; cannam@62: }); cannam@62: {% endhighlight %} cannam@62: cannam@62: For [generic methods](language.html#generic-methods), the `fooRequest()` method will be a template; cannam@62: you must explicitly specify type parameters. cannam@62: cannam@62: ### Servers cannam@62: cannam@62: The generated `Server` type is an abstract interface which may be subclassed to implement a cannam@62: capability. Each method takes a `context` argument and returns a `kj::Promise` which cannam@62: resolves when the call is finished. The parameter and result structures are accessed through the cannam@62: context -- `context.getParams()` returns a `Reader` for the parameters, and `context.getResults()` cannam@62: returns a `Builder` for the results. The context also has methods for controlling RPC logistics, cannam@62: such as cancellation -- see `capnp::CallContext` in `capnp/capability.h` for details. cannam@62: cannam@62: Accessing the results through the context (rather than by returning them) is unintuitive, but cannam@62: necessary because the underlying RPC transport needs to have control over where the results are cannam@62: allocated. For example, a zero-copy shared memory transport would need to allocate the results in cannam@62: the shared memory segment. Hence, the method implementation cannot just create its own cannam@62: `MessageBuilder`. cannam@62: cannam@62: {% highlight c++ %} cannam@62: class DirectoryImpl final: public Directory::Server { cannam@62: public: cannam@62: kj::Promise open(OpenContext context) override { cannam@62: auto iter = files.find(context.getParams().getName()); cannam@62: cannam@62: // Throw an exception if not found. cannam@62: KJ_REQUIRE(iter != files.end(), "File not found."); cannam@62: cannam@62: context.getResults().setFile(iter->second); cannam@62: cannam@62: return kj::READY_NOW; cannam@62: } cannam@62: cannam@62: // Any method which we don't implement will simply throw cannam@62: // an exception by default. cannam@62: cannam@62: private: cannam@62: std::map files; cannam@62: }; cannam@62: {% endhighlight %} cannam@62: cannam@62: On the server side, [generic methods](language.html#generic-methods) are NOT templates. Instead, cannam@62: the generated code is exactly as if all of the generic parameters were bound to `AnyPointer`. The cannam@62: server generally does not get to know exactly what type the client requested; it must be designed cannam@62: to be correct for any parameterization. cannam@62: cannam@62: ## Initializing RPC cannam@62: cannam@62: Cap'n Proto makes it easy to start up an RPC client or server using the "EZ RPC" classes, cannam@62: defined in `capnp/ez-rpc.h`. These classes get you up and running quickly, but they hide a lot cannam@62: of details that power users will likely want to manipulate. Check out the comments in `ez-rpc.h` cannam@62: to understand exactly what you get and what you miss. For the purpose of this overview, we'll cannam@62: show you how to use EZ RPC to get started. cannam@62: cannam@62: ### Starting a client cannam@62: cannam@62: A client should typically look like this: cannam@62: cannam@62: {% highlight c++ %} cannam@62: #include cannam@62: #include "my-interface.capnp.h" cannam@62: #include cannam@62: cannam@62: int main(int argc, const char* argv[]) { cannam@62: // We expect one argument specifying the server address. cannam@62: if (argc != 2) { cannam@62: std::cerr << "usage: " << argv[0] << " HOST[:PORT]" << std::endl; cannam@62: return 1; cannam@62: } cannam@62: cannam@62: // Set up the EzRpcClient, connecting to the server on port cannam@62: // 5923 unless a different port was specified by the user. cannam@62: capnp::EzRpcClient client(argv[1], 5923); cannam@62: auto& waitScope = client.getWaitScope(); cannam@62: cannam@62: // Request the bootstrap capability from the server. cannam@62: MyInterface::Client cap = client.getMain(); cannam@62: cannam@62: // Make a call to the capability. cannam@62: auto request = cap.fooRequest(); cannam@62: request.setParam(123); cannam@62: auto promise = request.send(); cannam@62: cannam@62: // Wait for the result. This is the only line that blocks. cannam@62: auto response = promise.wait(waitScope); cannam@62: cannam@62: // All done. cannam@62: std::cout << response.getResult() << std::endl; cannam@62: return 0; cannam@62: } cannam@62: {% endhighlight %} cannam@62: cannam@62: Note that for the connect address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6 cannam@62: addresses. Additionally, a Unix domain socket can be specified as `unix:` followed by a path name. cannam@62: cannam@62: For a more complete example, see the cannam@62: [calculator client sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-client.c++). cannam@62: cannam@62: ### Starting a server cannam@62: cannam@62: A server might look something like this: cannam@62: cannam@62: {% highlight c++ %} cannam@62: #include cannam@62: #include "my-interface-impl.h" cannam@62: #include cannam@62: cannam@62: int main(int argc, const char* argv[]) { cannam@62: // We expect one argument specifying the address to which cannam@62: // to bind and accept connections. cannam@62: if (argc != 2) { cannam@62: std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]" cannam@62: << std::endl; cannam@62: return 1; cannam@62: } cannam@62: cannam@62: // Set up the EzRpcServer, binding to port 5923 unless a cannam@62: // different port was specified by the user. Note that the cannam@62: // first parameter here can be any "Client" object or anything cannam@62: // that can implicitly cast to a "Client" object. You can even cannam@62: // re-export a capability imported from another server. cannam@62: capnp::EzRpcServer server(kj::heap(), argv[1], 5923); cannam@62: auto& waitScope = server.getWaitScope(); cannam@62: cannam@62: // Run forever, accepting connections and handling requests. cannam@62: kj::NEVER_DONE.wait(waitScope); cannam@62: } cannam@62: {% endhighlight %} cannam@62: cannam@62: Note that for the bind address, Cap'n Proto supports DNS host names as well as IPv4 and IPv6 cannam@62: addresses. The special address `*` can be used to bind to the same port on all local IPv4 and cannam@62: IPv6 interfaces. Additionally, a Unix domain socket can be specified as `unix:` followed by a cannam@62: path name. cannam@62: cannam@62: For a more complete example, see the cannam@62: [calculator server sample](https://github.com/sandstorm-io/capnproto/tree/master/c++/samples/calculator-server.c++). cannam@62: cannam@62: ## Debugging cannam@62: cannam@62: If you've written a server and you want to connect to it to issue some calls for debugging, perhaps cannam@62: interactively, the easiest way to do it is to use [pycapnp](http://jparyani.github.io/pycapnp/). cannam@62: We have decided not to add RPC functionality to the `capnp` command-line tool because pycapnp is cannam@62: better than anything we might provide.