Recursive autoReconnect logic

Skip to first unread message


Sep 26, 2022, 8:31:03 AM9/26/22
to Cap'n Proto
I'm trying to add reconnection behaviour to capabilities with autoReconnect, but the behaviour isn't what I'd expect when I have multiple levels of reconnecting capabilities that are pipelined:

Given a trivial schema that obtains one capability from another:

interface TestB {
  getA @0 () -> (a : Capability);

This example code deliberately creates capability B as disconnected, so I'd expect an attempt to reconnect the B capability, but instead, only reconnection of the A capability is attempted:

#include "reconnect-test.capnp.h"
#include <capnp/reconnect.h>
#include <kj/main.h>
#include <kj/async.h>
#include <kj/debug.h>

int main(int argc, char* argv[]) {
  kj::EventLoop loop;
  kj::WaitScope waitScope{loop};

  auto b = capnp::autoReconnect([&](){
    KJ_LOG(WARNING, "Reconnecting B");
    return TestB::Client{KJ_EXCEPTION(DISCONNECTED)};

  auto a = capnp::autoReconnect([&]{
    KJ_LOG(WARNING, "Reconnecting A");
    return b.getARequest().send().getA();

  try {
    a.typelessRequest(0, 0, nullptr).send().wait(waitScope);
  catch (kj::Exception& exc) {
    KJ_LOG(ERROR, "1", exc);

  try {
    a.typelessRequest(0, 0, nullptr).send().wait(waitScope);
  catch (kj::Exception& exc) {
    KJ_LOG(ERROR, "2", exc);

reconnect-test.cpp:12: warning: Reconnecting B
reconnect-test.cpp:17: warning: Reconnecting A
reconnect-test.cpp:17: warning: Reconnecting A
reconnect-test.cpp:25: error: 1; exc = reconnect-test.cpp:13: disconnected
reconnect-test.cpp:17: warning: Reconnecting A
reconnect-test.cpp:32: error: 2; exc = reconnect-test.cpp:13: disconnected

I'd expect the second attempt to result in a reconnection of the broken capability B when the (eventual) call to getARequest() occurs?

If I remove the pipelining, and force the call to getARequest to complete, then B reconnects just fine.


Kenton Varda

Sep 28, 2022, 9:24:25 PM9/28/22
to Vaci, Cap'n Proto
Indeed, if you call send(), pull a pipelined capability off of it, and then drop the Promise that `send()` returned... then the logic to detect disconnect will never run, because it's chained on the promise that you canceled.

This does seem like a bug... I think I even remember hitting this before. One way to work around it is to make sure you save the promise returned by `send()` off to the side rather than throwing it away, so that it's allowed to complete the disconnect-detection logic.

It's a bit hard for `ReconnectHook` to do this automatically, because if it did, then you wouldn't actually be able to cancel calls made through ReconnectHook... attempts to cancel would be ignored. I guess what you really want is that the call won't be canceled if a pipelined capability still exists. I think it would be possible to build that but it might be a little convoluted...


You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to
To view this discussion on the web visit
Reply all
Reply to author
0 new messages