Skip to content

Commit 74459b8

Browse files
committed
test: getParams() called after request cancel (#34777)
Test from: bitcoin/bitcoin#34777 (comment)
1 parent 7f954aa commit 74459b8

File tree

3 files changed

+41
-0
lines changed

3 files changed

+41
-0
lines changed

include/mp/proxy.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ struct ProxyContext
7070
Connection* connection;
7171
EventLoopRef loop;
7272
CleanupList cleanup_fns;
73+
//! Hook called on the worker thread just before loop->sync() in PassField
74+
//! for Context arguments. Used by tests to inject precise disconnect timing.
75+
std::function<void()> testing_hook_before_sync;
7376

7477
ProxyContext(Connection* connection);
7578
};

include/mp/type-context.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
7272
auto self = server.thisCap();
7373
auto invoke = [self = kj::mv(self), call_context = kj::mv(server_context.call_context), &server, req, fn, args...](CancelMonitor& cancel_monitor) mutable {
7474
MP_LOG(*server.m_context.loop, Log::Debug) << "IPC server executing request #" << req;
75+
if (server.m_context.testing_hook_before_sync) server.m_context.testing_hook_before_sync();
7576
ServerContext server_context{server, call_context, req};
7677
{
7778
// Before invoking the function, store a reference to the

test/mp/test/test.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
328365
KJ_TEST("Make simultaneous IPC calls on single remote thread")
329366
{
330367
TestSetup setup;

0 commit comments

Comments
 (0)