Thread sanitizer reports data race when using std::packaged_task/std::exception_ptr

心不动则不痛 提交于 2021-02-09 11:00:45

问题


I am experiencing some issues with thread sanitizer (TSan) complaining about a data race in some production code where std::packaged_task are handed over to a dispatcher thread by wrapping them in a std::function. For this question I have simplified what it does in production, while triggering TSan. The implementation is similar to the answer given by Anthony Williams in this question (at least that is my understanding): Non-obvious lifetime issue with std::promise and std::future.

Note that the same data race is reported if using std::promise. It will also complain if a simple int is thrown/catched. No race is found if the exception is not accessed in the catch clause. No race is found if I omit using the wrapping lambda and simply std::move the std::packaged_task to the thread.

#include <future>
#include <thread>
#include <stdexcept>
#include <string>

void throw_exception_func()
{
  throw std::runtime_error("test_exception");
}

int main()
{

  for(int i = 0; i<1000; ++i) {
   std::packaged_task<void()>  packaged_task(throw_exception_func);
   std::future<void>           task_future      = packaged_task.get_future();
   const std::function<void()> wrapper_func     = [&]{
    std::packaged_task<void()> pt = std::packaged_task<void()>(std::move(packaged_task));
    pt();
   };

   std::thread                     task_thread(wrapper_func);
   bool                            expected_exception = false;
   try {
     task_future.get();
   } catch (const std::runtime_error& e) {
     expected_exception = std::string(e.what()).substr(0, 4) == "test"; // Do something with the exception.
   }

   task_thread.join();
   if(!expected_exception) {
     throw std::runtime_error("Did not get expected exception");
   };

  }

  return 0;
}

TSan output when compiled with clang++-3.6 test_packaged_task.cpp -g3 -std=c++14 -pthread -fsanitize=thread -D_GLIBCXX_DEBUG:

    SUMMARY: ThreadSanitizer: data race ??:0 operator delete(void*)
    ==================
    ==================
    WARNING: ThreadSanitizer: data race (pid=70669)
      Write of size 8 at 0x7d2400004d00 by thread T3:
        #0 free <null> (a.out+0x000000463eab)
        #1 std::__exception_ptr::exception_ptr::exception_ptr(std::__exception_ptr::exception_ptr const&) <null> (libstdc++.so.6+0x00000008d4c8)
        #2 ~_Result /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:611 (a.out+0x0000004e08f7)
        #3 std::__future_base::_Result<void>::_M_destroy() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:616 (a.out+0x0000004e084c)
        #4 std::__future_base::_Result_base::_Deleter::operator()(std::__future_base::_Result_base*) const /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:198 (a.out+0x0000004d3f3a)
        #5 ~unique_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/unique_ptr.h:236 (a.out+0x0000004d3cff)
        #6 ~_State_baseV2 /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:313 (a.out+0x0000004d95c3)
        #7 ~_Task_state_base /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:564 (a.out+0x0000004d941f)
        #8 ~_Task_state /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:567 (a.out+0x0000004d921a)
        #9 void __gnu_cxx::new_allocator<int>::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/ext/new_allocator.h:124 (a.out+0x0000004d91b4)
        #10 void std::allocator_traits<std::allocator<int> >::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::allocator<int>&, std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/alloc_traits.h:542 (a.out+0x0000004d9140)
        #11 std::_Sp_counted_ptr_inplace<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:531 (a.out+0x0000004d8d75)
        #12 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:150 (a.out+0x0000004ce7fd)
        #13 ~__shared_count /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:659 (a.out+0x0000004ce770)
        #14 ~__shared_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr_base.h:925 (a.out+0x0000004d3de9)
        #15 ~shared_ptr /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/bits/shared_ptr.h:93 (a.out+0x0000004d3d93)
        #16 ~packaged_task /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1504 (a.out+0x0000004cbd90)
        #17 operator() /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:20 (a.out+0x0000004cac4b)
        #18 void std::call_once<void (std::__future_base::_State_baseV2::*)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*>(std::once_flag&, void (std::__future_base::_State_baseV2::*&&)(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*, bool*), std::__future_base::_State_baseV2*&&, std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>*&&, bool*&&) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/mutex:738 (a.out+0x0000004dcdeb)
        #19 std::__future_base::_State_baseV2::_M_set_result(std::function<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> ()>, bool) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:386 (a.out+0x0000004dfee9)
        #20 std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1403 (a.out+0x0000004d9d54)
        #21 std::packaged_task<void ()>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:1547 (a.out+0x0000004d2674)
        #22 operator() /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:19 (a.out+0x0000004cac3d)
        #23 std::_Function_handler<void (), main::$_0>::_M_invoke(std::_Any_data const&) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1871 (a.out+0x0000004ca748)
        #24 std::function<void ()>::operator()() const /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:2267 (a.out+0x0000004d14b0)
        #25 void std::_Bind_simple<std::function<void ()> ()>::_M_invoke<>(std::_Index_tuple<>) /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1530 (a.out+0x0000004d1370)
        #26 std::_Bind_simple<std::function<void ()> ()>::operator()() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/functional:1520 (a.out+0x0000004d1310)
        #27 std::thread::_Impl<std::_Bind_simple<std::function<void ()> ()> >::_M_run() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/thread:115 (a.out+0x0000004d12b9)
        #28 std::this_thread::__sleep_for(std::chrono::duration<long, std::ratio<1l, 1l> >, std::chrono::duration<long, std::ratio<1l, 1000000000l> >) <null> (libstdc++.so.6+0x0000000b8c6f)

      Previous read of size 8 at 0x7d2400004d00 by main thread:
        #0 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:27 (a.out+0x0000004c9e10)
        #1 std::future<void>::get() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:846 (a.out+0x0000004cb6b5)
        #2 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:25 (a.out+0x0000004c9c9c)
        #3 std::future<void>::get() /usr/bin/../lib/gcc/x86_64-linux-gnu/5.3.1/../../../../include/c++/5.3.1/future:846 (a.out+0x0000004cb6b5)
        #4 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build_clang/test_packaged_task.cpp:25 (a.out+0x0000004c9c9c)
    ... repeated....

