24 comments

[ 3.0 ms ] story [ 66.0 ms ] thread
My gut reaction was "ewwww", but upon reflection, this does seem like a great idea. I disagree with the claim that it "solves" the RPC problem (you may be making it less expensive to make a network request, but you still can't pretend a network call is a function call; one of the big problems with such pretending isn't the latency but things like "total network failure" that are poorly represented by the function call abstraction), but it does go a long way towards mitigating it. The ability to create much more orthogonal and composable RPC interactions is a lesson that many such protocols could stand to learn.
Oops, you're right, I should have said "helps solve" rather than "solves".

Yes, total network failure is still an issue. Typically, applications need to solve this by catching a network exception somewhere high up the stack and then starting over with a fresh connection.

There's certainly an argument to be made for using a different abstraction, e.g. those provided by ZeroMQ, that can handle fault tolerance more transparently. But, it all depends on your use case. A lot of things just don't fit into stateless models -- particularly real-time collaborative types of things. (ZeroMQ pairs nicely with Cap'n Proto serialization, BTW, if that's the route you prefer.)

Awesome, a C++ implementation of the Future monad. Personally I would prefer .flatMap to .then, as in Scala. That would also allow for a companion function .map that transforms the result of a Future using a function that does not return a promise.

ie: (in Scala, my C++-fu is weak)

    getTweetsFor("domenic") // returns a future
       .map(parseTweetsForUrls)
       .map(_.head)
       .flatMap(expandUrlUsingTwitterApi)
       .flatMap(httpGet)
Heh, yeah, I only realized yesterday, I think, that promises are monads.

then() will actually accept functions returning regular values, too. A big difference between then() and map() is that then() accepts an optional second parameter for exception handling. In any case, I used "then" because that's what Javascript promises use.

JavaScript promises are wrong and broken. See the huge spec of the Promises/A+ vs. the few lines of specification for a correct one.
Are they broken in a way that I have copied? I don't actually know too much about the details, I just copied the `then()` call.
I haven't looked at your implementation, so I'm basing this on the statement "then() will actually accept functions returning regular values, too" in your grandparent comment. Because of this you don't have a true Monad (as in your Promise implementation doesn't satisfy the third monad law).

One easy way to think of Monads is that they are like black holes - you are free to put something into it but can't ever pull it back out. Instead, all you are allowed to do is chain them together (with bind). So to transform the value inside a monad, you must write a function that takes that value and returns the transformed value wrapped in the same type of monad (Promise in this case). The monadic machinery in 'bind' will take care of pushing the result from one Promise into the transform function.

I don't think there's a problem here. We get two functions with a monad m:

bind :: m a -> (a -> m b) -> m b

return :: a -> m a

These allow us to define

then :: m a -> (a -> b) -> m b

then x f = bind x (return . f)

i.e. it's trivial to lift a regular function into the monad.

What dwrensha said, although I'd describe it like this: Allowing then() to take a function which returns a bare value rather than a promise is just syntax sugar. You can always wrap a bare value in a promise by creating an immediate (already-fulfilled) promise. Giving then() such a function -- that wraps its result in a promise -- is equivalent in behavior to giving then() a function that just returns a bare value, except that the latter is somewhat more efficient in implementation.
Is anyone using Cap'n Proto in production? What has your experience been?
Canonical is using it in Ubuntu Unity:

http://packages.ubuntu.com/source/trusty/unity-scopes-api

There is also the OSX text editor, TextMate, which uses Cap'n Proto for some kind of cache file.

Sadly, most users don't actually tell me about their experiences, but given the number of people asking questions and filing bugs, I suspect there are many more.

The biggest thing holding people back is lack of MSVC support.

I don't understand how this is different from the standard RPC model.

A typical RPC in a C++/Java/C# language looks something like this:

  var client = EchoClient::Connect("corba://example.com:9000");

  var request = new EchoRequest();
  request.set_message("hello world!")

  var options = new RpcOptions();
  options.set_timeout_milliseconds(5000);

  Future<EchoResponse> futureResponse = client.Echo(request, options);
  EchoResponse response = futureResponse.Wait();
According to the article this is going "back in time", but that's completely silly -- the response is being returned at the point of the Wait() call, which blocks until the server has finished RPC processing.

And of course, languages with proper thread support (e.g. Haskell) don't need to bother with futures at all, because it's easy to spawn a new thread for each request instead of blocking some central main thread.

And it's fairly easy to provide syntactic sugar over this to provide closure-based control flow

Where does that code come from? That's not Cap'n Proto.

Sorry, but did you read the docs?

That's the kind of code that would be used in a typical RPC system written in the last ~20 years. You have some sort of client stub, a request object, and a response handle that has operations for waiting until the server has generated the response. The actual marshaling implementation could be ASN.1 or JSON or Thrift or whatever the local programmers want.

I want to know how the Cap'n'proto design discussed in the post is significantly different from this standard RPC model.

If you're executing a 2nd procedure call that uses the output of the first (or 3rd, 4th 5th etc) then it requires 1 call. A traditional RPC system would either need a combined method (which is suboptimal from a "clean design" perspective) or you'd get the response of the 1st back, and then just pass it back to the server for the 2nd call.
So the major improvement described here is a hint to the server that it should pass the response from one RPC directly into the request of another, rather than returning anything to the client?

That seems like it'd only be useful in very specific circumstances; you'd need RPC methods with the same type for request and response, and it only works if you're using the same backend for each call (e.g. you can't have load balancing of separate calls).

If you read the documentation, I give an extended argument for why this is useful: it enables more object-oriented protocol design.

http://kentonv.github.io/capnproto/rpc.html

Also, it is not the case that the new RPC's input type has to match the old one's output. You can take just one object embedded in the old response and use it in the new request.

Please do look at the examples.

Cap'n Proto's RPC interface is different. The call returns a promise (like a future), but you don't have to wait on it. The promise itself has methods that let you do things with the results, such as call them or use them in the parameters to other calls that go back to the server. These chained calls can be sent to the server before the initial call completes, by just telling the server "When the previous call completes, do this with the result".

This is all described in detail in the docs.

http://kentonv.github.io/capnproto/rpc.html

Scala uses the Future monad pretty widely to model concurrency. Does Haskell not use the Future monad? What does it use instead?
In Haskell it'd be more typical to use blocking operations for RPCs and spawn a thread for each sequence. For example, the article has a sequence of operations that should be run in order; that would be a single thread, with each operation blocking until it's ready. If some operations need to be async, then spawn another thread for just that operation.

This model is possible because the Haskell runtime decouples threads from the OS's process-with-shared-address-space abstraction, so running a few million threads is a reasonable design choice. Users of Erlang will find the Haskell threading model very familiar, with the exception that Haskell also provides ways to pass around mutable state (e.g. you could pass a pointer through a channel).

It is possible to implement something like a Future monad if you really want to, based on MVar, but cheap threads make it unnecessary.

It seems my post has been severely penalized by HN. Went from #2 to #30 all at once, then dropped from the front page.

Anyone know what I did wrong?

You submitted a post about programming to PoliticsNews. Please try again when you have an outrageous story about government abuse.
I think I figured it out. Someone with moderator rights must have read the summary, not read the documentation in detail, and decided that I had only implemented regular old promises/futures and was calling this "time travel". They probably missed the point of promise pipelining -- which is something that no mainstream RPC system implements to my knowledge, only research systems like CapTP. So they demoted it for making what they assumed to be a ridiculous claim about a mundane piece of software.

Sigh.

SMB and NFSv4 have support for something similar in their protocols. Nice to see it generalized. The file protocol versions only chain RPCs on success. You can't operate directly on the result.