11 Jan 2016
Today I’ve released verson 0.6 of the three main Cap’n Proto crates: the runtime, the code generator, and the remote procedure call system.
The biggest change is that the RPC system is now asynchronous, built on top of the GJ event loop library.
Cap’n Proto’s RPC protocol allows method calls to be made on distributed objects. It has a built-in notion of pipelining that can minimize network round-trips, and it allows object references to be transmitted within messages, alongside plain-old-data.
Such a system has an inherent need to deal with concurrency, both in its internal implementation in its publicly-exposed interfaces.
For example, it’s common for a user to want to implement an
RPC method that makes a bunch of other RPC calls
and then collects their results
before returning.
How should these actions compose?
In the new version of capnp-rpc
,
which uses GJ’s Promise
abstraction,
each of the inner method calls
returns a promise,
and those promises can be collected with Promise::all()
to form a new promise which can then be returned from the outer method.
To see more concretely what this kind of thing looks like, take a look at the calculator example, which also showcases some fancier features. There are also some more-practical examples in the form of Sandstorm apps: a simple GET/PUT/DELETE server and a word game.
A Promise<T, E>
is essentially a deferred Result<T, E>
,
so it should be no surprise that
today’s release pertains to
our continuing
story
about error handling.
Last time, we described our switch to using Result<T,E>
pervasively,
so that we could return an Err(e)
on a decode error,
rather than panicking.
That switch had some costs:
try!()
s in our code, one for any time we dereference a Cap’n Proto pointer.T
to Result<T, E>
,
and in some cases need to define helper functions so that try!()
has a place to return to.In my opinion, (1) is not so bad, and it has the advantage of making control flow more clear.
The proposed ?
operator
would make this even nicer.
In the asynchronous world of Cap’n Proto RPC,
(2) becomes less of a hassle, because
most functions that need to read a Cap’n Proto message
are asynchronous, and therefore already return a Promise<T, E>
.
In such cases, we can use the pry!()
macro that GJ defines. The pry!()
macro acts like try!()
, but in the early return case returns
Promise::err(e)
rather than Result::Err(e)
.
One error-related question that often arises when designing interfaces that use Promise<T,E>
is:
what concrete error type should be plugged in for E
?
In previous versions of capnproto-rust, I had defined an error enum capnp::Error
with various cases, one of which wrapped a std::io::Error
.
This got me into trouble when I wanted to start using
Promise::fork()
,
which requires that E
be Clone
.
The problem is that std::io::Error
is not Clone
!
To address this problem, I have redefined ::capnp::Error
to follow the design of
kj::Exception.
It’s now a very simple struct with a String
description and an ErrorKind
enum,
where the only variants of
ErrorKind
are Failed
, Disconnected
, Overloaded
, and Unimplemented
.
The observation here is that there seems to be very little gained by
defining hierarchies of errors wrapping other errors.