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([&]() { 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;