Skip to content

Commit 2ed97a0

Browse files
committed
test: worker thread destroyed before it is initialized
Add testing_hook_makethread that fires on the worker thread between the Lock and set_value operations in makeThread. A checker thread uses try_lock to verify the waiter mutex is held at that point. With the fix (Lock before set_value) the mutex is held, so try_lock fails. Without the fix (set_value before Lock) the hook fires before Lock, so try_lock succeeds -- indicating the race window exists.
1 parent 789ac8c commit 2ed97a0

3 files changed

Lines changed: 47 additions & 0 deletions

File tree

include/mp/proxy-io.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,11 @@ class Connection
461461
//! ThreadMap.makeThread) used to service requests to clients.
462462
::capnp::CapabilityServerSet<Thread> m_threads;
463463

464+
//! Hook called on the worker thread inside makeThread, right after
465+
//! set_value. Used by tests to verify the waiter mutex is held when
466+
//! the thread context is published.
467+
std::function<void()> testing_hook_makethread;
468+
464469
//! Canceler for canceling promises that we want to discard when the
465470
//! connection is destroyed. This is used to interrupt method calls that are
466471
//! still executing at time of disconnection.

src/mp/proxy.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ kj::Promise<void> ProxyServer<ThreadMap>::makeThread(MakeThreadContext context)
418418
g_thread_context.waiter = std::make_unique<Waiter>();
419419
Lock lock(g_thread_context.waiter->m_mutex);
420420
thread_context.set_value(&g_thread_context);
421+
if (this->m_connection.testing_hook_makethread) this->m_connection.testing_hook_makethread();
421422
// Wait for shutdown signal from ProxyServer<Thread> destructor (signal
422423
// is just waiter getting set to null.)
423424
g_thread_context.waiter->wait(lock, [] { return !g_thread_context.waiter; });

test/mp/test/test.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,47 @@ KJ_TEST("Calling IPC method, disconnecting and blocking during the call")
325325
signal.set_value();
326326
}
327327

328+
KJ_TEST("Worker thread destroyed before it is initialized")
329+
{
330+
// Regression test for bitcoin/bitcoin#34711, bitcoin/bitcoin#34756
331+
// (worker thread destroyed before it acquires the waiter mutex). The
332+
// fix acquires the lock before calling set_value so the
333+
// ProxyServer<Thread> destructor cannot null the waiter while the
334+
// worker is between set_value and Lock.
335+
//
336+
// The testing_hook_makethread fires right after set_value in
337+
// makeThread's worker thread. A checker thread uses try_lock to
338+
// verify the waiter mutex is held at that point. With the fix
339+
// (Lock before set_value) the mutex is held, so try_lock fails.
340+
// Without the fix (set_value before Lock) the hook fires before
341+
// Lock, so try_lock succeeds — indicating the race window exists.
342+
TestSetup setup;
343+
ProxyClient<messages::FooInterface>* foo = setup.client.get();
344+
foo->initThreadMap();
345+
setup.server->m_impl->m_fn = [] {};
346+
347+
std::promise<std::mutex*> mutex_promise;
348+
std::promise<void> check_done;
349+
Connection* conn = setup.server->m_context.connection;
350+
conn->testing_hook_makethread = [&] {
351+
mutex_promise.set_value(&g_thread_context.waiter->m_mutex.m_mutex);
352+
check_done.get_future().wait();
353+
};
354+
355+
std::atomic<bool> lock_was_held{false};
356+
std::thread check_thread{[&] {
357+
std::mutex* m = mutex_promise.get_future().get();
358+
bool locked = m->try_lock();
359+
if (locked) m->unlock();
360+
lock_was_held = !locked;
361+
check_done.set_value();
362+
}};
363+
364+
foo->callFnAsync();
365+
check_thread.join();
366+
KJ_EXPECT(lock_was_held);
367+
}
368+
328369
KJ_TEST("Calling async IPC method, with server disconnect racing the call")
329370
{
330371
// Regression test for bitcoin/bitcoin#34777 (heap-use-after-free where

0 commit comments

Comments
 (0)