From d2f6aa2e84ef5703ce037241e5558a29dff922f5 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Thu, 26 Jun 2025 11:54:51 -0400 Subject: [PATCH 01/11] ci: add thread sanitizer job --- .github/workflows/ci.yml | 2 +- ci/README.md | 7 ++++--- ci/configs/sanitize.bash | 7 +++++++ ci/scripts/ci.sh | 5 ++++- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 ci/configs/sanitize.bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4933588c..2e751c5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - config: [default, llvm, gnu32] + config: [default, llvm, gnu32, sanitize] name: build • ${{ matrix.config }} diff --git a/ci/README.md b/ci/README.md index 72eebba8..85eb467c 100644 --- a/ci/README.md +++ b/ci/README.md @@ -16,9 +16,10 @@ All CI is just bash and nix. To run jobs locally: ```bash -CI_CONFIG=ci/configs/default.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/llvm.bash ci/scripts/run.sh -CI_CONFIG=ci/configs/gnu32.bash ci/scripts/run.sh +CI_CONFIG=ci/configs/default.bash ci/scripts/run.sh +CI_CONFIG=ci/configs/llvm.bash ci/scripts/run.sh +CI_CONFIG=ci/configs/gnu32.bash ci/scripts/run.sh +CI_CONFIG=ci/configs/sanitize.bash ci/scripts/run.sh ``` By default CI jobs will reuse their build directories. `CI_CLEAN=1` can be specified to delete them before running instead. diff --git a/ci/configs/sanitize.bash b/ci/configs/sanitize.bash new file mode 100644 index 00000000..ce920f44 --- /dev/null +++ b/ci/configs/sanitize.bash @@ -0,0 +1,7 @@ +CI_DESC="CI job running ThreadSanitizer" +CI_DIR=build-sanitize +export CXX=clang++ +export CXXFLAGS="-ggdb -Werror -Wall -Wextra -Wpedantic -Wthread-safety-analysis -Wno-unused-parameter -fsanitize=thread" +CMAKE_ARGS=() +BUILD_ARGS=(-k -j4) +BUILD_TARGETS=(mptest) diff --git a/ci/scripts/ci.sh b/ci/scripts/ci.sh index 23c4cc13..baf21700 100755 --- a/ci/scripts/ci.sh +++ b/ci/scripts/ci.sh @@ -11,9 +11,12 @@ set -o errexit -o nounset -o pipefail -o xtrace [ "${CI_CONFIG+x}" ] && source "$CI_CONFIG" : "${CI_DIR:=build}" +if ! [ -v BUILD_TARGETS ]; then + BUILD_TARGETS=(all tests mpexamples) +fi [ -n "${CI_CLEAN-}" ] && rm -rf "${CI_DIR}" cmake -B "$CI_DIR" "${CMAKE_ARGS[@]+"${CMAKE_ARGS[@]}"}" -cmake --build "$CI_DIR" -t all tests mpexamples -- "${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"}" +cmake --build "$CI_DIR" -t "${BUILD_TARGETS[@]}" -- "${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"}" ctest --test-dir "$CI_DIR" --output-on-failure From 0d986ff144cde007e8d6fa71c1f0632a623b7c67 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Thu, 26 Jun 2025 11:59:01 -0400 Subject: [PATCH 02/11] mptest: fix race condition in TestSetup constructor Fix race condition caught by TSAN between TestSetup constructor and std::thread launched by the constructor, which could lead to the constructor setting member variables to default values after the std::thread set them, which would not be the intended order and could lead to bugs. This change fixes all TSAN errors from the test except one, which will be fixed in a subsequent commit in this PR. The TSAN errors looked like the following (this is just the first error, there were different ones as well): [ TEST ] test.cpp:108: Call FooInterface methods ================== WARNING: ThreadSanitizer: data race (pid=1970628) Write of size 8 at 0x7ffcf7ba4c30 by thread T1: #0 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const::{lambda(mp::Connection&)#1}::operator()(mp::Connection&) const test/mp/test/test.cpp:71 (mptest+0x131c3d) #1 capnp::Capability::Client std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const::{lambda(mp::Connection&)#1}&, mp::Connection&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x131a25) #2 std::enable_if, capnp::Capability::Client>::type std::__invoke_r(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const::{lambda(mp::Connection&)#1}&, mp::Connection&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:114 (mptest+0x131a25) #3 std::_Function_handler::_M_invoke(std::_Any_data const&, mp::Connection&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_function.h:290 (mptest+0x131a25) #4 mp::Connection::Connection(mp::EventLoop&, kj::Own&&, std::function const&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_function.h:591 (mptest+0x13167f) #5 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:1077 (mptest+0x13054c) #6 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x1303f9) #7 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x1303f9) #8 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x1303f9) #9 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x1303f9) #10 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x1303f9) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Previous write of size 8 at 0x7ffcf7ba4c30 by main thread: #0 __tsan_memset ??:? (mptest+0x8811f) #1 mp::test::TestSetup::TestSetup(bool) test/mp/test/test.cpp:57 (mptest+0x12c3b4) #2 mp::test::TestCase108::run() test/mp/test/test.cpp:110 (mptest+0x128160) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) Location is stack of main thread. Thread T1 (tid=1970630, running) created by main thread at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::test::TestCase108::run() test/mp/test/test.cpp:110 (mptest+0x128160) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) SUMMARY: ThreadSanitizer: data race test/mp/test/test.cpp:71 in mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const::{lambda(mp::Connection&)#1}::operator()(mp::Connection&) const --- test/mp/test/test.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/mp/test/test.cpp b/test/mp/test/test.cpp index fa5a6f4c..f621356d 100644 --- a/test/mp/test/test.cpp +++ b/test/mp/test/test.cpp @@ -49,12 +49,14 @@ namespace test { class TestSetup { public: - std::thread thread; std::function server_disconnect; std::function client_disconnect; std::promise>> client_promise; std::unique_ptr> client; ProxyServer* server{nullptr}; + //! Thread variable should be after other struct members so the thread does + //! not start until the other members are initialized. + std::thread thread; TestSetup(bool client_owns_connection = true) : thread{[&] { From 07230f259f55b9444a00ae89a9847a8790dcafcb Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Fri, 27 Jun 2025 12:25:11 -0400 Subject: [PATCH 03/11] refactor: rename ProxyClient::m_cleanup_it Rename `ProxyClient` `m_cleanup_it` member to `m_disconnect_cb`. There is no change in behavor in this commit. The change is just being made so a bugfix in an upcoming commit will be more obvious. The `m_cleanup_it` name was a bad name because it didn't make it clear that this cleanup function only runs on sudden disconnects, not clean shutdowns. It was also confusing because this is a member of ProxyClient object, which has its own cleanup functions, but this doesn't actually refer to one of those functions, it refers to a cleanup function in an entirely different object (the Connection object). --- include/mp/proxy-io.h | 20 ++++++++++---------- src/mp/proxy.cpp | 16 ++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index 3ed7bad7..196397a3 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -66,16 +66,16 @@ struct ProxyClient : public ProxyClientBase ProxyClient(const ProxyClient&) = delete; ~ProxyClient(); - void setCleanup(const std::function& fn); - - //! Cleanup function to run when the connection is closed. If the Connection - //! gets destroyed before this ProxyClient object, this cleanup - //! callback lets it destroy this object and remove its entry in the - //! thread's request_threads or callback_threads map (after resetting - //! m_cleanup_it so the destructor does not try to access it). But if this - //! object gets destroyed before the Connection, there's no need to run the - //! cleanup function and the destructor will unregister it. - std::optional m_cleanup_it; + void setDisconnectCallback(const std::function& fn); + + //! Reference to callback function that is run if there is a sudden + //! disconnect and the Connection object is destroyed before this + //! ProxyClient object. The callback will destroy this object and + //! remove its entry from the thread's request_threads or callback_threads + //! map. It will also reset m_disconnect_cb so the destructor does not + //! access it. In the normal case where there is no sudden disconnect, the + //! destructor will unregister m_disconnect_cb so the callback is never run. + std::optional m_disconnect_cb; }; template <> diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp index 579f7995..478df766 100644 --- a/src/mp/proxy.cpp +++ b/src/mp/proxy.cpp @@ -311,16 +311,16 @@ std::tuple SetThread(ConnThreads& threads, std::mutex& mutex, thread = threads.emplace( std::piecewise_construct, std::forward_as_tuple(connection), std::forward_as_tuple(make_thread(), connection, /* destroy_connection= */ false)).first; - thread->second.setCleanup([&threads, &mutex, thread] { + thread->second.setDisconnectCallback([&threads, &mutex, thread] { // Note: it is safe to use the `thread` iterator in this cleanup // function, because the iterator would only be invalid if the map entry // was removed, and if the map entry is removed the ProxyClient // destructor unregisters the cleanup. // Connection is being destroyed before thread client is, so reset - // thread client m_cleanup_it member so thread client destructor does not + // thread client m_disconnect_cb member so thread client destructor does not // try unregister this callback after connection is destroyed. - thread->second.m_cleanup_it.reset(); + thread->second.m_disconnect_cb.reset(); // Remove connection pointer about to be destroyed from the map const std::unique_lock lock(mutex); threads.erase(thread); @@ -333,16 +333,16 @@ ProxyClient::~ProxyClient() // If thread is being destroyed before connection is destroyed, remove the // cleanup callback that was registered to handle the connection being // destroyed before the thread being destroyed. - if (m_cleanup_it) { - m_context.connection->removeSyncCleanup(*m_cleanup_it); + if (m_disconnect_cb) { + m_context.connection->removeSyncCleanup(*m_disconnect_cb); } } -void ProxyClient::setCleanup(const std::function& fn) +void ProxyClient::setDisconnectCallback(const std::function& fn) { assert(fn); - assert(!m_cleanup_it); - m_cleanup_it = m_context.connection->addSyncCleanup(fn); + assert(!m_disconnect_cb); + m_disconnect_cb = m_context.connection->addSyncCleanup(fn); } ProxyServer::ProxyServer(ThreadContext& thread_context, std::thread&& thread) From 81d58f5580e892858f42f841d7a72fee9c516850 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Fri, 27 Jun 2025 12:42:56 -0400 Subject: [PATCH 04/11] refactor: Rename ProxyClient cleanup_it variable Rename `ProxyClient` constructor `cleanup_it` local variable to `disconnect_cb` to make a bugfix in an upcoming commit more obvious. Reasons for renaming this variable are the same as the ones stated for renaming a similar variable in the previous commit. --- include/mp/proxy-io.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index 196397a3..aa91d735 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -393,7 +393,7 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli { // Handler for the connection getting destroyed before this client object. - auto cleanup_it = m_context.connection->addSyncCleanup([this]() { + auto disconnect_cb = m_context.connection->addSyncCleanup([this]() { // Release client capability by move-assigning to temporary. { typename Interface::Client(std::move(m_client)); @@ -410,13 +410,13 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // down while external code is still holding client references. // // The first case is handled here when m_context.connection is not null. The - // second case is handled by the cleanup function, which sets m_context.connection to - // null so nothing happens here. - m_context.cleanup_fns.emplace_front([this, destroy_connection, cleanup_it]{ + // second case is handled by the disconnect_cb function, which sets + // m_context.connection to null so nothing happens here. + m_context.cleanup_fns.emplace_front([this, destroy_connection, disconnect_cb]{ if (m_context.connection) { - // Remove cleanup callback so it doesn't run and try to access - // this object after it's already destroyed. - m_context.connection->removeSyncCleanup(cleanup_it); + // Remove disconnect callback so it doesn't run and try to access this + // object after it's already destroyed. + m_context.connection->removeSyncCleanup(disconnect_cb); // If the capnp interface defines a destroy method, call it to destroy // the remote object, waiting for it to be deleted server side. If the From 97e82ce19c47cb74b735f37c9b5729a17b26ec28 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Sat, 28 Jun 2025 08:08:18 -0400 Subject: [PATCH 05/11] doc: Add note about Waiter::m_mutex and interaction with the EventLoop::m_mutex --- include/mp/proxy-io.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index aa91d735..d52b02de 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -298,6 +298,13 @@ struct Waiter }); } + //! Mutex mainly used internally by waiter class, but also used externally + //! to guard access to related state. Specifically, since the thread_local + //! ThreadContext struct owns a Waiter, the Waiter::m_mutex is used to guard + //! access to other parts of the struct to avoid needing to deal with more + //! mutexes than necessary. This mutex can be held at the same time as + //! EventLoop::m_mutex as long as Waiter::mutex is locked first and + //! EventLoop::m_mutex is locked second. std::mutex m_mutex; std::condition_variable m_cv; std::optional> m_fn; From d8011c83608e63db0caba2d6e1ef354537f25a63 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Thu, 26 Jun 2025 12:39:10 -0400 Subject: [PATCH 06/11] proxy-io: fix race conditions in ProxyClientBase cleanup handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a TSAN data race in the "disconnecting and blocking during the call" unit test. The error output is below and problem it shows is that the ProxyClientBase `m_context.connection->removeSyncCleanup(disconnect_cb);` call is dereferencing `Connection::m_loop.m_loop` pointer variable shortly before the ~Connection destructor sets the pointer to null in another thread, without any synchronization in between. This race could potentially cause segfaults if the timing was different and the onDisconnect call happened at the same time as the removeSyncCleanup call. Fix the error by moving the removeSyncCleanup call into a loop->sync() block so it runs on the event loop thread instead of running asynchronously. Moving this call fixes the TSAN error shown below but it causes 3 new TSAN errors in the same unit test, which will be fixed in a seperate commit. The TSAN error below error shows the ProxyClientBase object not using proper synchronization when accessing a Connection object field before the Connection object is destroyed. But this fix here causes Connection destruction to delayed, so the new errors show the opposite problem of code called from the Connection destructor not using proper synchronization before accessing ProxyClientBase fields. To explain the error output below in more detail: The test case that triggers this data race has 3 threads. There is the main client thread calling foo->callFnAsync() which is idle during this time. There is the corresponding server thread T11 in the stack trace below which is executing the callFnAsync() function, and calling setup.client_disconnect() to check for correct behavior when there is a disconnect during the IPC call. Finally there is the event loop thread T10 in the stack trace below which is actually deleting the server Connection object in response to the client_disconnect() call (which deletes the client Connection object). During the callFnAsync() call, the server thread T11 responsible for executing that call creates a ProxyClient object and inserts it into the g_thread_context.request_threads std::map in the type-context.h CustomPassField function, in case the server needs to make an IPC call to the client (which it does not in this test). Before the CustomPassField funcion returns it deletes the entry in the request_threads map, deleting the ProxyClient object, and leading to the ProxyClientBase `m_context.connection->removeSyncCleanup(disconnect_cb);` call referenced in the beginning which accesses the m_loop pointer variable without a lock. Error looks like: WARNING: ThreadSanitizer: data race (pid=2025645) Write of size 8 at 0x726000012c00 by thread T10: #0 mp::EventLoopRef::reset(bool) src/mp/proxy.cpp:61 (mptest+0x2213bf) #1 mp::Connection::~Connection() include/mp/proxy.h:58 (mptest+0x221a9b) #2 kj::_::TransformPromiseNode::getImpl(kj::_::ExceptionOrValue&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:93 (mptest+0x133817) #3 kj::_::TransformPromiseNodeBase::get(kj::_::ExceptionOrValue&) ??:? (libkj-async.so.1.1.0+0x3da3d) (BuildId: 21ff3d5ab929b5e72be4dbbab1a9223f705af6e4) #4 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x222f60) #5 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #6 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #7 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #8 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #9 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #10 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Previous read of size 8 at 0x726000012c00 by thread T11 (mutexes: write M0): #0 mp::Connection::removeSyncCleanup(std::_List_iterator >) include/mp/proxy.h:60 (mptest+0x221dbf) #1 std::_Function_handler::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#2}>::_M_invoke(std::_Any_data const&) include/mp/proxy-io.h:419 (mptest+0x228814) #2 mp::CleanupRun(std::__cxx11::list, std::allocator > >&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_function.h:591 (mptest+0x1ca65b) #3 mp::ProxyClientBase::~ProxyClientBase() include/mp/proxy-io.h:446 (mptest+0x227790) #4 mp::ProxyClient::~ProxyClient() src/mp/proxy.cpp:339 (mptest+0x223fbd) #5 std::pair >::~pair() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_iterator.h:3013 (mptest+0x1cad8a) #6 void std::destroy_at > >(std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_construct.h:88 (mptest+0x1cad8a) #7 void std::allocator_traits > > > >::destroy > >(std::allocator > > >&, std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/alloc_traits.h:599 (mptest+0x1cad8a) #8 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_destroy_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:621 (mptest+0x1cad8a) #9 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_drop_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:629 (mptest+0x1cad8a) #10 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1934 (mptest+0x1cad8a) #11 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase_aux(std::_Rb_tree_const_iterator > >, std::_Rb_tree_const_iterator > >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1251 (mptest+0x1cace1) #12 _ZZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEvENKUlvE0_clEv /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:2519 (mptest+0x1f9b7e) #13 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_E3runEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:2007 (mptest+0x1f915d) #14 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_ED2Ev /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:1996 (mptest+0x1f915d) #15 _ZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEv include/mp/type-context.h:122 (mptest+0x1f915d) #16 _ZN2kj8FunctionIFvvEE4ImplIZN2mp9PassFieldINS4_8AccessorINS4_10foo_fields7ContextELi17EEENS4_19ServerInvokeContextINS4_11ProxyServerINS4_4test8messages12FooInterfaceEEEN5capnp11CallContextINSE_17CallFnAsyncParamsENSE_18CallFnAsyncResultsEEEEENS4_10ServerCallEJNS4_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS4_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS4_8PriorityILi1EEESO_RSV_RKT1_DpOT2_EUlvE_EclEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:142 (mptest+0x1f8e89) #17 kj::Function::operator()() /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:119 (mptest+0x1547f6) #18 void mp::Unlock, kj::Function&>(std::unique_lock&, kj::Function&) include/mp/util.h:198 (mptest+0x1547f6) #19 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() include/mp/proxy-io.h:294 (mptest+0x2266ca) #20 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Location is heap block of size 1024 at 0x726000012c00 allocated by thread T10: #0 operator new(unsigned long) ??:? (mptest+0x127c3c) #1 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:1077 (mptest+0x130879) #2 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #3 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #4 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #5 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #6 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #7 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Mutex M0 (0x721c00003790) created at: #0 pthread_mutex_lock ??:? (mptest+0x9aa5c) #1 __gthread_mutex_lock(pthread_mutex_t*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/x86_64-unknown-linux-gnu/bits/gthr-default.h:762 (mptest+0x2265e2) #2 std::mutex::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_mutex.h:113 (mptest+0x2265e2) #3 std::unique_lock::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:147 (mptest+0x2265e2) #4 std::unique_lock::unique_lock(std::mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:73 (mptest+0x2265e2) #5 mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const src/mp/proxy.cpp:399 (mptest+0x2265e2) #6 void std::__invoke_impl::makeThread(capnp::CallContext)::$_0>(std::__invoke_other, mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x2265e2) #7 std::__invoke_result::makeThread(capnp::CallContext)::$_0>::type std::__invoke::makeThread(capnp::CallContext)::$_0>(mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x2265e2) #8 void std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x2265e2) #9 std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x2265e2) #10 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x2265e2) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Thread T10 (tid=2025656, running) created by main thread at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::test::TestCase251::run() test/mp/test/test.cpp:271 (mptest+0x12b5bd) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) Thread T11 (tid=2025657, running) created by thread T10 at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:602 (mptest+0x220bb2) #3 virtual thunk to mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:? (mptest+0x220bb2) #4 capnp::LocalClient::callInternal(unsigned long, unsigned short, capnp::CallContextHook&) ??:? (libcapnp-rpc.so.1.1.0+0x7551d) (BuildId: ec28fb7ea510d2e28e99522abd0ce44adde7cf44) #5 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x222f60) #6 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #7 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #8 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #9 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #10 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #11 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #12 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) SUMMARY: ThreadSanitizer: data race src/mp/proxy.cpp:61 in mp::EventLoopRef::reset(bool) ================== [ PASS ] test.cpp:251: Calling IPC method, disconnecting and blocking during the call (175646 μs) 5 test(s) passed ThreadSanitizer: reported 1 warnings nt method call interrupted by disconnect. LOG0: {mptest-2025645/mptest-2025654} IPC server destroy N2mp11ProxyServerINS_4test8messages12FooInterfaceEEE LOG0: {mptest-2025645/mptest-2025654} EventLoop::loop done, cancelling event listeners. LOG0: {mptest-2025645/mptest-2025654} EventLoop::loop bye. LOG0: {mptest-2025645/mptest-2025645} IPC client send FooInterface.add$Params (a = 1, b = 2) LOG0: {mptest-2025645/mptest-2025656} IPC server recv request #35 FooInterface.add$Params (a = 1, b = 2) LOG0: {mptest-2025645/mptest-2025656} IPC server send response #35 FooInterface.add$Results (result = 3) LOG0: {mptest-2025645/mptest-2025645} IPC client recv FooInterface.add$Results (result = 3) LOG0: {mptest-2025645/mptest-2025645} IPC client send FooInterface.initThreadMap$Params (threadMap = ) LOG0: {mptest-2025645/mptest-2025656} IPC server recv request #36 FooInterface.initThreadMap$Params (threadMap = ) LOG0: {mptest-2025645/mptest-2025656} IPC server send response #36 FooInterface.initThreadMap$Results (threadMap = ) LOG0: {mptest-2025645/mptest-2025645} IPC client recv FooInterface.initThreadMap$Results (threadMap = ) LOG0: {mptest-2025645/mptest-2025645} IPC client send FooInterface.callFnAsync$Params (context = (thread = , callbackThread = )) LOG0: {mptest-2025645/mptest-2025656} IPC server recv request #37 FooInterface.callFnAsync$Params (context = (thread = , callbackThread = )) LOG0: {mptest-2025645/mptest-2025656} IPC server post request #37 {mptest-2025645/mptest-2025657 (from mptest-2025645/mptest-2025645)} LOG1: IPC client method call interrupted by disconnect. LOG0: {mptest-2025645/mptest-2025656} IPC server send response #37 FooInterface.callFnAsync$Results () LOG0: {mptest-2025645/mptest-2025656} IPC server destroy N2mp11ProxyServerINS_4test8messages12FooInterfaceEEE LOG0: {mptest-2025645/mptest-2025656} EventLoop::loop done, cancelling event listeners. LOG0: {mptest-2025645/mptest-2025656} EventLoop::loop bye. 0% tests passed, 1 tests failed out of 1 Total Test time (real) = 0.22 sec The following tests FAILED: 1 - mptest (Failed) --- include/mp/proxy-io.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index d52b02de..b6556e86 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -420,11 +420,7 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // second case is handled by the disconnect_cb function, which sets // m_context.connection to null so nothing happens here. m_context.cleanup_fns.emplace_front([this, destroy_connection, disconnect_cb]{ - if (m_context.connection) { - // Remove disconnect callback so it doesn't run and try to access this - // object after it's already destroyed. - m_context.connection->removeSyncCleanup(disconnect_cb); - + { // If the capnp interface defines a destroy method, call it to destroy // the remote object, waiting for it to be deleted server side. If the // capnp interface does not define a destroy method, this will just call @@ -433,6 +429,14 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code m_context.loop->sync([&]() { + // Remove disconnect callback on cleanup so it doesn't run and try + // to access this object after it's destroyed. This call needs to + // run inside loop->sync() on the event loop thread because + // otherwise, if there were an ill-timed disconnect, the + // onDisconnect handler could fire and delete the Connection object + // before the removeSyncCleanup call. + if (m_context.connection) m_context.connection->removeSyncCleanup(disconnect_cb); + // Release client capability by move-assigning to temporary. { typename Interface::Client(std::move(m_client)); From fe4a188803c64d153ef587690e5d02a35fa23460 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Fri, 27 Jun 2025 14:19:51 -0400 Subject: [PATCH 07/11] proxy-io: fix race conditions in disconnect callback code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 3 TSAN data races exposed by previous commit. (Context is described in the previous commit message.) The first race condition happens setting `m_context.connection = nullptr` in ProxyClientBase disconnect_cb callback without a lock, after a prior `m_context.connection` pointer dereference in the ProxyClient::~ProxyClient() destructor in another thread. This is fixed by adding using the `m_context.loop->m_mutex` mutex to guard acceses to the `m_connection` variable. The second race condition happens in the ~Connection destructor accessing the `m_sync_cleanup_fns` without synchronization after a prior call to removeSyncCleanup from ProxyClient::~ProxyClient() in the other thread. This is fixed by using a lock to guard access to `m_sync_cleanup_fns` here. (Was already covered by this lock in the other functions). The third race condition happens in the Connection destructor setting its m_loop.m_loop pointer to null without synchronization after the ProxyClient::~ProxyClient() destructor dereferenced it earlier inside a connection->removeSyncCleanup() call. The destructor will never actually make that call if the Connection is destroyed because it checks to see if m_disconnect_cb is null beforehand. However m_disconnect_cb is sometimes accesed without synchronization so TSAN correctly reports a data race. This problem is fixed by consistently guarding m_disconnect_cb with Waiter::m_mutex locks. This fix also depends on the second fix above to make the ~Connection destructor actually wait for the removeSyncCleanup() call to finish. >>> Data race #1 accessing ProxyClientBase::m_context.connection <<< [ TEST ] test.cpp:251: Calling IPC method, disconnecting and blocking during the call LOG0: {mptest-2029510/mptest-2029510} IPC client send FooInterface.add$Params (a = 1, b = 2) LOG0: {mptest-2029510/mptest-2029521} IPC server recv request #35 FooInterface.add$Params (a = 1, b = 2) LOG0: {mptest-2029510/mptest-2029521} IPC server send response #35 FooInterface.add$Results (result = 3) LOG0: {mptest-2029510/mptest-2029510} IPC client recv FooInterface.add$Results (result = 3) LOG0: {mptest-2029510/mptest-2029510} IPC client send FooInterface.initThreadMap$Params (threadMap = ) LOG0: {mptest-2029510/mptest-2029521} IPC server recv request #36 FooInterface.initThreadMap$Params (threadMap = ) LOG0: {mptest-2029510/mptest-2029521} IPC server send response #36 FooInterface.initThreadMap$Results (threadMap = ) LOG0: {mptest-2029510/mptest-2029510} IPC client recv FooInterface.initThreadMap$Results (threadMap = ) LOG0: {mptest-2029510/mptest-2029510} IPC client send FooInterface.callFnAsync$Params (context = (thread = , callbackThread = )) LOG0: {mptest-2029510/mptest-2029521} IPC server recv request #37 FooInterface.callFnAsync$Params (context = (thread = , callbackThread = )) LOG0: {mptest-2029510/mptest-2029521} IPC server post request #37 {mptest-2029510/mptest-2029522 (from mptest-2029510/mptest-2029510)} LOG1: IPC client method call interrupted by disconnect. ================== WARNING: ThreadSanitizer: data race (pid=2029510) Write of size 8 at 0x722000004240 by thread T10: #0 mp::ProxyClientBase::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}::operator()() const include/mp/proxy-io.h:401 (mptest+0x2287c6) 396 auto disconnect_cb = m_context.connection->addSyncCleanup([this]() { 397 // Release client capability by move-assigning to temporary. 398 { 399 typename Interface::Client(std::move(m_client)); 400 } 401 m_context.connection = nullptr; 402 }); #1 void std::__invoke_impl::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}&>(std::__invoke_other, mp::ProxyClientBase::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x2287c6) #2 std::enable_if::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}&>, void>::type std::__invoke_r::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}&>(mp::ProxyClientBase::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:111 (mptest+0x2287c6) #3 std::_Function_handler::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}>::_M_invoke(std::_Any_data const&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_function.h:290 (mptest+0x2287c6) #4 mp::Connection::~Connection() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_function.h:591 (mptest+0x221a05) #5 kj::_::TransformPromiseNode::getImpl(kj::_::ExceptionOrValue&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:93 (mptest+0x133817) #6 kj::_::TransformPromiseNodeBase::get(kj::_::ExceptionOrValue&) ??:? (libkj-async.so.1.1.0+0x3da3d) (BuildId: 21ff3d5ab929b5e72be4dbbab1a9223f705af6e4) #7 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) #8 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #9 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #10 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #11 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #12 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #13 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #14 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Previous read of size 8 at 0x722000004240 by thread T11 (mutexes: write M0): #0 mp::ProxyClient::~ProxyClient() src/mp/proxy.cpp:337 (mptest+0x22404d) 336 if (m_disconnect_cb) { 337 m_context.connection->removeSyncCleanup(*m_disconnect_cb); 338 } #1 std::pair >::~pair() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_iterator.h:3013 (mptest+0x1cadda) #2 void std::destroy_at > >(std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_construct.h:88 (mptest+0x1cadda) #3 void std::allocator_traits > > > >::destroy > >(std::allocator > > >&, std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/alloc_traits.h:599 (mptest+0x1cadda) #4 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_destroy_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:621 (mptest+0x1cadda) #5 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_drop_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:629 (mptest+0x1cadda) #6 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1934 (mptest+0x1cadda) #7 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase_aux(std::_Rb_tree_const_iterator > >, std::_Rb_tree_const_iterator > >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1251 (mptest+0x1cad31) #8 _ZZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEvENKUlvE0_clEv /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:2519 (mptest+0x1f9c2e) #9 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_E3runEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:2007 (mptest+0x1f920d) #10 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_ED2Ev /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:1996 (mptest+0x1f920d) #11 _ZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEv include/mp/type-context.h:122 (mptest+0x1f920d) #12 _ZN2kj8FunctionIFvvEE4ImplIZN2mp9PassFieldINS4_8AccessorINS4_10foo_fields7ContextELi17EEENS4_19ServerInvokeContextINS4_11ProxyServerINS4_4test8messages12FooInterfaceEEEN5capnp11CallContextINSE_17CallFnAsyncParamsENSE_18CallFnAsyncResultsEEEEENS4_10ServerCallEJNS4_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS4_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS4_8PriorityILi1EEESO_RSV_RKT1_DpOT2_EUlvE_EclEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:142 (mptest+0x1f8f39) #13 kj::Function::operator()() /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:119 (mptest+0x154816) #14 void mp::Unlock, kj::Function&>(std::unique_lock&, kj::Function&) include/mp/util.h:198 (mptest+0x154816) #15 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() include/mp/proxy-io.h:294 (mptest+0x22677a) #16 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Location is heap block of size 128 at 0x722000004200 allocated by thread T11: #0 operator new(unsigned long) ??:? (mptest+0x127c3c) #1 std::__new_allocator > > >::allocate(unsigned long, void const*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/new_allocator.h:151 (mptest+0x2281e4) #2 std::allocator > > >::allocate(unsigned long) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/allocator.h:196 (mptest+0x2281e4) #3 std::allocator_traits > > > >::allocate(std::allocator > > >&, unsigned long) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/alloc_traits.h:515 (mptest+0x2281e4) #4 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_get_node() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:559 (mptest+0x2281e4) #5 std::_Rb_tree_node > >* std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_create_node, std::tuple >(std::piecewise_construct_t const&, std::tuple&&, std::tuple&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:609 (mptest+0x2281e4) #6 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_Auto_node::_Auto_node, std::tuple >(std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >&, std::piecewise_construct_t const&, std::tuple&&, std::tuple&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1633 (mptest+0x228064) #7 std::pair > >, bool> std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_emplace_unique, std::tuple >(std::piecewise_construct_t const&, std::tuple&&, std::tuple&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:2432 (mptest+0x228064) #8 std::pair > >, bool> std::map, std::less, std::allocator > > >::emplace, std::tuple >(std::piecewise_construct_t const&, std::tuple&&, std::tuple&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_map.h:608 (mptest+0x223c07) #9 mp::SetThread(std::map, std::less, std::allocator > > >&, std::mutex&, mp::Connection*, std::function const&) src/mp/proxy.cpp:311 (mptest+0x223c07) #10 _ZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEv include/mp/type-context.h:93 (mptest+0x1f912b) #11 _ZN2kj8FunctionIFvvEE4ImplIZN2mp9PassFieldINS4_8AccessorINS4_10foo_fields7ContextELi17EEENS4_19ServerInvokeContextINS4_11ProxyServerINS4_4test8messages12FooInterfaceEEEN5capnp11CallContextINSE_17CallFnAsyncParamsENSE_18CallFnAsyncResultsEEEEENS4_10ServerCallEJNS4_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS4_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS4_8PriorityILi1EEESO_RSV_RKT1_DpOT2_EUlvE_EclEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:142 (mptest+0x1f8f39) #12 kj::Function::operator()() /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:119 (mptest+0x154816) #13 void mp::Unlock, kj::Function&>(std::unique_lock&, kj::Function&) include/mp/util.h:198 (mptest+0x154816) #14 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() include/mp/proxy-io.h:294 (mptest+0x22677a) #15 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Mutex M0 (0x721c00003790) created at: #0 pthread_mutex_lock ??:? (mptest+0x9aa5c) #1 __gthread_mutex_lock(pthread_mutex_t*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/x86_64-unknown-linux-gnu/bits/gthr-default.h:762 (mptest+0x226692) #2 std::mutex::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_mutex.h:113 (mptest+0x226692) #3 std::unique_lock::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:147 (mptest+0x226692) #4 std::unique_lock::unique_lock(std::mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:73 (mptest+0x226692) #5 mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const src/mp/proxy.cpp:399 (mptest+0x226692) #6 void std::__invoke_impl::makeThread(capnp::CallContext)::$_0>(std::__invoke_other, mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x226692) #7 std::__invoke_result::makeThread(capnp::CallContext)::$_0>::type std::__invoke::makeThread(capnp::CallContext)::$_0>(mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x226692) #8 void std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x226692) #9 std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x226692) #10 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x226692) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Thread T10 (tid=2029521, running) created by main thread at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::test::TestCase251::run() test/mp/test/test.cpp:271 (mptest+0x12b5bd) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) Thread T11 (tid=2029522, running) created by thread T10 at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:602 (mptest+0x220c62) #3 virtual thunk to mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:? (mptest+0x220c62) #4 capnp::LocalClient::callInternal(unsigned long, unsigned short, capnp::CallContextHook&) ??:? (libcapnp-rpc.so.1.1.0+0x7551d) (BuildId: ec28fb7ea510d2e28e99522abd0ce44adde7cf44) #5 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) #6 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #7 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #8 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #9 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #10 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #11 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #12 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) SUMMARY: ThreadSanitizer: data race include/mp/proxy-io.h:401 in mp::ProxyClientBase::ProxyClientBase(mp::Thread::Client, mp::Connection*, bool)::{lambda()#1}::operator()() const ================== >>> Data race #2 accessing Connection::m_sync_cleanup_fns <<< ================== WARNING: ThreadSanitizer: data race (pid=2029510) Write of size 8 at 0x726000012ff8 by thread T10: #0 std::__cxx11::_List_base, std::allocator > >::_M_dec_size(unsigned long) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_list.h:487 (mptest+0x221a20) #1 std::__cxx11::list, std::allocator > >::_M_erase(std::_List_iterator >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_list.h:2023 (mptest+0x221a20) #2 std::__cxx11::list, std::allocator > >::pop_front() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_list.h:1301 (mptest+0x221a20) #3 mp::Connection::~Connection() src/mp/proxy.cpp:137 (mptest+0x221a20) 135 while (!m_sync_cleanup_fns.empty()) { 136 m_sync_cleanup_fns.front()(); 137 m_sync_cleanup_fns.pop_front(); 138 } #4 kj::_::TransformPromiseNode::getImpl(kj::_::ExceptionOrValue&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:93 (mptest+0x133817) #5 kj::_::TransformPromiseNodeBase::get(kj::_::ExceptionOrValue&) ??:? (libkj-async.so.1.1.0+0x3da3d) (BuildId: 21ff3d5ab929b5e72be4dbbab1a9223f705af6e4) #6 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) #7 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #8 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #9 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #10 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #11 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #12 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #13 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Previous write of size 8 at 0x726000012ff8 by thread T11 (mutexes: write M0, write M1): #0 std::__cxx11::_List_base, std::allocator > >::_M_dec_size(unsigned long) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_list.h:487 (mptest+0x221ea5) #1 std::__cxx11::list, std::allocator > >::_M_erase(std::_List_iterator >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_list.h:2023 (mptest+0x221ea5) #2 std::__cxx11::list, std::allocator > >::erase(std::_List_const_iterator >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/list.tcc:158 (mptest+0x221ea5) #3 mp::Connection::removeSyncCleanup(std::_List_iterator >) src/mp/proxy.cpp:158 (mptest+0x221ea5) 155 void Connection::removeSyncCleanup(CleanupIt it) 156 { 157 const Lock lock(m_loop->m_mutex); 158 m_sync_cleanup_fns.erase(it); 159 } #4 mp::ProxyClient::~ProxyClient() src/mp/proxy.cpp:337 (mptest+0x224065) #5 std::pair >::~pair() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_iterator.h:3013 (mptest+0x1cadda) #6 void std::destroy_at > >(std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_construct.h:88 (mptest+0x1cadda) #7 void std::allocator_traits > > > >::destroy > >(std::allocator > > >&, std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/alloc_traits.h:599 (mptest+0x1cadda) #8 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_destroy_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:621 (mptest+0x1cadda) #9 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_drop_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:629 (mptest+0x1cadda) #10 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1934 (mptest+0x1cadda) #11 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase_aux(std::_Rb_tree_const_iterator > >, std::_Rb_tree_const_iterator > >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1251 (mptest+0x1cad31) #12 _ZZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEvENKUlvE0_clEv /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:2519 (mptest+0x1f9c2e) #13 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_E3runEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:2007 (mptest+0x1f920d) #14 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_ED2Ev /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:1996 (mptest+0x1f920d) #15 _ZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEv include/mp/type-context.h:122 (mptest+0x1f920d) #16 _ZN2kj8FunctionIFvvEE4ImplIZN2mp9PassFieldINS4_8AccessorINS4_10foo_fields7ContextELi17EEENS4_19ServerInvokeContextINS4_11ProxyServerINS4_4test8messages12FooInterfaceEEEN5capnp11CallContextINSE_17CallFnAsyncParamsENSE_18CallFnAsyncResultsEEEEENS4_10ServerCallEJNS4_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS4_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS4_8PriorityILi1EEESO_RSV_RKT1_DpOT2_EUlvE_EclEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:142 (mptest+0x1f8f39) #17 kj::Function::operator()() /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:119 (mptest+0x154816) #18 void mp::Unlock, kj::Function&>(std::unique_lock&, kj::Function&) include/mp/util.h:198 (mptest+0x154816) #19 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() include/mp/proxy-io.h:294 (mptest+0x22677a) #20 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Location is heap block of size 1024 at 0x726000012c00 allocated by thread T10: #0 operator new(unsigned long) ??:? (mptest+0x127c3c) #1 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:1077 (mptest+0x130879) #2 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #3 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #4 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #5 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #6 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #7 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Mutex M0 (0x721c00003790) created at: #0 pthread_mutex_lock ??:? (mptest+0x9aa5c) #1 __gthread_mutex_lock(pthread_mutex_t*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/x86_64-unknown-linux-gnu/bits/gthr-default.h:762 (mptest+0x226692) #2 std::mutex::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_mutex.h:113 (mptest+0x226692) #3 std::unique_lock::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:147 (mptest+0x226692) #4 std::unique_lock::unique_lock(std::mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:73 (mptest+0x226692) #5 mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const src/mp/proxy.cpp:399 (mptest+0x226692) #6 void std::__invoke_impl::makeThread(capnp::CallContext)::$_0>(std::__invoke_other, mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x226692) #7 std::__invoke_result::makeThread(capnp::CallContext)::$_0>::type std::__invoke::makeThread(capnp::CallContext)::$_0>(mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x226692) #8 void std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x226692) #9 std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x226692) #10 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x226692) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Mutex M1 (0x7f5d5d7fd510) created at: #0 pthread_mutex_lock ??:? (mptest+0x9aa5c) #1 __gthread_mutex_lock(pthread_mutex_t*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/x86_64-unknown-linux-gnu/bits/gthr-default.h:762 (mptest+0x22131c) #2 std::mutex::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_mutex.h:113 (mptest+0x22131c) #3 std::unique_lock::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:147 (mptest+0x22131c) #4 std::unique_lock::unique_lock(std::mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:73 (mptest+0x22131c) #5 mp::Lock::Lock(mp::Mutex&) include/mp/util.h:172 (mptest+0x22131c) #6 std::__detail::__variant::_Uninitialized::_Uninitialized(std::in_place_index_t<0ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:250 (mptest+0x22131c) #7 std::__detail::__variant::_Variadic_union::_Variadic_union(std::in_place_index_t<0ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:412 (mptest+0x22131c) #8 std::__detail::__variant::_Variadic_union::_Variadic_union<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:418 (mptest+0x22131c) #9 std::__detail::__variant::_Variant_storage::_Variant_storage<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:486 (mptest+0x22131c) #10 std::__detail::__variant::_Copy_ctor_base::_Variant_storage<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:584 (mptest+0x22131c) #11 std::__detail::__variant::_Move_ctor_base::_Variant_storage<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:621 (mptest+0x22131c) #12 std::__detail::__variant::_Copy_assign_base::_Variant_storage<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:659 (mptest+0x22131c) #13 std::__detail::__variant::_Move_assign_base::_Variant_storage<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:711 (mptest+0x22131c) #14 std::__detail::__variant::_Variant_base::_Variant_base<1ul, mp::Mutex&>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:773 (mptest+0x22131c) #15 std::variant::variant<1ul, mp::Mutex&, mp::Lock, void>(std::in_place_index_t<1ul>, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:1478 (mptest+0x22131c) #16 std::variant::variant(std::in_place_type_t, mp::Mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/variant:1458 (mptest+0x22131c) #17 mp::PtrOrValue::PtrOrValue(mp::Lock*, mp::Mutex&) include/mp/util.h:138 (mptest+0x22131c) #18 mp::EventLoopRef::EventLoopRef(mp::EventLoop&, mp::Lock*) src/mp/proxy.cpp:50 (mptest+0x22131c) #19 mp::Connection::Connection(mp::EventLoop&, kj::Own&&, std::function const&) include/mp/proxy-io.h:321 (mptest+0x1318e7) #20 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:1077 (mptest+0x1308dc) #21 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #22 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #23 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #24 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #25 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #26 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Thread T10 (tid=2029521, running) created by main thread at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::test::TestCase251::run() test/mp/test/test.cpp:271 (mptest+0x12b5bd) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) Thread T11 (tid=2029522, running) created by thread T10 at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:602 (mptest+0x220c62) #3 virtual thunk to mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:? (mptest+0x220c62) #4 capnp::LocalClient::callInternal(unsigned long, unsigned short, capnp::CallContextHook&) ??:? (libcapnp-rpc.so.1.1.0+0x7551d) (BuildId: ec28fb7ea510d2e28e99522abd0ce44adde7cf44) #5 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) #6 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #7 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #8 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #9 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #10 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #11 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #12 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) SUMMARY: ThreadSanitizer: data race src/mp/proxy.cpp:137 in mp::Connection::~Connection() ================== >>> Data race #3 accesing Connection::m_loop::m_loop <<< ================== WARNING: ThreadSanitizer: data race (pid=2029510) Write of size 8 at 0x726000012c00 by thread T10: #0 mp::EventLoopRef::reset(bool) src/mp/proxy.cpp:61 (mptest+0x22146f) 58 void EventLoopRef::reset(bool relock) MP_NO_TSA 59 { 60 if (auto* loop{m_loop}) { 61 m_loop = nullptr; 62 auto loop_lock{PtrOrValue{m_lock, loop->m_mutex}}; 63 loop_lock->assert_locked(loop->m_mutex); 64 assert(loop->m_num_clients > 0); 65 loop->m_num_clients -= 1; #1 mp::Connection::~Connection() include/mp/proxy.h:58 (mptest+0x221b4b) 58 ~EventLoopRef() { reset(); } #2 kj::_::TransformPromiseNode::getImpl(kj::_::ExceptionOrValue&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:93 (mptest+0x133817) #3 kj::_::TransformPromiseNodeBase::get(kj::_::ExceptionOrValue&) ??:? (libkj-async.so.1.1.0+0x3da3d) (BuildId: 21ff3d5ab929b5e72be4dbbab1a9223f705af6e4) #4 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) 228 for (;;) { 229 const size_t read_bytes = wait_stream->read(&buffer, 0, 1).wait(m_io_context.waitScope); 230 if (read_bytes != 1) throw std::logic_error("EventLoop wait_stream closed unexpectedly"); #5 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #6 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #7 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #8 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #9 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #10 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Previous read of size 8 at 0x726000012c00 by thread T11 (mutexes: write M0): #0 mp::Connection::removeSyncCleanup(std::_List_iterator >) include/mp/proxy.h:60 (mptest+0x221e6f) 60 EventLoop* operator->() const { assert(m_loop); return m_loop; } #1 mp::ProxyClient::~ProxyClient() src/mp/proxy.cpp:337 (mptest+0x224065) 155 void Connection::removeSyncCleanup(CleanupIt it) 156 { 157 const Lock lock(m_loop->m_mutex); 158 m_sync_cleanup_fns.erase(it); 159 } ... 336 if (m_disconnect_cb) { 337 m_context.connection->removeSyncCleanup(*m_disconnect_cb); 338 } #2 std::pair >::~pair() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_iterator.h:3013 (mptest+0x1cadda) #3 void std::destroy_at > >(std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_construct.h:88 (mptest+0x1cadda) #4 void std::allocator_traits > > > >::destroy > >(std::allocator > > >&, std::pair >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/alloc_traits.h:599 (mptest+0x1cadda) #5 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_destroy_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:621 (mptest+0x1cadda) #6 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_drop_node(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:629 (mptest+0x1cadda) #7 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase(std::_Rb_tree_node > >*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1934 (mptest+0x1cadda) #8 std::_Rb_tree >, std::_Select1st > >, std::less, std::allocator > > >::_M_erase_aux(std::_Rb_tree_const_iterator > >, std::_Rb_tree_const_iterator > >) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:1251 (mptest+0x1cad31) #9 _ZZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEvENKUlvE0_clEv /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/stl_tree.h:2519 (mptest+0x1f9c2e) #10 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_E3runEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:2007 (mptest+0x1f920d) #11 _ZN2kj1_8DeferredIZZN2mp9PassFieldINS2_8AccessorINS2_10foo_fields7ContextELi17EEENS2_19ServerInvokeContextINS2_11ProxyServerINS2_4test8messages12FooInterfaceEEEN5capnp11CallContextINSC_17CallFnAsyncParamsENSC_18CallFnAsyncResultsEEEEENS2_10ServerCallEJNS2_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL1p1_12call_context9getParamsEEENS2_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS2_8PriorityILi1EEESM_RST_RKT1_DpOT2_ENUlvE_clEvEUlvE0_ED2Ev /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/common.h:1996 (mptest+0x1f920d) #12 _ZZN2mp9PassFieldINS_8AccessorINS_10foo_fields7ContextELi17EEENS_19ServerInvokeContextINS_11ProxyServerINS_4test8messages12FooInterfaceEEEN5capnp11CallContextINS9_17CallFnAsyncParamsENS9_18CallFnAsyncResultsEEEEENS_10ServerCallEJNS_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS_7Context6ReaderEEE5valueEN2kj7PromiseINT0_11CallContextEEEE4typeENS_8PriorityILi1EEESJ_RSR_RKT1_DpOT2_ENUlvE_clEv include/mp/type-context.h:122 (mptest+0x1f920d) 104 KJ_DEFER({ 105 std::unique_lock lock(thread_context.waiter->m_mutex); 106 // Call erase here with a Connection* argument instead 107 // of an iterator argument, because the `request_thread` 108 // iterator may be invalid if the connection is closed 109 // during this function call. More specifically, the 110 // iterator may be invalid because SetThread adds a 111 // cleanup callback to the Connection destructor that 112 // erases the thread from the map, and also because the 113 // ProxyServer destructor calls 114 // request_threads.clear(). 115 if (erase_thread) { 116 disconnected = !request_threads.erase(server.m_context.connection); 117 } else { 118 disconnected = !request_threads.count(server.m_context.connection); 119 } 120 }); 121 fn.invoke(server_context, args...); 122 } #13 _ZN2kj8FunctionIFvvEE4ImplIZN2mp9PassFieldINS4_8AccessorINS4_10foo_fields7ContextELi17EEENS4_19ServerInvokeContextINS4_11ProxyServerINS4_4test8messages12FooInterfaceEEEN5capnp11CallContextINSE_17CallFnAsyncParamsENSE_18CallFnAsyncResultsEEEEENS4_10ServerCallEJNS4_8TypeListIJEEEEEENSt9enable_ifIXsr3std7is_sameIDTclsrT_3getcldtdtfL0p1_12call_context9getParamsEEENS4_7Context6ReaderEEE5valueENS_7PromiseINT0_11CallContextEEEE4typeENS4_8PriorityILi1EEESO_RSV_RKT1_DpOT2_EUlvE_EclEv /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:142 (mptest+0x1f8f39) #14 kj::Function::operator()() /nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/function.h:119 (mptest+0x154816) #15 void mp::Unlock, kj::Function&>(std::unique_lock&, kj::Function&) include/mp/util.h:198 (mptest+0x154816) #16 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() include/mp/proxy-io.h:294 (mptest+0x22677a) #17 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Location is heap block of size 1024 at 0x726000012c00 allocated by thread T10: #0 operator new(unsigned long) ??:? (mptest+0x127c3c) #1 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_ptr.h:1077 (mptest+0x130879) #2 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #3 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #4 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #5 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #6 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #7 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Mutex M0 (0x721c00003790) created at: #0 pthread_mutex_lock ??:? (mptest+0x9aa5c) #1 __gthread_mutex_lock(pthread_mutex_t*) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/x86_64-unknown-linux-gnu/bits/gthr-default.h:762 (mptest+0x226692) #2 std::mutex::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_mutex.h:113 (mptest+0x226692) #3 std::unique_lock::lock() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:147 (mptest+0x226692) #4 std::unique_lock::unique_lock(std::mutex&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/unique_lock.h:73 (mptest+0x226692) #5 mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const src/mp/proxy.cpp:399 (mptest+0x226692) #6 void std::__invoke_impl::makeThread(capnp::CallContext)::$_0>(std::__invoke_other, mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x226692) #7 std::__invoke_result::makeThread(capnp::CallContext)::$_0>::type std::__invoke::makeThread(capnp::CallContext)::$_0>(mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x226692) #8 void std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x226692) #9 std::thread::_Invoker::makeThread(capnp::CallContext)::$_0> >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x226692) #10 std::thread::_State_impl::makeThread(capnp::CallContext)::$_0> > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x226692) #11 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) Thread T10 (tid=2029521, running) created by main thread at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::test::TestCase251::run() test/mp/test/test.cpp:271 (mptest+0x12b5bd) #3 kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::{lambda()#1}&&) ??:? (libkj-test.so.1.1.0+0x7290) (BuildId: 50ddf81234cd06daf5f2d3f11713ed193ade4eb7) Thread T11 (tid=2029522, running) created by thread T10 at: #0 pthread_create ??:? (mptest+0x9a2a5) #1 std::thread::_M_start_thread(std::unique_ptr >, void (*)()) ??:? (libstdc++.so.6+0xed138) #2 mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:602 (mptest+0x220c62) #3 virtual thunk to mp::ThreadMap::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext) build-tsan/include/mp/proxy.capnp.c++:? (mptest+0x220c62) #4 capnp::LocalClient::callInternal(unsigned long, unsigned short, capnp::CallContextHook&) ??:? (libcapnp-rpc.so.1.1.0+0x7551d) (BuildId: ec28fb7ea510d2e28e99522abd0ce44adde7cf44) #5 mp::EventLoop::loop() src/mp/proxy.cpp:229 (mptest+0x223010) #6 mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const test/mp/test/test.cpp:92 (mptest+0x130d23) #7 void std::__invoke_impl(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:61 (mptest+0x130789) #8 std::__invoke_result::type std::__invoke(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/invoke.h:96 (mptest+0x130789) #9 void std::thread::_Invoker >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:301 (mptest+0x130789) #10 std::thread::_Invoker >::operator()() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:308 (mptest+0x130789) #11 std::thread::_State_impl > >::_M_run() /nix/store/9ds850ifd4jwcccpp3v14818kk74ldf2-gcc-14.2.1.20250322/include/c++/14.2.1.20250322/bits/std_thread.h:253 (mptest+0x130789) #12 execute_native_thread_routine ??:? (libstdc++.so.6+0xed063) SUMMARY: ThreadSanitizer: data race src/mp/proxy.cpp:61 in mp::EventLoopRef::reset(bool) ================== LOG0: {mptest-2029510/mptest-2029521} IPC server send response #37 FooInterface.callFnAsync$Results () LOG0: {mptest-2029510/mptest-2029521} IPC server destroy N2mp11ProxyServerINS_4test8messages12FooInterfaceEEE LOG0: {mptest-2029510/mptest-2029521} EventLoop::loop done, cancelling event listeners. LOG0: {mptest-2029510/mptest-2029521} EventLoop::loop bye. [ PASS ] test.cpp:251: Calling IPC method, disconnecting and blocking during the call (192025 μs) 5 test(s) passed ThreadSanitizer: reported 3 warnings --- include/mp/proxy-io.h | 3 +++ src/mp/proxy.cpp | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index b6556e86..367a9beb 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -75,6 +75,8 @@ struct ProxyClient : public ProxyClientBase //! map. It will also reset m_disconnect_cb so the destructor does not //! access it. In the normal case where there is no sudden disconnect, the //! destructor will unregister m_disconnect_cb so the callback is never run. + //! Since this variable is accessed from multiple threads, accesses should + //! be guarded with the associated Waiter::m_mutex. std::optional m_disconnect_cb; }; @@ -405,6 +407,7 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli { typename Interface::Client(std::move(m_client)); } + Lock lock{m_context.loop->m_mutex}; m_context.connection = nullptr; }); diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp index 478df766..55ff05ea 100644 --- a/src/mp/proxy.cpp +++ b/src/mp/proxy.cpp @@ -132,9 +132,11 @@ Connection::~Connection() // on clean and unclean shutdowns. In unclean shutdown case when the // connection is broken, sync and async cleanup lists will filled with // callbacks. In the clean shutdown case both lists will be empty. + Lock lock{m_loop->m_mutex}; while (!m_sync_cleanup_fns.empty()) { - m_sync_cleanup_fns.front()(); - m_sync_cleanup_fns.pop_front(); + CleanupList fn; + fn.splice(fn.begin(), m_sync_cleanup_fns, m_sync_cleanup_fns.begin()); + Unlock(lock, fn.front()); } } @@ -320,9 +322,9 @@ std::tuple SetThread(ConnThreads& threads, std::mutex& mutex, // Connection is being destroyed before thread client is, so reset // thread client m_disconnect_cb member so thread client destructor does not // try unregister this callback after connection is destroyed. - thread->second.m_disconnect_cb.reset(); // Remove connection pointer about to be destroyed from the map const std::unique_lock lock(mutex); + thread->second.m_disconnect_cb.reset(); threads.erase(thread); }); return {thread, true}; From 84b292fcc4db0991fc1a806d7d9c548426047583 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Sat, 28 Jun 2025 06:49:45 -0400 Subject: [PATCH 08/11] mptest: fix MemorySanitizer: use-of-uninitialized-value Fix MemorySanitizer: use-of-uninitialized-value error in "disconnecting and blocking" promise::get_future call reported: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3015153067 - https://cirrus-ci.com/task/4658925234028544and and fixed: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3015232660 An issue exists to add an MSAN CI job to catch errors like this more quickly in the future https://github.com/bitcoin-core/libmultiprocess/issues/188 Error looks like: [ TEST ] test.cpp:251: Calling IPC method, disconnecting and blocking during the call ... MemorySanitizer: use-of-uninitialized-value #0 0x7f83ecb19853 in std::__1::promise::get_future() /msan/llvm-project/libcxx/src/future.cpp:154:16 #1 0x55bf563af03c in mp::test::TestCase251::run()::$_0::operator()() const /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/test/mp/test/test.cpp:280:16 #2 0x55bf563af03c in decltype(std::declval()()) std::__1::__invoke[abi:de200100](mp::test::TestCase251::run()::$_0&) /msan/cxx_build/include/c++/v1/__type_traits/invoke.h:179:25 #3 0x55bf563af03c in void std::__1::__invoke_void_return_wrapper::__call[abi:de200100](mp::test::TestCase251::run()::$_0&) /msan/cxx_build/include/c++/v1/__type_traits/invoke.h:251:5 #4 0x55bf563af03c in void std::__1::__invoke_r[abi:de200100](mp::test::TestCase251::run()::$_0&) /msan/cxx_build/include/c++/v1/__type_traits/invoke.h:273:10 #5 0x55bf563af03c in std::__1::__function::__alloc_func, void ()>::operator()[abi:de200100]() /msan/cxx_build/include/c++/v1/__functional/function.h:167:12 #6 0x55bf563af03c in std::__1::__function::__func, void ()>::operator()() /msan/cxx_build/include/c++/v1/__functional/function.h:319:10 #7 0x55bf565f1b25 in std::__1::__function::__value_func::operator()[abi:de200100]() const /msan/cxx_build/include/c++/v1/__functional/function.h:436:12 #8 0x55bf565f1b25 in std::__1::function::operator()() const /msan/cxx_build/include/c++/v1/__functional/function.h:995:10 #9 0x55bf565f1b25 in mp::test::FooImplementation::callFnAsync() /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/test/mp/test/foo.h:83:40 #10 0x55bf565f1b25 in decltype(auto) mp::ProxyMethodTraits::invoke, capnp::CallContext>>(mp::ServerInvokeContext, capnp::CallContext>&) /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/include/mp/proxy.h:288:16 #11 0x55bf565f1b25 in decltype(auto) mp::ServerCall::invoke, capnp::CallContext>>(mp::ServerInvokeContext, capnp::CallContext>&, mp::TypeList<>) const /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/include/mp/proxy-types.h:448:16 #12 0x55bf565f1b25 in std::__1::enable_if::get(fp1.call_context.getParams())), mp::Context::Reader>::value, kj::Promise, capnp::CallContext>::CallContext>>::type mp::PassField, mp::ServerInvokeContext, capnp::CallContext>, mp::ServerCall, mp::TypeList<>>(mp::Priority<1>, mp::TypeList<>, mp::ServerInvokeContext, capnp::CallContext>&, mp::ServerCall const&, mp::TypeList<>&&)::'lambda'()::operator()() /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/include/mp/type-context.h:121:24 #13 0x55bf565f12d1 in kj::Function::Impl::get(fp1.call_context.getParams())), mp::Context::Reader>::value, kj::Promise, capnp::CallContext>::CallContext>>::type mp::PassField, mp::ServerInvokeContext, capnp::CallContext>, mp::ServerCall, mp::TypeList<>>(mp::Priority<1>, mp::TypeList<>, mp::ServerInvokeContext, capnp::CallContext>&, mp::ServerCall const&, mp::TypeList<>&&)::'lambda'()>::operator()() /ci_container_base/depends/x86_64-pc-linux-gnu/include/kj/function.h:142:14 #14 0x55bf5641125e in kj::Function::operator()() /ci_container_base/depends/x86_64-pc-linux-gnu/include/kj/function.h:119:12 #15 0x55bf5641125e in void mp::Unlock, kj::Function&>(std::__1::unique_lock&, kj::Function&) /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/include/mp/util.h:198:5 #16 0x55bf5667e45b in void mp::Waiter::wait::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'()>(std::__1::unique_lock&, mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'())::'lambda'()::operator()() const /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/./ipc/libmultiprocess/include/mp/proxy-io.h:294:17 #17 0x55bf5667e45b in void std::__1::condition_variable::wait::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'()>(std::__1::unique_lock&, mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'())::'lambda'()>(std::__1::unique_lock&, mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'()) /msan/cxx_build/include/c++/v1/__condition_variable/condition_variable.h:146:11 #18 0x55bf5667e45b in void mp::Waiter::wait::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'()>(std::__1::unique_lock&, mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const::'lambda'()) /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/./ipc/libmultiprocess/include/mp/proxy-io.h:285:14 #19 0x55bf5667e45b in mp::ProxyServer::makeThread(capnp::CallContext)::$_0::operator()() const /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/./ipc/libmultiprocess/src/mp/proxy.cpp:404:34 #20 0x55bf5667e45b in decltype(std::declval::makeThread(capnp::CallContext)::$_0>()()) std::__1::__invoke[abi:de200100]::makeThread(capnp::CallContext)::$_0>(mp::ProxyServer::makeThread(capnp::CallContext)::$_0&&) /msan/cxx_build/include/c++/v1/__type_traits/invoke.h:179:25 #21 0x55bf5667e45b in void std::__1::__thread_execute[abi:de200100]>, mp::ProxyServer::makeThread(capnp::CallContext)::$_0>(std::__1::tuple>, mp::ProxyServer::makeThread(capnp::CallContext)::$_0>&, std::__1::__tuple_indices<...>) /msan/cxx_build/include/c++/v1/__thread/thread.h:199:3 #22 0x55bf5667e45b in void* std::__1::__thread_proxy[abi:de200100]>, mp::ProxyServer::makeThread(capnp::CallContext)::$_0>>(void*) /msan/cxx_build/include/c++/v1/__thread/thread.h:208:3 #23 0x7f83ec69caa3 (/lib/x86_64-linux-gnu/libc.so.6+0x9caa3) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) #24 0x7f83ec729c3b (/lib/x86_64-linux-gnu/libc.so.6+0x129c3b) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) [11:46:33.109] Member fields were destroyed #0 0x55bf56348abd in __sanitizer_dtor_callback_fields /msan/llvm-project/compiler-rt/lib/msan/msan_interceptors.cpp:1044:5 #1 0x7f83ecb196de in ~promise /msan/cxx_build/include/c++/v1/future:1341:22 #2 0x7f83ecb196de in std::__1::promise::~promise() /msan/llvm-project/libcxx/src/future.cpp:151:1 #3 0x55bf563adb36 in mp::test::TestCase251::run() /ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/test/mp/test/test.cpp:293:1 #4 0x55bf5669252e in kj::TestRunner::run()::'lambda'()::operator()() const /usr/src/kj/test.c++:318:11 #5 0x55bf5669252e in kj::Maybe kj::runCatchingExceptions(kj::TestRunner::run()::'lambda'()&&) /usr/src/kj/exception.h:371:5 #6 0x55bf5669071e in kj::TestRunner::run() /usr/src/kj/test.c++:318:11 #7 0x55bf5668f977 in auto kj::TestRunner::getMain()::'lambda5'(auto&, auto&&...)::operator()(auto&, auto&&...) /usr/src/kj/test.c++:217:27 #8 0x55bf5668f977 in auto kj::_::BoundMethod::operator()<>() /usr/src/kj/function.h:263:12 #9 0x55bf5668f977 in kj::Function::Impl>::operator()() /usr/src/kj/function.h:142:14 #10 0x55bf56b7362c in kj::Function::operator()() /usr/src/kj/function.h:119:12 #11 0x55bf56b7362c in kj::MainBuilder::MainImpl::operator()(kj::StringPtr, kj::ArrayPtr) /usr/src/kj/main.c++:623:5 #12 0x55bf56b8865c in kj::Function)>::Impl::operator()(kj::StringPtr, kj::ArrayPtr) /usr/src/kj/function.h:142:14 #13 0x55bf56b6a592 in kj::Function)>::operator()(kj::StringPtr, kj::ArrayPtr) /usr/src/kj/function.h:119:12 #14 0x55bf56b6a592 in kj::runMainAndExit(kj::ProcessContext&, kj::Function)>&&, int, char**)::$_0::operator()() const /usr/src/kj/main.c++:228:5 #15 0x55bf56b6a592 in kj::Maybe kj::runCatchingExceptions)>&&, int, char**)::$_0>(kj::runMainAndExit(kj::ProcessContext&, kj::Function)>&&, int, char**)::$_0&&) /usr/src/kj/exception.h:371:5 #16 0x55bf56b69b5d in kj::runMainAndExit(kj::ProcessContext&, kj::Function)>&&, int, char**) /usr/src/kj/main.c++:228:5 #17 0x55bf5668be8f in main /usr/src/kj/test.c++:381:1 #18 0x7f83ec62a1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) #19 0x7f83ec62a28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) #20 0x55bf5630b374 in _start (/ci_container_base/ci/scratch/build-x86_64-pc-linux-gnu/src/ipc/libmultiprocess/test/mptest+0x77374) [11:46:33.109] SUMMARY: MemorySanitizer: use-of-uninitialized-value /msan/llvm-project/libcxx/src/future.cpp:154:16 in std::__1::promise::get_future() Exiting --- test/mp/test/test.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/mp/test/test.cpp b/test/mp/test/test.cpp index f621356d..1793b34c 100644 --- a/test/mp/test/test.cpp +++ b/test/mp/test/test.cpp @@ -268,12 +268,12 @@ KJ_TEST("Calling IPC method, disconnecting and blocking during the call") // ProxyServer objects associated with the connection. Having an in-progress // RPC call requires keeping the ProxyServer longer. + std::promise signal; TestSetup setup{/*client_owns_connection=*/false}; ProxyClient* foo = setup.client.get(); KJ_EXPECT(foo->add(1, 2) == 3); foo->initThreadMap(); - std::promise signal; setup.server->m_impl->m_fn = [&] { EventLoopRef loop{*setup.server->m_context.loop}; setup.client_disconnect(); @@ -289,6 +289,11 @@ KJ_TEST("Calling IPC method, disconnecting and blocking during the call") } KJ_EXPECT(disconnected); + // Now that the disconnect has been detected, set signal allowing the + // callFnAsync() IPC call to return. Since signalling may not wake up the + // thread right away, it is important for the signal variable to be declared + // *before* the TestSetup variable so is not destroyed while + // signal.get_future().get() is called. signal.set_value(); } From e09143d2ea2f072c267e1eacb7634cecd3f4798b Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Sat, 28 Jun 2025 09:24:14 -0400 Subject: [PATCH 09/11] proxy-types: fix UndefinedBehaviorSanitizer: null-pointer-use Fix UndefinedBehaviorSanitizer: null-pointer-use error binding a null pointer to an unused reference variable. This error should not cause problems in practice because the reference is not used, but is technically undefined behavior. Issue was reported: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3012101603 - https://github.com/Sjors/bitcoin/actions/runs/15921104847/job/44907875067?pr=90 - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3015153067 - https://github.com/Sjors/bitcoin/actions/runs/15942739184/job/44973004692?pr=90 and fixed: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3015255113 Error looks like: [ TEST ] test.cpp:197: Call IPC method after client connection is closed /home/runner/work/_temp/src/ipc/libmultiprocess/include/mp/proxy-types.h:612:40: runtime error: reference binding to null pointer of type 'Connection' #0 0x5647ad32fc50 in void mp::clientInvoke, capnp::Request (mp::test::messages::FooInterface::Client::*)(kj::Maybe), mp::ClientParam, int>, mp::ClientParam, int>, mp::ClientParam, int&>>(mp::ProxyClient&, capnp::Request (mp::test::messages::FooInterface::Client::* const&)(kj::Maybe), mp::ClientParam, int>&&, mp::ClientParam, int>&&, mp::ClientParam, int&>&&) /home/runner/work/_temp/build-asan/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/include/mp/proxy-types.h:612:25 #1 0x5647ad32b62a in mp::ProxyClient::add(int, int) /home/runner/work/_temp/build-asan/src/ipc/libmultiprocess/test/mp/test/foo.capnp.proxy-client.c++:20:5 #2 0x5647ad2eb9ef in mp::test::TestCase197::run() /home/runner/work/_temp/build-asan/src/ipc/libmultiprocess/test/./ipc/libmultiprocess/test/mp/test/test.cpp:206:14 #3 0x7f6aaf7100e1 (/lib/x86_64-linux-gnu/libkj-test-1.0.1.so+0x50e1) (BuildId: 2ff7f524274168e50347a2d6dd423f273ae8d268) #4 0x7f6aaf710657 (/lib/x86_64-linux-gnu/libkj-test-1.0.1.so+0x5657) (BuildId: 2ff7f524274168e50347a2d6dd423f273ae8d268) #5 0x7f6aaf49f37f in kj::MainBuilder::MainImpl::operator()(kj::StringPtr, kj::ArrayPtr) (/lib/x86_64-linux-gnu/libkj-1.0.1.so+0x5537f) (BuildId: 4b52c0e2756bcb53e58e705fcb10bab8f63f24fd) #6 0x7f6aaf49a499 in kj::runMainAndExit(kj::ProcessContext&, kj::Function)>&&, int, char**) (/lib/x86_64-linux-gnu/libkj-1.0.1.so+0x50499) (BuildId: 4b52c0e2756bcb53e58e705fcb10bab8f63f24fd) #7 0x7f6aaf70efcb in main (/lib/x86_64-linux-gnu/libkj-test-1.0.1.so+0x3fcb) (BuildId: 2ff7f524274168e50347a2d6dd423f273ae8d268) #8 0x7f6aaeeb81c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) #9 0x7f6aaeeb828a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 42c84c92e6f98126b3e2230ebfdead22c235b667) #10 0x5647ad1fd614 in _start (/home/runner/work/_temp/build-asan/src/ipc/libmultiprocess/test/mptest+0x11d614) (BuildId: a172f78701ced3f93ad52c3562f181811a1c98e8) SUMMARY: UndefinedBehaviorSanitizer: null-pointer-use /home/runner/work/_temp/src/ipc/libmultiprocess/include/mp/proxy-types.h:612:40 --- include/mp/proxy-types.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/include/mp/proxy-types.h b/include/mp/proxy-types.h index 597bdfda..de96d134 100644 --- a/include/mp/proxy-types.h +++ b/include/mp/proxy-types.h @@ -609,42 +609,44 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel << "{" << g_thread_context.thread_name << "} IPC client first request from current thread, constructing waiter"; } - ClientInvokeContext invoke_context{*proxy_client.m_context.connection, g_thread_context}; + ThreadContext& thread_context{g_thread_context}; + std::optional invoke_context; // Must outlive waiter->wait() call below std::exception_ptr exception; std::string kj_exception; bool done = false; const char* disconnected = nullptr; proxy_client.m_context.loop->sync([&]() { if (!proxy_client.m_context.connection) { - const std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); + const std::unique_lock lock(thread_context.waiter->m_mutex); done = true; disconnected = "IPC client method called after disconnect."; - invoke_context.thread_context.waiter->m_cv.notify_all(); + thread_context.waiter->m_cv.notify_all(); return; } auto request = (proxy_client.m_client.*get_request)(nullptr); using Request = CapRequestTraits; using FieldList = typename ProxyClientMethodTraits::Fields; - IterateFields().handleChain(invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...); + invoke_context.emplace(*proxy_client.m_context.connection, thread_context); + IterateFields().handleChain(*invoke_context, request, FieldList(), typename FieldObjs::BuildParams{&fields}...); proxy_client.m_context.loop->logPlain() - << "{" << invoke_context.thread_context.thread_name << "} IPC client send " + << "{" << thread_context.thread_name << "} IPC client send " << TypeName() << " " << LogEscape(request.toString()); proxy_client.m_context.loop->m_task_set->add(request.send().then( [&](::capnp::Response&& response) { proxy_client.m_context.loop->logPlain() - << "{" << invoke_context.thread_context.thread_name << "} IPC client recv " + << "{" << thread_context.thread_name << "} IPC client recv " << TypeName() << " " << LogEscape(response.toString()); try { IterateFields().handleChain( - invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...); + *invoke_context, response, FieldList(), typename FieldObjs::ReadResults{&fields}...); } catch (...) { exception = std::current_exception(); } - const std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); + const std::unique_lock lock(thread_context.waiter->m_mutex); done = true; - invoke_context.thread_context.waiter->m_cv.notify_all(); + thread_context.waiter->m_cv.notify_all(); }, [&](const ::kj::Exception& e) { if (e.getType() == ::kj::Exception::Type::DISCONNECTED) { @@ -652,16 +654,16 @@ void clientInvoke(ProxyClient& proxy_client, const GetRequest& get_request, Fiel } else { kj_exception = kj::str("kj::Exception: ", e).cStr(); proxy_client.m_context.loop->logPlain() - << "{" << invoke_context.thread_context.thread_name << "} IPC client exception " << kj_exception; + << "{" << thread_context.thread_name << "} IPC client exception " << kj_exception; } - const std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); + const std::unique_lock lock(thread_context.waiter->m_mutex); done = true; - invoke_context.thread_context.waiter->m_cv.notify_all(); + thread_context.waiter->m_cv.notify_all(); })); }); - std::unique_lock lock(invoke_context.thread_context.waiter->m_mutex); - invoke_context.thread_context.waiter->wait(lock, [&done]() { return done; }); + std::unique_lock lock(thread_context.waiter->m_mutex); + thread_context.waiter->wait(lock, [&done]() { return done; }); if (exception) std::rethrow_exception(exception); if (!kj_exception.empty()) proxy_client.m_context.loop->raise() << kj_exception; if (disconnected) proxy_client.m_context.loop->raise() << disconnected; From c6f7fdf1735015d069b173dd5d511ab148b678e7 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Mon, 30 Jun 2025 11:59:22 -0400 Subject: [PATCH 10/11] type-context: revert client disconnect workaround This reverts commit 196e6fcdbc1f4d7d03f4fd05b9221917c44f5807 from https://github.com/bitcoin-core/libmultiprocess/pull/118 which added a workaround which is no longer needed after 315ff537fb6550468c3148e795dffb78313bd11f from https://github.com/bitcoin-core/libmultiprocess/pull/160 When the workaround was introduced it prevented segfaults that happened when an IPC client disconnected during a long-running asynchronous call. But it prevented them by leaking server objects, and these leaks now sometimes cause the new "disconnecting and blocking" unit test introduced in #160 to hang. Since the workaround is no longer necessary, revert it now to fix the test hangs. The problem with test hangs was reported: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3018191015 - https://github.com/Sjors/bitcoin/actions/runs/15966265407/job/45027248310?pr=90 - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3019582045 - https://cirrus-ci.com/task/4999408900636672 And the fix was posted: - https://github.com/Sjors/bitcoin/pull/90#issuecomment-3019738107 --- include/mp/type-context.h | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/include/mp/type-context.h b/include/mp/type-context.h index 952734f3..894daadb 100644 --- a/include/mp/type-context.h +++ b/include/mp/type-context.h @@ -69,7 +69,6 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& const auto& params = call_context.getParams(); Context::Reader context_arg = Accessor::get(params); ServerContext server_context{server, call_context, req}; - bool disconnected{false}; { // Before invoking the function, store a reference to the // callbackThread provided by the client in the @@ -101,7 +100,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& // recursive call (IPC call calling back to the caller which // makes another IPC call), so avoid modifying the map. const bool erase_thread{inserted}; - KJ_DEFER({ + KJ_DEFER(if (erase_thread) { std::unique_lock lock(thread_context.waiter->m_mutex); // Call erase here with a Connection* argument instead // of an iterator argument, because the `request_thread` @@ -112,24 +111,10 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn& // erases the thread from the map, and also because the // ProxyServer destructor calls // request_threads.clear(). - if (erase_thread) { - disconnected = !request_threads.erase(server.m_context.connection); - } else { - disconnected = !request_threads.count(server.m_context.connection); - } + request_threads.erase(server.m_context.connection); }); fn.invoke(server_context, args...); } - if (disconnected) { - // If disconnected is true, the Connection object was - // destroyed during the method call. Deal with this by - // returning without ever fulfilling the promise, which will - // cause the ProxyServer object to leak. This is not ideal, - // but fixing the leak will require nontrivial code changes - // because there is a lot of code assuming ProxyServer - // objects are destroyed before Connection objects. - return; - } KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() { server.m_context.loop->sync([&] { auto fulfiller_dispose = kj::mv(fulfiller); From 6f340a583f2b29b32866c289d5f4d7662b4fe90e Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Mon, 30 Jun 2025 18:40:50 -0400 Subject: [PATCH 11/11] doc: fix DrahtBot LLM Linter error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Possible typos and grammar issues: - “try unregister this callback” -> “try to unregister this callback” [missing “to” for infinitive] https://github.com/bitcoin-core/libmultiprocess/pull/186#issuecomment-3011157858 --- src/mp/proxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mp/proxy.cpp b/src/mp/proxy.cpp index 55ff05ea..c9fecf5c 100644 --- a/src/mp/proxy.cpp +++ b/src/mp/proxy.cpp @@ -321,7 +321,7 @@ std::tuple SetThread(ConnThreads& threads, std::mutex& mutex, // Connection is being destroyed before thread client is, so reset // thread client m_disconnect_cb member so thread client destructor does not - // try unregister this callback after connection is destroyed. + // try to unregister this callback after connection is destroyed. // Remove connection pointer about to be destroyed from the map const std::unique_lock lock(mutex); thread->second.m_disconnect_cb.reset();