Skip to content

Commit 789ac8c

Browse files
committed
test: m_on_cancel called after request finishes
Test from: bitcoin/bitcoin#34782 (comment)
1 parent 5aa8c36 commit 789ac8c

File tree

3 files changed

+34
-0
lines changed

3 files changed

+34
-0
lines changed

include/mp/proxy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ struct ProxyContext
7373
//! Hook called on the worker thread just before loop->sync() in PassField
7474
//! for Context arguments. Used by tests to inject precise disconnect timing.
7575
std::function<void()> testing_hook_before_sync;
76+
//! Hook called on the worker thread just before returning results.
77+
std::function<void()> testing_hook_after_cleanup;
7678

7779
ProxyContext(Connection* connection);
7880
};

include/mp/type-context.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
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;
7575
if (server.m_context.testing_hook_before_sync) server.m_context.testing_hook_before_sync();
76+
// Save testing_hook_after_cleanup to a local because the
77+
// server may be freed in the cleanup sync() below.
78+
auto testing_hook_after_cleanup = std::move(server.m_context.testing_hook_after_cleanup);
7679
ServerContext server_context{server, call_context, req};
7780
{
7881
// Before invoking the function, store a reference to the
@@ -191,6 +194,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
191194
}
192195
// End of scope: if KJ_DEFER was reached, it runs here
193196
}
197+
if (testing_hook_after_cleanup) testing_hook_after_cleanup();
194198
return call_context;
195199
};
196200

test/mp/test/test.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,34 @@ KJ_TEST("Calling async IPC method, with server disconnect racing the call")
362362
disconnect_thread.join();
363363
}
364364

365+
KJ_TEST("Calling async IPC method, with server disconnect after cleanup")
366+
{
367+
// Regression test for bitcoin/bitcoin#34782 (stack-use-after-return where
368+
// the m_on_cancel callback accessed stack-local cancel_mutex and
369+
// server_context after the invoke lambda's inner scope exited). The fix
370+
// clears m_on_cancel in the cleanup loop->sync() so it is null by the
371+
// time the scope exits.
372+
//
373+
// Use testing_hook_after_cleanup to trigger a server disconnect after the
374+
// inner scope exits (cancel_mutex destroyed). Without the fix, the
375+
// disconnect fires m_on_cancel which accesses the destroyed mutex.
376+
TestSetup setup;
377+
ProxyClient<messages::FooInterface>* foo = setup.client.get();
378+
foo->initThreadMap();
379+
setup.server->m_impl->m_fn = [] {};
380+
381+
setup.server->m_context.testing_hook_after_cleanup = [&] {
382+
setup.server_disconnect();
383+
};
384+
385+
try {
386+
foo->callFnAsync();
387+
KJ_EXPECT(false);
388+
} catch (const std::runtime_error& e) {
389+
KJ_EXPECT(std::string_view{e.what()} == "IPC client method call interrupted by disconnect.");
390+
}
391+
}
392+
365393
KJ_TEST("Make simultaneous IPC calls on single remote thread")
366394
{
367395
TestSetup setup;

0 commit comments

Comments
 (0)