Code to reproduce:
#include <thread>
#include <future>
#include <asio.hpp>
int main()
{
std::promise< asio::io_context * > promise;
std::thread child_thread{
[&promise]() {
asio::io_context ctx;
asio::post( ctx, [&promise, &ctx]() {
promise.set_value( &ctx );
} );
auto work = asio::make_work_guard( ctx );
ctx.run();
}
};
auto * ctx_ptr = promise.get_future().get();
asio::post( *ctx_ptr, [ctx_ptr]() { ctx_ptr->stop(); } );
child_thread.join();
}
The report from ThreadSanitizer (KDE Neon Linux, GCC 13.3, Asio 1-38-2):
WARNING: ThreadSanitizer: data race (pid=133604)
Write of size 8 at 0x724400000078 by thread T1:
#0 pthread_cond_destroy ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1303 (libtsan.so.2+0x5a1ab) (B
uildId: 2a13a7710e361d06f7babbea53065ca2be93f738)
#1 asio::detail::posix_event::~posix_event() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps
/asio-src/asio/include/asio/detail/posix_event.hpp:43 (_unit.asio.deadlock_on_exit+0xb520) (BuildId: f995a9aaf35e6819612
2e7f4e475cc4bc0579cb3)
#2 asio::detail::conditionally_enabled_event::~conditionally_enabled_event() /home/eao197/sandboxes/so5extra.github/
dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/conditionally_enabled_event.hpp:45 (_unit.asio.deadlo
ck_on_exit+0xb520)
#3 asio::detail::scheduler::~scheduler() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asi
o-src/asio/include/asio/detail/impl/scheduler.ipp:167 (_unit.asio.deadlock_on_exit+0xb520)
#4 void std::_Destroy<asio::detail::scheduler>(asio::detail::scheduler*) /usr/include/c++/13/bits/stl_construct.h:15
1 (_unit.asio.deadlock_on_exit+0xa6b7) (BuildId: f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#5 void std::allocator_traits<asio::execution_context::allocator<asio::detail::scheduler> >::_S_destroy<asio::execut
ion_context::allocator<asio::detail::scheduler>, asio::detail::scheduler>(asio::execution_context::allocator<asio::detai
l::scheduler>&, asio::detail::scheduler*, ...) /usr/include/c++/13/bits/alloc_traits.h:293 (_unit.asio.deadlock_on_exit+
0xa6b7)
#6 void std::allocator_traits<asio::execution_context::allocator<asio::detail::scheduler> >::destroy<asio::detail::s
cheduler>(asio::execution_context::allocator<asio::detail::scheduler>&, asio::detail::scheduler*) /usr/include/c++/13/bi
ts/alloc_traits.h:393 (_unit.asio.deadlock_on_exit+0xa6b7)
#7 void asio::detail::deallocate_object<asio::execution_context::allocator<void>, asio::detail::scheduler>(asio::exe
cution_context::allocator<void> const&, asio::detail::scheduler*) /home/eao197/sandboxes/so5extra.github/dev/cmake_build
_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/memory.hpp:85 (_unit.asio.deadlock_on_exit+0xa6b7)
#8 void asio::detail::service_registry::destroy_allocated<asio::detail::scheduler>(asio::execution_context::service*
) /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/impl/service
_registry.hpp:109 (_unit.asio.deadlock_on_exit+0xa6b7)
#9 asio::detail::service_registry::destroy_services() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cp
p17/_deps/asio-src/asio/include/asio/detail/impl/service_registry.ipp:54 (_unit.asio.deadlock_on_exit+0x8b57) (BuildId:
f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#10 asio::execution_context::destroy() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-
src/asio/include/asio/impl/execution_context.ipp:75 (_unit.asio.deadlock_on_exit+0x8b57)
#11 asio::execution_context::~execution_context() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/
_deps/asio-src/asio/include/asio/impl/execution_context.ipp:64 (_unit.asio.deadlock_on_exit+0x8b57)
#12 asio::io_context::~io_context() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src
/asio/include/asio/impl/io_context.ipp:58 (_unit.asio.deadlock_on_exit+0x8b57)
#13 operator() /home/eao197/sandboxes/so5extra.github/dev/test/asio/deadlock_on_exit/main.cpp:20 (_unit.asio.deadloc
k_on_exit+0x8b57)
#14 __invoke_impl<void, main()::<lambda()> > /usr/include/c++/13/bits/invoke.h:61 (_unit.asio.deadlock_on_exit+0x8b5
7)
#15 __invoke<main()::<lambda()> > /usr/include/c++/13/bits/invoke.h:96 (_unit.asio.deadlock_on_exit+0x8b57)
#16 _M_invoke<0> /usr/include/c++/13/bits/std_thread.h:292 (_unit.asio.deadlock_on_exit+0x8b57)
#17 operator() /usr/include/c++/13/bits/std_thread.h:299 (_unit.asio.deadlock_on_exit+0x8b57)
#18 _M_run /usr/include/c++/13/bits/std_thread.h:244 (_unit.asio.deadlock_on_exit+0x8b57)
#19 <null> <null> (libstdc++.so.6+0xecdb3) (BuildId: 753c6c8608b61d4e67be8f0c890e03e0aa046b8b)
Previous read of size 8 at 0x724400000078 by main thread:
#0 pthread_cond_signal ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1289 (libtsan.so.2+0x59768) (Bu
ildId: 2a13a7710e361d06f7babbea53065ca2be93f738)
#1 bool asio::detail::posix_event::maybe_unlock_and_signal_one<asio::detail::conditionally_enabled_mutex<asio::detai
l::posix_mutex>::scoped_lock>(asio::detail::conditionally_enabled_mutex<asio::detail::posix_mutex>::scoped_lock&) /home/
eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/posix_event.hpp:96 (
_unit.asio.deadlock_on_exit+0xe5a9) (BuildId: f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#2 bool asio::detail::posix_event::maybe_unlock_and_signal_one<asio::detail::conditionally_enabled_mutex<asio::detai
l::posix_mutex>::scoped_lock>(asio::detail::conditionally_enabled_mutex<asio::detail::posix_mutex>::scoped_lock&) /home/
eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/posix_event.hpp:89 (
_unit.asio.deadlock_on_exit+0xe5a9)
#3 asio::detail::conditionally_enabled_event::maybe_unlock_and_signal_one(asio::detail::conditionally_enabled_mutex<
asio::detail::posix_mutex>::scoped_lock&) /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-s
rc/asio/include/asio/detail/conditionally_enabled_event.hpp:82 (_unit.asio.deadlock_on_exit+0xe5a9)
#4 asio::detail::scheduler::wake_one_thread_and_unlock(asio::detail::conditionally_enabled_mutex<asio::detail::posix
_mutex>::scoped_lock&) /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asi
o/detail/impl/scheduler.ipp:676 (_unit.asio.deadlock_on_exit+0xe5a9)
#5 asio::detail::scheduler::post_immediate_completion(asio::detail::scheduler_operation*, bool) /home/eao197/sandbox
es/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/impl/scheduler.ipp:376 (_unit.asio
.deadlock_on_exit+0xe5a9)
#6 execute<asio::detail::binder0<main()::<lambda()> > > /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_
cpp17/_deps/asio-src/asio/include/asio/impl/io_context.hpp:240 (_unit.asio.deadlock_on_exit+0x6b8e) (BuildId: f995a9aaf3
5e68196122e7f4e475cc4bc0579cb3)
#7 operator()<main()::<lambda()>, asio::detail::empty_work_function> /home/eao197/sandboxes/so5extra.github/dev/cmak
e_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/initiate_post.hpp:117 (_unit.asio.deadlock_on_exit+0x6b8e)
#8 initiate<asio::detail::initiate_post_with_executor<asio::io_context::basic_executor_type<std::allocator<void>, 0>
>, main()::<lambda()>, asio::detail::empty_work_function> /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_c
pp17/_deps/asio-src/asio/include/asio/async_result.hpp:329 (_unit.asio.deadlock_on_exit+0x6b8e)
#9 async_initiate<main()::<lambda()>, void(), asio::detail::initiate_post_with_executor<asio::io_context::basic_exec
utor_type<std::allocator<void>, 0> >, asio::detail::empty_work_function> /home/eao197/sandboxes/so5extra.github/dev/cmak
e_build_tsan_cpp17/_deps/asio-src/asio/include/asio/async_result.hpp:682 (_unit.asio.deadlock_on_exit+0x6b8e)
#10 post<asio::io_context, main()::<lambda()> > /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_d
eps/asio-src/asio/include/asio/post.hpp:209 (_unit.asio.deadlock_on_exit+0x6b8e)
#11 main /home/eao197/sandboxes/so5extra.github/dev/test/asio/deadlock_on_exit/main.cpp:24 (_unit.asio.deadlock_on_e
xit+0x6b8e)
Location is heap block of size 287 at 0x724400000000 allocated by thread T1:
#0 operator new(unsigned long) ../../../../src/libsanitizer/tsan/tsan_new_delete.cpp:64 (libtsan.so.2+0xa6fae) (Buil
dId: 2a13a7710e361d06f7babbea53065ca2be93f738)
#1 std::__new_allocator<unsigned char>::allocate(unsigned long, void const*) /usr/include/c++/13/bits/new_allocator.
h:151 (_unit.asio.deadlock_on_exit+0x9e10) (BuildId: f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#2 std::allocator_traits<std::allocator<unsigned char> >::allocate(std::allocator<unsigned char>&, unsigned long) /u
sr/include/c++/13/bits/alloc_traits.h:482 (_unit.asio.deadlock_on_exit+0x9e10)
#3 asio::execution_context::allocator_impl<std::allocator<void> >::allocate(unsigned long, unsigned long) /home/eao1
97/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/impl/execution_context.hpp:62 (
_unit.asio.deadlock_on_exit+0x9e10)
#4 asio::execution_context::allocator<asio::detail::scheduler>::allocate(unsigned long) const /home/eao197/sandboxes
/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/execution_context.hpp:404 (_unit.asio.deadl
ock_on_exit+0x12f1c) (BuildId: f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#5 std::allocator_traits<asio::execution_context::allocator<asio::detail::scheduler> >::allocate(asio::execution_con
text::allocator<asio::detail::scheduler>&, unsigned long) /usr/include/c++/13/bits/alloc_traits.h:333 (_unit.asio.deadlo
ck_on_exit+0x12f1c)
#6 asio::detail::scheduler* asio::detail::allocate_object<asio::detail::scheduler, asio::execution_context::allocato
r<void>, asio::execution_context&, bool>(asio::execution_context::allocator<void> const&, asio::execution_context&, bool
&&) /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/memory.hpp
:65 (_unit.asio.deadlock_on_exit+0x12f1c)
#7 asio::execution_context::service* asio::detail::service_registry::create<asio::detail::scheduler, asio::execution
_context, bool>(asio::execution_context&, void*, bool&&) /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp
17/_deps/asio-src/asio/include/asio/detail/impl/service_registry.hpp:99 (_unit.asio.deadlock_on_exit+0x12f1c)
#8 asio::detail::scheduler& asio::detail::service_registry::make_service<asio::detail::scheduler, bool>(bool&&) /hom
e/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/detail/impl/service_regis
try.hpp:46 (_unit.asio.deadlock_on_exit+0x12f1c)
#9 asio::detail::scheduler& asio::make_service<asio::detail::scheduler, bool>(asio::execution_context&, bool&&) /hom
e/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asio/include/asio/impl/execution_context.hp
p:113 (_unit.asio.deadlock_on_exit+0x87e2) (BuildId: f995a9aaf35e68196122e7f4e475cc4bc0579cb3)
#10 asio::io_context::io_context() /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/
asio/include/asio/impl/io_context.ipp:39 (_unit.asio.deadlock_on_exit+0x87e2)
#11 operator() /home/eao197/sandboxes/so5extra.github/dev/test/asio/deadlock_on_exit/main.cpp:12 (_unit.asio.deadloc
k_on_exit+0x87e2)
#12 __invoke_impl<void, main()::<lambda()> > /usr/include/c++/13/bits/invoke.h:61 (_unit.asio.deadlock_on_exit+0x87e
2)
#13 __invoke<main()::<lambda()> > /usr/include/c++/13/bits/invoke.h:96 (_unit.asio.deadlock_on_exit+0x87e2)
#14 _M_invoke<0> /usr/include/c++/13/bits/std_thread.h:292 (_unit.asio.deadlock_on_exit+0x87e2)
#15 operator() /usr/include/c++/13/bits/std_thread.h:299 (_unit.asio.deadlock_on_exit+0x87e2)
#16 _M_run /usr/include/c++/13/bits/std_thread.h:244 (_unit.asio.deadlock_on_exit+0x87e2)
#17 <null> <null> (libstdc++.so.6+0xecdb3) (BuildId: 753c6c8608b61d4e67be8f0c890e03e0aa046b8b)
Thread T1 (tid=133606, running) created by main thread at:
#0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:1022 (libtsan.so.2+0x5ac1a) (BuildId
: 2a13a7710e361d06f7babbea53065ca2be93f738)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, voi
d (*)()) <null> (libstdc++.so.6+0xeceb0) (BuildId: 753c6c8608b61d4e67be8f0c890e03e0aa046b8b)
#2 __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58 (libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c240675
41ac5390e6f527fb5947bb)
SUMMARY: ThreadSanitizer: data race /home/eao197/sandboxes/so5extra.github/dev/cmake_build_tsan_cpp17/_deps/asio-src/asi
o/include/asio/detail/posix_event.hpp:43 in asio::detail::posix_event::~posix_event()
==================
ThreadSanitizer: reported 1 warnings
It seems that the reason in the call to asio::detail::posix_event::maybe_unlock_and_signal_one and this call first unlocks the lock and only then signals the cond_var.
This creates a possibility that another thread can obtain the lock between these two steps and destroy the io_context just before the cond_var will be signaled.
It seems that the right logic is implemented in unlock_and_signal_one_for_destruction, but this method isn't called at all. The conditionally_enabled_mutex::unlock_and_signal_one_for_destruction calls maybe_unlock_and_signal_one, but not unlock_and_signal_one_for_destruction 🙁
I've tried to call unlock_and_signal_one_for_destruction in the conditionally_enabled_mutex::unlock_and_signal_one_for_destruction, but it didn't help 🥹
Code to reproduce:
The report from ThreadSanitizer (KDE Neon Linux, GCC 13.3, Asio 1-38-2):
It seems that the reason in the call to asio::detail::posix_event::maybe_unlock_and_signal_one and this call first unlocks the
lockand only then signals the cond_var.This creates a possibility that another thread can obtain the lock between these two steps and destroy the
io_contextjust before the cond_var will be signaled.It seems that the right logic is implemented in unlock_and_signal_one_for_destruction, but this method isn't called at all. The conditionally_enabled_mutex::unlock_and_signal_one_for_destruction calls
maybe_unlock_and_signal_one, but notunlock_and_signal_one_for_destruction🙁I've tried to call
unlock_and_signal_one_for_destructionin theconditionally_enabled_mutex::unlock_and_signal_one_for_destruction, but it didn't help 🥹