[llvm-bugs] [Bug 38682] New: Race condition in __make_async_assoc_state

via llvm-bugs llvm-bugs at lists.llvm.org
Thu Aug 23 08:06:33 PDT 2018


https://bugs.llvm.org/show_bug.cgi?id=38682

            Bug ID: 38682
           Summary: Race condition in __make_async_assoc_state
           Product: libc++
           Version: unspecified
          Hardware: PC
                OS: All
            Status: NEW
          Severity: enhancement
          Priority: P
         Component: All Bugs
          Assignee: unassignedclangbugs at nondot.org
          Reporter: ldionne at apple.com
                CC: llvm-bugs at lists.llvm.org, mclow.lists at gmail.com

The following program exhibits a race condition, which can be seen by running
it under Tsan:

------------------------------------------------------------------------------
#include <cassert>
#include <future>
#include <numeric>
#include <vector>


static int worker(std::vector<int> const& data) {
  return std::accumulate(data.begin(), data.end(), 0);
}

int main() {
  std::vector<int> const v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  for (int i = 0; i != 20; ++i) {
    std::future<int> fut = std::async(std::launch::async, worker, v);
    std::future<int> fut2 = std::async(std::launch::async, worker, v);
    int answer = fut.get();
    int answer2 = fut2.get();
    assert(answer == answer2);
  }
}
------------------------------------------------------------------------------

$ clang++ -std=c++11 -fsanitize=thread main.cpp -o main.exe && ./main.exe

------------------------------------------------------------------------------
WARNING: ThreadSanitizer: data race (pid=97622)
  Write of size 4 at 0x7b2c00000088 by thread T1 (mutexes: write M14):
    #0 void std::__1::__assoc_state<int>::set_value<int>(int&&) <null>:1062560
(main.exe:x86_64+0x10000556b)
    #1 std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::__execute()
<null>:1062560 (main.exe:x86_64+0x1000047de)
    #2 void*
std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct,
std::__1::default_delete<std::__1::__thread_struct> >, void
(std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::*)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*> >(void*) <null>:1062560
(main.exe:x86_64+0x100006b82)

  Previous read of size 4 at 0x7b2c00000088 by main thread:
    #0 std::__1::future<int>::future(std::__1::__assoc_state<int>*)
<null>:1062560 (main.exe:x86_64+0x1000071f1)
    #1 std::__1::future<int>::future(std::__1::__assoc_state<int>*)
<null>:1062560 (main.exe:x86_64+0x1000043c8)
    #2 std::__1::future<int> std::__1::__make_async_assoc_state<int,
std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int> >
const&), std::__1::vector<int, std::__1::allocator<int> > >
>(std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int>
> const&), std::__1::vector<int, std::__1::allocator<int> > >&&) <null>:1062560
(main.exe:x86_64+0x100003a13)
    #3 std::__1::future<std::__1::__invoke_of<std::__1::decay<int
(&)(std::__1::vector<int, std::__1::allocator<int> > const&)>::type,
std::__1::decay<std::__1::vector<int, std::__1::allocator<int> >
const&>::type>::type> std::__1::async<int (&)(std::__1::vector<int,
std::__1::allocator<int> > const&), std::__1::vector<int,
std::__1::allocator<int> > const&>(std::__1::launch, int
(&&&)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > const&&&) <null>:1062560
(main.exe:x86_64+0x10000196a)
    #4 main <null>:1062560 (main.exe:x86_64+0x10000104c)

  Location is heap block of size 176 at 0x7b2c00000000 allocated by main
thread:
    #0 operator new(unsigned long) <null>:1062592
(libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x6e733)
    #1 std::__1::future<int> std::__1::__make_async_assoc_state<int,
std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int> >
const&), std::__1::vector<int, std::__1::allocator<int> > >
>(std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int>
> const&), std::__1::vector<int, std::__1::allocator<int> > >&&) <null>:1062592
(main.exe:x86_64+0x1000036c8)
    #2 std::__1::future<std::__1::__invoke_of<std::__1::decay<int
(&)(std::__1::vector<int, std::__1::allocator<int> > const&)>::type,
std::__1::decay<std::__1::vector<int, std::__1::allocator<int> >
const&>::type>::type> std::__1::async<int (&)(std::__1::vector<int,
std::__1::allocator<int> > const&), std::__1::vector<int,
std::__1::allocator<int> > const&>(std::__1::launch, int
(&&&)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > const&&&) <null>:1062592
(main.exe:x86_64+0x10000196a)
    #3 main <null>:1062592 (main.exe:x86_64+0x10000104c)

  Mutex M14 (0x7b2c00000018) created at:
    #0 pthread_mutex_lock <null>:1062448
(libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x3945e)
    #1 std::__1::mutex::lock() <null>:1062448 (libc++.1.dylib:x86_64+0x3a698)
    #2 std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::__execute()
<null>:1062448 (main.exe:x86_64+0x1000047de)
    #3 void*
std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct,
std::__1::default_delete<std::__1::__thread_struct> >, void
(std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::*)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*> >(void*) <null>:1062448
(main.exe:x86_64+0x100006b82)

  Thread T1 (tid=572648, running) created by main thread at:
    #0 pthread_create <null>:1062640
(libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x2a13d)
    #1 std::__1::thread::thread<void (std::__1::__async_assoc_state<int,
std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int> >
const&), std::__1::vector<int, std::__1::allocator<int> > > >::*)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*, void>(void
(std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::*&&)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*&&) <null>:1062640
(main.exe:x86_64+0x1000060d0)
    #2 std::__1::thread::thread<void (std::__1::__async_assoc_state<int,
std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int> >
const&), std::__1::vector<int, std::__1::allocator<int> > > >::*)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*, void>(void
(std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >::*&&)(),
std::__1::__async_assoc_state<int, std::__1::__async_func<int
(*)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > > >*&&) <null>:1062640
(main.exe:x86_64+0x100004358)
    #3 std::__1::future<int> std::__1::__make_async_assoc_state<int,
std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int> >
const&), std::__1::vector<int, std::__1::allocator<int> > >
>(std::__1::__async_func<int (*)(std::__1::vector<int, std::__1::allocator<int>
> const&), std::__1::vector<int, std::__1::allocator<int> > >&&) <null>:1062640
(main.exe:x86_64+0x1000039b0)
    #4 std::__1::future<std::__1::__invoke_of<std::__1::decay<int
(&)(std::__1::vector<int, std::__1::allocator<int> > const&)>::type,
std::__1::decay<std::__1::vector<int, std::__1::allocator<int> >
const&>::type>::type> std::__1::async<int (&)(std::__1::vector<int,
std::__1::allocator<int> > const&), std::__1::vector<int,
std::__1::allocator<int> > const&>(std::__1::launch, int
(&&&)(std::__1::vector<int, std::__1::allocator<int> > const&),
std::__1::vector<int, std::__1::allocator<int> > const&&&) <null>:1062640
(main.exe:x86_64+0x10000196a)
    #5 main <null>:1062640 (main.exe:x86_64+0x10000104c)

SUMMARY: ThreadSanitizer: data race (main.exe:x86_64+0x10000556b) in void
std::__1::__assoc_state<int>::set_value<int>(int&&)
------------------------------------------------------------------------------

The race seems to be between the following functions:

    template <class _Rp>
    future<_Rp>::future(__assoc_state<_Rp>* __state)
        : __state_(__state)
    {
        if (__state_->__has_future_attached())
            __throw_future_error(future_errc::future_already_retrieved);
        __state_->__add_shared();
        __state_->__set_future_attached();
    }

and:

    template <class _Rp>
    template <class _Arg>
    void __assoc_state<_Rp>::set_value(_Arg&& __arg) {
        unique_lock<mutex> __lk(this->__mut_);
        if (this->__has_value())
            __throw_future_error(future_errc::promise_already_satisfied);
        ::new(&__value_) _Rp(_VSTD::forward<_Arg>(__arg));
        this->__state_ |= base::__constructed | base::ready;
        __cv_.notify_all();
    }

Those functions are run concurrently from __make_async_assoc_state:

    template <class _Rp, class _Fp>
    future<_Rp> __make_async_assoc_state(_Fp&& __f) {
        unique_ptr<__async_assoc_state<_Rp, _Fp>, __release_shared_count>
            __h(new __async_assoc_state<_Rp, _Fp>(_VSTD::forward<_Fp>(__f)));
        _VSTD::thread([h = __h.get()]{
            h->set_value(h->__func_()); // call to set_value()
        }).detach();
        return future<_Rp>(__h.get()); // call to future's constructor
    }

The problem is that the constructor reads the state of the future (with
__has_future_attached()) without synchronization, and set_value() sets that
state concurrently. set_value() properly acquires the mutex, but the
constructor does not.

My proposed resolution would be to take a lock in the constructor at the very
beginning, like so:

    template <class _Rp>
    future<_Rp>::future(__assoc_state<_Rp>* __state)
        : __state_(__state)
    {
        unique_lock<mutex> __lk(__state_->__mut_);
        if (__state_->__has_future_attached())
            __throw_future_error(future_errc::future_already_retrieved);
        __state_->__add_shared();
        __state_->__set_future_attached();
    }

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20180823/23eb5b32/attachment-0001.html>


More information about the llvm-bugs mailing list