@@ -325,6 +325,43 @@ KJ_TEST("Calling IPC method, disconnecting and blocking during the call")
325325 signal.set_value ();
326326}
327327
328+ KJ_TEST (" Calling async IPC method, with server disconnect racing the call" )
329+ {
330+ // Regression test for bitcoin/bitcoin#34777 (heap-use-after-free where
331+ // getParams() was called on the worker thread after the event loop thread
332+ // freed the RpcCallContext on disconnect). The fix moves getParams() inside
333+ // loop->sync() so it always runs on the event loop thread.
334+ //
335+ // Use testing_hook_before_sync to pause the worker thread just before it
336+ // enters loop->sync(), then disconnect the server from a separate thread.
337+ TestSetup setup;
338+ ProxyClient<messages::FooInterface>* foo = setup.client .get ();
339+ foo->initThreadMap ();
340+ setup.server ->m_impl ->m_fn = [] {};
341+
342+ std::promise<void > worker_ready;
343+ std::promise<void > disconnect_done;
344+ auto disconnect_done_future = disconnect_done.get_future ().share ();
345+ setup.server ->m_context .testing_hook_before_sync = [&worker_ready, disconnect_done_future] {
346+ worker_ready.set_value ();
347+ disconnect_done_future.wait ();
348+ };
349+
350+ std::thread disconnect_thread{[&] {
351+ worker_ready.get_future ().wait ();
352+ setup.server_disconnect ();
353+ disconnect_done.set_value ();
354+ }};
355+
356+ try {
357+ foo->callFnAsync ();
358+ KJ_EXPECT (false );
359+ } catch (const std::runtime_error& e) {
360+ KJ_EXPECT (std::string_view{e.what ()} == " IPC client method call interrupted by disconnect." );
361+ }
362+ disconnect_thread.join ();
363+ }
364+
328365KJ_TEST (" Make simultaneous IPC calls on single remote thread" )
329366{
330367 TestSetup setup;
0 commit comments