Skip to content

Data race detected by ThreadSanitizer when io_context::stop is called from another thread #1757

Description

@eao197

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 🥹

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions