Accepting a dead socket kills IPC listeners in macOS (Cap'n proto bug) #319

issue xyzconstant opened this issue on July 23, 2026
  1. xyzconstant commented at 9:41 PM on July 23, 2026: contributor

    In macOS, when a queued client disconnects before being accepted, ListenConnections's accept path throws:

    mp/proxy.cpp:48: error: Uncaught exception in daemonized task.; exception = kj/async-io-unix.c++:1365: failed: setsocketopt(IPPROTO_TCP, TCP_NODELAY): Invalid argument
    

    This issue was first seen while working on adding coverage for ListenConnections in #310. While reviewing bitcoin/bitcoin#35037, I came across it again while experimenting with socat. It's trivial to hit because of the new -ipcbind settings introduced there.

    To reproduce it, here are the steps (macOS):

    1. The bitcoin/bitcoin#35037 branch is needed since it has a max-connections cli setting available, so pull the changes and compile bitcoin-node.

    2. Start bitcoin-node in regtest and cap the connection limit at 1

      ./build/bin/bitcoin-node -regtest -ipcbind=unix:/tmp/a.sock,max-connections=1 
      
    3. In another terminal (let's call it terminal A), connect to the listening address using socat.

      socat - UNIX-CONNECT:/tmp/a.sock
      

      The node should log the accepted connection:

      2026-07-23T18:52:00Z ipc: {bitcoin-node-90447/b-capnp-loop-13449344} IPC server: socket connected.
      

      Because the connection slots are limited to only one, a single connection is enough to saturate it and start queuing new ones.

    4. Open another terminal (Terminal B), try to connect to the listener again using the command from step 3. Since the listener is already saturated, the connection shouldn't be accepted, so no new "socket connected" log line should appear.

    5. At this point, the second connection is queued, so close it in Terminal B.

    6. Next, close the accepted connection in Terminal A so the listener attempts to accept the already-dead socket that is next in the queue, triggering the bug.

       2026-07-23T19:21:31Z ipc: {bitcoin-node-1724/b-capnp-loop-13498216} IPC server: socket disconnected.
       mp/proxy.cpp:48: error: Uncaught exception in daemonized task.; exception = kj/async-io-unix.c++:1365: failed: setsocketopt(IPPROTO_TCP, TCP_NODELAY): Invalid argument
       stack: 105b5b327 105b5abbb 1047447b7 1047464bb 1047468e3
       2026-07-23T19:21:31Z [error] ipc: {bitcoin-node-1724/b-capnp-loop-13498216} Uncaught exception in daemonized task.
      

      Now the listener has stopped accepting connections

      $ socat - UNIX-CONNECT:/tmp/a.sock
      2026/07/23 16:26:36 socat[3651] E connect(, LEN=13 AF=1 "/tmp/a.sock", 13): Connection refused
      

      This is a Cap'n Proto bug, @ViniciusCestarii found the root cause (see #310#pullrequestreview-4723481031): After accept(), kj always calls setsockopt(TCP_NODELAY) on the connection socket. For Unix sockets, kj handles the usual "not supported" error codes. On macOS, EINVAL is returned when the client has already disconnected, but kj only tolerates that code when built for FreeBSD. The unexpected error then goes uncaught in the accept loop (escapes mp::ListenConnections), causing the listener to stop accepting new connections.

    It's fixed on capnproto's v2 branch (capnproto/capnproto@7df5bd078), but no release includes it. I proposed another solution at the libmultiprocess layer, but it unconditionally (and dangerously) continued the accept loop, ignoring errors that may deserve attention.

    Until it's released (or backported), a temporary workaround may be to add a patch to Bitcoin Core's depends, as suggested by @ryanofsky in #310#pullrequestreview-4724053865. I've already opened a PR to Bitcoin Core: bitcoin/bitcoin#35796.

  2. Sjors commented at 12:37 PM on July 24, 2026: member

    Would it make to open a backport pull request against capnp? I don't completely understand their branch strategy, but I think master is v1? That said, we probably don't want to bump our minimum version just for this issue, so a workaround here is useful.

  3. xyzconstant referenced this in commit 6da460d784 on Jul 24, 2026
  4. xyzconstant commented at 12:31 AM on July 25, 2026: contributor

    That said, we probably don't want to bump our minimum version just for this issue, so a workaround here is useful.

    Just opened a PR in Core with a patch to capnp.mk: bitcoin/bitcoin#35796

    Would it make to open a backport pull request against capnp? I don't completely understand their branch strategy, but I think master is v1?

    Yes, you're right, master is indeed v1. v2 seems to be taking longer than the next v1.x version, so I may also open a backport PR in capnp.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/libmultiprocess. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-07-27 10:30 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me