Repository navigation
bitcoin-cli uncaught kj::ExceptionImpl with #32297 #183
Description
Originally posted by @pinheadmz in #182
Not sure if my observation belongs here or in a new issue. Testing bitcoin/bitcoin#32297 I tried to use
socatto inspect the IPC request:
socat -v UNIX-LISTEN:/Users/matthewzipkin/.bitcoin/regtest/node.sock STDOUTand then make a bitcoin-cli request even though bitcoin-node was not running:
bccli -regtest getblockcounterror:
libc++abi: terminating due to uncaught exception of type kj::ExceptionImpl: /opt/homebrew/include/kj/memory.h:258: failed: expected ptr != nullptr; null Own<> dereference stack: 1070007cb 10489843b 10494a81f 10494a74b 104951f4f 104951def 104951d93 104951d33 104951c73 104951c17 10494f59b 1048acccb 1048ac9d7 104ddc727 104ddc077 10488ab97 10488aa0f 10488a8e7 104889977 1a01b6f93 1a01b1d33 Abort trap: 6Stepping through with lldb I found the error in
ConnectStream():libmultiprocess/include/mp/proxy-io.h
Lines 559 to 580 in 0198397
//! Given stream file descriptor, make a new ProxyClient object to send requests //! over the stream. Also create a new Connection object embedded in the //! client that is freed when the client is closed. template <typename InitInterface> std::unique_ptr<ProxyClient<InitInterface>> ConnectStream(EventLoop& loop, int fd) { typename InitInterface::Client init_client(nullptr); std::unique_ptr<Connection> connection; loop.sync([&] { auto stream = loop.m_io_context.lowLevelProvider->wrapSocketFd(fd, kj::LowLevelAsyncIoProvider::TAKE_OWNERSHIP); connection = std::make_unique<Connection>(loop, kj::mv(stream)); init_client = connection->m_rpc_system->bootstrap(ServerVatId().vat_id).castAs<InitInterface>(); Connection* connection_ptr = connection.get(); connection->onDisconnect([&loop, connection_ptr] { loop.log() << "IPC client: unexpected network disconnect."; delete connection_ptr; }); }); return std::make_unique<ProxyClient<InitInterface>>( kj::mv(init_client), connection.release(), /* destroy_connection= */ true); } What I believe happens is, we get a file descriptor way earlier in the stack (in bitcoin:
src/ipc/interfaces.cpp::connectAddress()but by the time we get toConnectStream()the listener has disconnected (or, just isn't bitcoin) and then things start going wrong inProxyClientBase()
Activity
ryanofsky commentedon Jun 16, 2025
It could be useful to see the backtrace from lldb, but depending on where the exception is being thrown, I think #32345 could help with this by turning the
kj::Exceptioninto anipc::Exceptionthat could be more easily caught in bitcoin-cli. It would make sense for theCallIPCmethod in bitcoin/bitcoin#32297 to catch this exception and treat it the same as the connection failing.pinheadmz commentedon Jun 16, 2025
bt:
ryanofsky commentedon Jun 16, 2025
Thanks, so from the stack trace it doesn't seem like any changes to bitcoin/bitcoin#32297 need to be made to fix this because the failing IPC call is happening when the
Initobject is being constructed inConnectStreamcalling down to theProxyClientBase<Init>constructor.So bitcoin/bitcoin#32345 might be sufficient by itself to fix this, or if not the
clientInvokefunction needs to be improved more to handle exceptions coming from this place.ryanofsky commentedon Jun 16, 2025
Looking at stack trace more, it does seem like a new fix will be required to prevent this crash. The line that is throwing is:
libmultiprocess/include/mp/proxy-types.h
Line 620 in 0198397
So probably need to add a try / catch around this line and turn it into an exception similar to 56fff76 from #160
pinheadmz commentedon Jun 17, 2025
For what it's worth, I rebased bitcoin/bitcoin#32297 on top of bitcoin/bitcoin#32345 and the edge case of ipcconnecting bitcoin-cli to a non-bitcoin socket failed gracefully:
ryanofsky commentedon Jun 17, 2025
Thanks for testing and that's interesting. I'm a little surprised bitcoin/bitcoin#32345 would fix this issue based on where the exception seemed to be coming from in the stack trace.
And either way, I would like to add unit test to cover the case of client connecting to a socket that just disconnects.
ryanofsky commentedon Aug 23, 2025
bitcoin/bitcoin#32345 is merged but it still seems like a good idea to add a test to covers connecting to a socket that just disconnects without replying, so will keep this open
WIP: Add test coverage for ConnectStream
WIP: Add test coverage for ConnectStream
xyzconstant commentedon Jun 24, 2026
I've been working on this issue and have pushed a commit that adds test coverage: xyzconstant@bb60535.
I've added tests for four scenarios:
recv(), the outcome will either be crashing with "IPC client method called after disconnect" or "IPC client method call interrupted by disconnect." This behavior seems to depend on whether therecv()call successfully reads the entirety of what is likely Capnp’s bootstrap request or only a portion of it.recv(), the process crashes with "IPC client method call interrupted by disconnect."recv()in awhileloop results in the test getting stuck at this line:libmultiprocess/include/mp/proxy-types.h
Line 754 in 8412fcd
It’s important to note that all of these cases call
FooInterface::initThreadMap()to invokeclientInvoke(), asConnectStream()on its own won't trigger any libmultiprocess request if there's not aconstruct()method available in the Init interface.I'll look into the 4th scenario further and see if there's an elegant solution that covers all of these scenarios (if needed).
I would appreciate your feedback on this, @ryanofsky.
ryanofsky commentedon Jul 5, 2026
Thanks for the tests! Followed up in #298
WIP: Add test coverage for ConnectStream
WIP: Add test coverage for ConnectStream
WIP: Add test coverage for ConnectStream
WIP: Add test coverage for ConnectStream
Merge #298: Fix error handling when creating clients (`mp::ConnectStr…