Compiling with GCC 5.2.1 and GCC 5.3.1 g++ test_packaged_task.cpp -g3 -std=c++14 -pthread -fsanitize=thread -D_GLIBCXX_DEBUGalso gives races, but the TSan output is slightly different:

==================
WARNING: ThreadSanitizer: data race (pid=75845)
  Write of size 8 at 0x7d2400009b30 by thread T5:
    #0 free <null> (libtsan.so.0+0x000000025819)
    #1 <null> <null> (libstdc++.so.6+0x00000008d4c8)
    #2 std::__future_base::_Result<void>::~_Result() /usr/include/c++/5/future:611 (a.out+0x00000040e159)
    #3 std::__future_base::_Result<void>::_M_destroy() /usr/include/c++/5/future:616 (a.out+0x0000004058f2)
    #4 std::__future_base::_Result_base::_Deleter::operator()(std::__future_base::_Result_base*) const /usr/include/c++/5/future:198 (a.out+0x000000404d2d)
    #5 std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter>::~unique_ptr() /usr/include/c++/5/bits/unique_ptr.h:236 (a.out+0x000000406042)
    #6 std::__future_base::_State_baseV2::~_State_baseV2() /usr/include/c++/5/future:313 (a.out+0x00000040daa1)
    #7 std::__future_base::_Task_state_base<void ()>::~_Task_state_base() /usr/include/c++/5/future:1360 (a.out+0x00000040db55)
    #8 std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>::~_Task_state() /usr/include/c++/5/future:1387 (a.out+0x00000040dfb5)
    #9 void __gnu_cxx::new_allocator<int>::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/include/c++/5/ext/new_allocator.h:124 (a.out+0x00000040eced)
    #10 void std::allocator_traits<std::allocator<int> >::destroy<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()> >(std::allocator<int>&, std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>*) /usr/include/c++/5/bits/alloc_traits.h:542 (a.out+0x00000040e9dd)
    #11 std::_Sp_counted_ptr_inplace<std::__future_base::_Task_state<void (*)(), std::allocator<int>, void ()>, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/include/c++/5/bits/shared_ptr_base.h:531 (a.out+0x00000040e464)
    #12 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/5/bits/shared_ptr_base.h:150 (a.out+0x00000040848a)
    #13 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/5/bits/shared_ptr_base.h:659 (a.out+0x000000405f1c)
    #14 std::__shared_ptr<std::__future_base::_Task_state_base<void ()>, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/5/bits/shared_ptr_base.h:925 (a.out+0x0000004078cd)
    #15 std::shared_ptr<std::__future_base::_Task_state_base<void ()> >::~shared_ptr() /usr/include/c++/5/bits/shared_ptr.h:93 (a.out+0x0000004078f9)
    #16 std::packaged_task<void ()>::~packaged_task() /usr/include/c++/5/future:1504 (a.out+0x0000004079bb)
    #17 main::{lambda()#1}::operator()() const <null> (a.out+0x000000403814)
    #18 _M_invoke /usr/include/c++/5/functional:1871 (a.out+0x000000403cda)
    #19 std::function<void ()>::operator()() const /usr/include/c++/5/functional:2267 (a.out+0x00000040f630)
    #20 void std::_Bind_simple<std::function<void ()> ()>::_M_invoke<>(std::_Index_tuple<>) /usr/include/c++/5/functional:1531 (a.out+0x00000040f254)
    #21 std::_Bind_simple<std::function<void ()> ()>::operator()() /usr/include/c++/5/functional:1520 (a.out+0x00000040ec1e)
    #22 std::thread::_Impl<std::_Bind_simple<std::function<void ()> ()> >::_M_run() /usr/include/c++/5/thread:115 (a.out+0x00000040e8e8)
    #23 <null> <null> (libstdc++.so.6+0x0000000b8c6f)

  Previous read of size 8 at 0x7d2400009b30 by main thread:
    #0 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build/test_packaged_task.cpp:27 (a.out+0x0000004039f8)

  Thread T5 (tid=76123, running) created by main thread at:
    #0 pthread_create <null> (libtsan.so.0+0x000000027577)
    #1 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) <null> (libstdc++.so.6+0x0000000b8db2)
    #2 main /home/chop/tardis_dev~chop_linux1606/tardis_dev/build/test_packaged_task.cpp:22 (a.out+0x0000004038f5)

SUMMARY: ThreadSanitizer: data race ??:0 __interceptor_free
==================

Can anyone help ? Is this a false positive by TSan ?

来源:https://stackoverflow.com/questions/37587096/thread-sanitizer-reports-data-race-when-using-stdpackaged-task-stdexception

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!