Skip to content

Commit 9536b63

Browse files
committed
test: m_on_cancel called after request finishes (#34782)
Test from: bitcoin/bitcoin#34782 (comment)
1 parent 25cf639 commit 9536b63

File tree

3 files changed

+38
-0
lines changed

3 files changed

+38
-0
lines changed

include/mp/proxy.h

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

7981
ProxyContext(Connection* connection);

include/mp/type-context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,9 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
193193
}
194194
// End of scope: if KJ_DEFER was reached, it runs here
195195
}
196+
#ifndef NDEBUG
197+
if (server.m_context.testing_hook_after_cleanup) server.m_context.testing_hook_after_cleanup();
198+
#endif
196199
return call_context;
197200
};
198201

test/mp/test/test.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,39 @@ KJ_TEST("Calling async IPC method, with server disconnect racing the call")
366366
#endif
367367
}
368368

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

0 commit comments

Comments
 (0)