From 90be8354d47f995f592ee4ce67b7348098a13b17 Mon Sep 17 00:00:00 2001 From: xyzconstant <263061129+xyzconstant@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:25:15 -0300 Subject: [PATCH 1/2] test: regression for ~ProxyClient destroy after peer disconnect Reproduces bitcoin-core/libmultiprocess#219 by saving a callback on the server then disconnecting before cleanup completes, so the server-side `~ProxyClient` runs its remote destroy call against a dead connection. Without the fix in the next commit, the throw escapes the noexcept destructor and aborts mptest. --- test/mp/test/test.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/mp/test/test.cpp b/test/mp/test/test.cpp index d91edb40..b259790b 100644 --- a/test/mp/test/test.cpp +++ b/test/mp/test/test.cpp @@ -427,6 +427,32 @@ KJ_TEST("Calling async IPC method, with server disconnect after cleanup") } } +KJ_TEST("Destroying ProxyClient<> with destroy method after peer disconnect") +{ + // Regression test for bitcoin-core/libmultiprocess#219 where + // ~ProxyClientBase would call std::terminate if the remote destroy RPC + // failed during teardown. + // + // Save a callback on the server so it holds a ProxyClient + // pointing back to this side, then disconnect. When the server is torn + // down, the ProxyClient destructor issues a destroy RPC over + // the now dead connection; without the bugfix the exception escapes the + // noexcept destructor and aborts the process. + + TestSetup setup{/*client_owns_connection=*/false}; + ProxyClient* foo = setup.client.get(); + foo->initThreadMap(); + + class Callback : public FooCallback + { + public: + int call(int arg) override { return arg; } + }; + + foo->saveCallback(std::make_shared()); + setup.client_disconnect(); +} + KJ_TEST("Make simultaneous IPC calls on single remote thread") { TestSetup setup; From 6de92e1c7324c4748d05687372256a5051c97bb4 Mon Sep 17 00:00:00 2001 From: xyzconstant <263061129+xyzconstant@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:25:15 -0300 Subject: [PATCH 2/2] proxy-client: tolerate exceptions from remote destroy during cleanup Fixes bitcoin-core/libmultiprocess#219 --- include/mp/proxy-io.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index d7b9f0e5..09465c04 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -538,7 +538,12 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // the remote object, waiting for it to be deleted server side. If the // capnp interface does not define a destroy method, this will just call // an empty stub defined in the ProxyClientBase class and do nothing. - Sub::destroy(*this); + // Exceptions are caught and logged rather than propagated because + // ~ProxyClientBase is noexcept and the peer may be gone by the time + // this runs. + if (kj::runCatchingExceptions([&]{ Sub::destroy(*this); }) != nullptr) { + MP_LOG(*m_context.loop, Log::Warning) << "Remote destroy call failed during cleanup. Continuing."; + } // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code m_context.loop->sync([&]() {