NVIDIA/stdexec

Data race in spawn_future

Closed this issue · 0 comments

https://gist.github.com/ccotter/188d12913de85ae41d56c8f56f675262 is the Relacy output from the async_scope test in #1400. This Relacy test is the same as the "after spawn_future result discarded" test in test_type_async_scope.cpp. The Relacy trace is nice because it shows the entire history of atomic accesses and thread interleavings.

Running the test_type_async_scope.cpp test in a loop for 100,000 iterations allows TSAN to capture the race, and running the ASAN version with a "fuzzed" version of the pthread APIs sleeping random amounts of times also exposes the race. Both ASAN/TSAN outputs are in the gist as well.

One of the TSAN diagnostics is reproduced here:

==================
WARNING: ThreadSanitizer: heap-use-after-free (pid=11568)
  Atomic read of size 1 at 0x72440000e018 by thread T5:
    #0 pthread_mutex_lock /home/ccotter/git/llvm-project/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1386:3 (test.exec+0x9c4cd)
    #1 __gthread_mutex_lock(pthread_mutex_t*) /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/x86_64-redhat-linux/bits/gthr-default.h:749:12 (test.exec+0x2300e6)
    #2 std::mutex::lock() /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/std_mutex.h:100:17 (test.exec+0x2300e6)
    #3 std::unique_lock<std::mutex>::lock() /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/unique_lock.h:139:17 (test.exec+0x2300e6)
    #4 std::unique_lock<std::mutex>::unique_lock(std::mutex&) /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/unique_lock.h:69:2 (test.exec+0x2300e6)
    #5 exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>::__t::__dispatch_result_() /home/ccotter/git/relacy/stdexec/include/exec/async_scope.hpp:527:28 (test.exec+0x2300e6)
    #6 exec::_pool_::static_thread_pool_::operation<stdexec::__detail::__receiver<exec::__scope::__nest_rcvr<exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>>, stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>, stdexec::__muchar (*) [1]>>::__t::__t(exec::_pool_::static_thread_pool_&, exec::_pool_::remote_queue*, stdexec::__detail::__receiver<exec::__scope::__nest_rcvr<exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>>, stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>, stdexec::__muchar (*) [1]>::__t, unsigned long, exec::nodemask const&)::'lambda'(exec::_pool_::task_base*, unsigned int)::operator()(exec::_pool_::task_base*, unsigned int) const /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp (test.exec+0x22f3bc)
    #7 exec::_pool_::static_thread_pool_::operation<stdexec::__detail::__receiver<exec::__scope::__nest_rcvr<exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>>, stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>, stdexec::__muchar (*) [1]>>::__t::__t(exec::_pool_::static_thread_pool_&, exec::_pool_::remote_queue*, stdexec::__detail::__receiver<exec::__scope::__nest_rcvr<exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>>, stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>, stdexec::__muchar (*) [1]>::__t, unsigned long, exec::nodemask const&)::'lambda'(exec::_pool_::task_base*, unsigned int)::__invoke(exec::_pool_::task_base*, unsigned int) /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp:1025:27 (test.exec+0x22f3bc)
    #8 exec::_pool_::static_thread_pool_::run(unsigned int) /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp:711:9 (test.exec+0x1f6981)
    #9 exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()::operator()() const /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp:682:45 (test.exec+0x1f6981)
    #10 void std::__invoke_impl<void, exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>(std::__invoke_other, exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()&&) /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/invoke.h:61:14 (test.exec+0x1f6981)
    #11 std::__invoke_result<exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>::type std::__invoke<exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>(exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()&&) /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/invoke.h:96:14 (test.exec+0x1f6981)
    #12 void std::thread::_Invoker<std::tuple<exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>>::_M_invoke<0ul>(std::_Index_tuple<0ul>) /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/std_thread.h:258:13 (test.exec+0x1f6981)
    #13 std::thread::_Invoker<std::tuple<exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>>::operator()() /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/std_thread.h:265:11 (test.exec+0x1f6981)
    #14 std::thread::_State_impl<std::thread::_Invoker<std::tuple<exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy)::'lambda'()>>>::_M_run() /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/std_thread.h:210:13 (test.exec+0x1f6981)
    #15 execute_native_thread_routine thread48.o (test.exec+0x335d42)

  Previous write of size 8 at 0x72440000e018 by main thread:
    #0 operator delete(void*, unsigned long) /home/ccotter/git/llvm-project/compiler-rt/lib/tsan/rtl/tsan_new_delete.cpp:150:3 (test.exec+0x114e6e)
    #1 std::default_delete<exec::__scope::__future_state<exec::__scope::__nest_sender<stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>>::__t, stdexec::__env::env<>>>::operator()(exec::__scope::__future_state<exec::__scope::__nest_sender<stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>>::__t, stdexec::__env::env<>>*) const /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/unique_ptr.h:95:2 (test.exec+0x22c4e6)
    #2 std::unique_ptr<exec::__scope::__future_state<exec::__scope::__nest_sender<stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>>::__t, stdexec::__env::env<>>, std::default_delete<exec::__scope::__future_state<exec::__scope::__nest_sender<stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>>::__t, stdexec::__env::env<>>>>::~unique_ptr() /opt/rh/devtoolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/../../../../include/c++/12/bits/unique_ptr.h:396:4 (test.exec+0x22c4e6)
    #3 exec::__scope::__future<exec::__scope::__nest_sender<stdexec::__sexpr<stdexec::(anonymous namespace)::'lambda13'(){}, stdexec::(anonymous namespace)::__anon>>, stdexec::__env::env<>>::__t::~__t() /home/ccotter/git/relacy/stdexec/include/exec/async_scope.hpp:637:9 (test.exec+0x22c4e6)
    #4 (anonymous namespace)::____C_A_T_C_H____T_E_S_T____0() /home/ccotter/git/relacy/stdexec/test/exec/test_type_async_scope.cpp:92:7 (test.exec+0x22c4e6)
    #5 Catch::TestInvokerAsFunction::invoke() const /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:14317:9 (test.exec+0x147449)
    #6 Catch::TestCase::invoke() const /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:14156:15 (test.exec+0x13be7e)
    #7 Catch::RunContext::invokeActiveTestCase() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13016:27 (test.exec+0x13be7e)
    #8 Catch::RunContext::runCurrentTest(std::string&, std::string&) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:12989:17 (test.exec+0x138a87)
    #9 Catch::RunContext::runTest(Catch::TestCase const&) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:12750:13 (test.exec+0x13737a)
    #10 Catch::(anonymous namespace)::TestGroup::execute() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13343:45 (test.exec+0x141a5a)
    #11 Catch::Session::runInternal() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13549:39 (test.exec+0x141a5a)
    #12 Catch::Session::run() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13505:24 (test.exec+0x14021f)
    #13 int Catch::Session::run<char>(int, char const* const*) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13227:30 (test.exec+0x16bd4e)
    #14 main /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:17504:29 (test.exec+0x16bd4e)

  Thread T5 (tid=11584, running) created by main thread at:
    #0 pthread_create /home/ccotter/git/llvm-project/compiler-rt/lib/tsan/rtl/tsan_interceptors_posix.cpp:1031:3 (test.exec+0x9bab2)
    #1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State>>, void (*)()) <null> (test.exec+0x335feb)
    #2 exec::_pool_::static_thread_pool_::static_thread_pool_(unsigned int, exec::bwos_params, exec::numa_policy) /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp:682:20 (test.exec+0x1f42bb)
    #3 exec::static_thread_pool::static_thread_pool(unsigned int, exec::bwos_params, exec::numa_policy) /home/ccotter/git/relacy/stdexec/include/exec/static_thread_pool.hpp:1558:9 (test.exec+0x22adbb)
    #4 (anonymous namespace)::____C_A_T_C_H____T_E_S_T____0() /home/ccotter/git/relacy/stdexec/test/exec/test_type_async_scope.cpp:44:30 (test.exec+0x22adbb)
    #5 Catch::TestInvokerAsFunction::invoke() const /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:14317:9 (test.exec+0x147449)
    #6 Catch::TestCase::invoke() const /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:14156:15 (test.exec+0x13be7e)
    #7 Catch::RunContext::invokeActiveTestCase() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13016:27 (test.exec+0x13be7e)
    #8 Catch::RunContext::runCurrentTest(std::string&, std::string&) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:12989:17 (test.exec+0x138a87)
    #9 Catch::RunContext::runTest(Catch::TestCase const&) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:12750:13 (test.exec+0x13737a)
    #10 Catch::(anonymous namespace)::TestGroup::execute() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13343:45 (test.exec+0x141a5a)
    #11 Catch::Session::runInternal() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13549:39 (test.exec+0x141a5a)
    #12 Catch::Session::run() /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13505:24 (test.exec+0x14021f)
    #13 int Catch::Session::run<char>(int, char const* const*) /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:13227:30 (test.exec+0x16bd4e)
    #14 main /home/ccotter/git/relacy/stdexec/b/_deps/catch2-src/single_include/catch2/catch.hpp:17504:29 (test.exec+0x16bd4e)

SUMMARY: ThreadSanitizer: heap-use-after-free /home/ccotter/git/relacy/stdexec/include/exec/async_scope.hpp:527:28 in exec::__scope::__future_rcvr<stdexec::completion_signatures<stdexec::__rcvrs::set_value_t (), stdexec::__rcvrs::set_stopped_t (), stdexec::__rcvrs::set_error_t (std::__exception_ptr::exception_ptr)>, stdexec::__env::env<>>::__t::__dispatch_result_()