diff src/capnproto-git-20161025/doc/cxxrpc.md @ 133:1ac99bfc383d

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.