* [Patch, libstdc++] Fix data races in basic_string implementation
@ 2015-09-01 12:52 Dmitry Vyukov
2015-09-01 14:27 ` Jonathan Wakely
2015-09-02 10:58 ` Marc Glisse
0 siblings, 2 replies; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-01 12:52 UTC (permalink / raw)
To: GCC Patches, libstdc++; +Cc: Alexander Potapenko, Kostya Serebryany
[-- Attachment #1: Type: text/plain, Size: 6272 bytes --]
Hello,
The refcounted basic_string implementation contains several data races
on _M_refcount:
1. _M_is_leaked loads _M_refcount concurrently with mutations of
_M_refcount. This loads needs to be memory_order_relaxed load, as
_M_is_leaked predicate does not change under the feet.
2. _M_is_shared loads _M_refcount concurrently with mutations of
_M_refcount. This loads needs to be memory_order_acquire, as another
thread can drop _M_refcount to zero concurrently which makes the
string non-shared and so the current thread can mutate the string. We
need reads of the string in another thread (while it was shared) to
happen-before the writes to the string in this thread (now that it is
non-shared).
This patch adds __gnu_cxx::__atomic_load_dispatch function to do the
loads of _M_refcount. The function does an acquire load. Acquire is
non needed for _M_is_leaked, but for simplicity as still do acquire
(hopefully the refcounted impl will go away in future).
This patch also uses the new function to do loads of _M_refcount in
string implementation.
I did non update doc/xml/manual/concurrency_extensions.xml to document
__gnu_cxx::__atomic_load_dispatch, because I am not sure we want to
extend that api now that we have language-level atomics. If you still
want me to update it, please say how to regenerate
doc/html/manual/ext_concurrency.html.
The race was detected with ThreadSanitizer on the following program:
#define _GLIBCXX_USE_CXX11_ABI 0
#include <string>
#include <thread>
#include <iostream>
#include <chrono>
int main() {
std::string s = "foo";
std::thread t([=](){
std::string x = s;
std::cout << &x << std::endl;
});
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << &s[0] << std::endl;
t.join();
}
$ g++ string.cc -std=c++11 -pthread -O1 -g -fsanitize=thread
$ ./a.out
WARNING: ThreadSanitizer: data race (pid=98135)
Read of size 4 at 0x7d080000efd0 by main thread:
#0 std::string::_Rep::_M_is_leaked() const
include/c++/6.0.0/bits/basic_string.h:2605 (a.out+0x000000401d7c)
#1 std::string::_M_leak()
include/c++/6.0.0/bits/basic_string.h:2730 (a.out+0x000000401d7c)
#2 std::string::operator[](unsigned long)
include/c++/6.0.0/bits/basic_string.h:3274 (a.out+0x000000401d7c)
#3 main /tmp/string.cc:14 (a.out+0x000000401d7c)
Previous atomic write of size 4 at 0x7d080000efd0 by thread T1:
#0 __tsan_atomic32_fetch_add
../../../../libsanitizer/tsan/tsan_interface_atomic.cc:611
(libtsan.so.0+0x00000005811f)
#1 __exchange_and_add include/c++/6.0.0/ext/atomicity.h:59
(a.out+0x000000401a19)
#2 __exchange_and_add_dispatch
include/c++/6.0.0/ext/atomicity.h:92 (a.out+0x000000401a19)
#3 std::string::_Rep::_M_dispose(std::allocator<char> const&)
include/c++/6.0.0/bits/basic_string.h:2659 (a.out+0x000000401a19)
#4 std::basic_string<char, std::char_traits<char>,
std::allocator<char> >::~basic_string()
include/c++/6.0.0/bits/basic_string.h:2961 (a.out+0x000000401a19)
#5 ~<lambda> /tmp/string.cc:9 (a.out+0x000000401a19)
#6 ~_Head_base include/c++/6.0.0/tuple:102 (a.out+0x000000401a19)
#7 ~_Tuple_impl include/c++/6.0.0/tuple:338 (a.out+0x000000401a19)
#8 ~tuple include/c++/6.0.0/tuple:521 (a.out+0x000000401a19)
#9 ~_Bind_simple include/c++/6.0.0/functional:1503 (a.out+0x000000401a19)
#10 ~_Impl include/c++/6.0.0/thread:107 (a.out+0x000000401a19)
#11 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
> > include/c++/6.0.0/ext/new_allocator.h:124 (a.out+0x0000004015c7)
#12 _S_destroy<std::allocator<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
> >, std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()> > >
include/c++/6.0.0/bits/alloc_traits.h:236 (a.out+0x0000004015c7)
#13 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
> > include/c++/6.0.0/bits/alloc_traits.h:336 (a.out+0x0000004015c7)
#14 _M_dispose include/c++/6.0.0/bits/shared_ptr_base.h:529
(a.out+0x0000004015c7)
#15 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release()
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:150
(libstdc++.so.6+0x0000000b6da4)
#16 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count()
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:662
(libstdc++.so.6+0x0000000b6da4)
#17 std::__shared_ptr<std::thread::_Impl_base,
(__gnu_cxx::_Lock_policy)2>::~__shared_ptr()
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:928
(libstdc++.so.6+0x0000000b6da4)
#18 std::shared_ptr<std::thread::_Impl_base>::~shared_ptr()
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr.h:93
(libstdc++.so.6+0x0000000b6da4)
#19 execute_native_thread_routine
libstdc++-v3/src/c++11/thread.cc:79 (libstdc++.so.6+0x0000000b6da4)
Location is heap block of size 28 at 0x7d080000efc0 allocated by main thread:
#0 operator new(unsigned long)
../../../../libsanitizer/tsan/tsan_interceptors.cc:571
(libtsan.so.0+0x0000000255a3)
#1 __gnu_cxx::new_allocator<char>::allocate(unsigned long, void
const*) /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/ext/new_allocator.h:104
(libstdc++.so.6+0x0000000cbaa8)
#2 std::string::_Rep::_S_create(unsigned long, unsigned long,
std::allocator<char> const&)
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.tcc:1051
(libstdc++.so.6+0x0000000cbaa8)
#3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
Thread T1 (tid=98137, finished) created by main thread at:
#0 pthread_create
../../../../libsanitizer/tsan/tsan_interceptors.cc:895
(libtsan.so.0+0x000000026d04)
#1 __gthread_create
/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:662
(libstdc++.so.6+0x0000000b6e52)
#2 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>,
void (*)()) libstdc++-v3/src/c++11/thread.cc:149
(libstdc++.so.6+0x0000000b6e52)
#3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
OK for trunk?
[-- Attachment #2: patch.diff --]
[-- Type: text/plain, Size: 2629 bytes --]
Index: include/bits/basic_string.h
===================================================================
--- include/bits/basic_string.h (revision 227363)
+++ include/bits/basic_string.h (working copy)
@@ -2601,11 +2601,11 @@
bool
_M_is_leaked() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount < 0; }
+ { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) < 0; }
bool
_M_is_shared() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount > 0; }
+ { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) > 0; }
void
_M_set_leaked() _GLIBCXX_NOEXCEPT
Index: include/ext/atomicity.h
===================================================================
--- include/ext/atomicity.h (revision 227363)
+++ include/ext/atomicity.h (working copy)
@@ -35,6 +35,16 @@
#include <bits/gthr.h>
#include <bits/atomic_word.h>
+// Even if the CPU doesn't need a memory barrier, we need to ensure
+// that the compiler doesn't reorder memory accesses across the
+// barriers.
+#ifndef _GLIBCXX_READ_MEM_BARRIER
+#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence (__ATOMIC_ACQUIRE)
+#endif
+#ifndef _GLIBCXX_WRITE_MEM_BARRIER
+#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence (__ATOMIC_RELEASE)
+#endif
+
namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
@@ -50,7 +60,7 @@
static inline void
__atomic_add(volatile _Atomic_word* __mem, int __val)
- { __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }
+ { __atomic_fetch_add(__mem, __val, __ATOMIC_RELEASE); }
#else
_Atomic_word
__attribute__ ((__unused__))
@@ -101,17 +111,27 @@
#endif
}
+ static inline _Atomic_word
+ __attribute__ ((__unused__))
+ __atomic_load_dispatch(const _Atomic_word* __mem)
+ {
+#ifdef __GTHREADS
+ if (__gthread_active_p())
+ {
+#ifdef _GLIBCXX_ATOMIC_BUILTINS
+ return __atomic_load_n(__mem, __ATOMIC_ACQUIRE);
+#else
+ // The best we can get with an old compiler.
+ _Atomic_word v = *(volatile _Atomic_word*)__mem;
+ _GLIBCXX_READ_MEM_BARRIER;
+ return v;
+#endif
+ }
+#endif
+ return *__mem;
+ }
+
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
-// Even if the CPU doesn't need a memory barrier, we need to ensure
-// that the compiler doesn't reorder memory accesses across the
-// barriers.
-#ifndef _GLIBCXX_READ_MEM_BARRIER
-#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence (__ATOMIC_ACQUIRE)
-#endif
-#ifndef _GLIBCXX_WRITE_MEM_BARRIER
-#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence (__ATOMIC_RELEASE)
-#endif
-
#endif
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 12:52 [Patch, libstdc++] Fix data races in basic_string implementation Dmitry Vyukov
@ 2015-09-01 14:27 ` Jonathan Wakely
2015-09-01 14:56 ` Dmitry Vyukov
2015-09-02 10:58 ` Marc Glisse
1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Wakely @ 2015-09-01 14:27 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
On 01/09/15 14:51 +0200, Dmitry Vyukov wrote:
>Hello,
>
>The refcounted basic_string implementation contains several data races
>on _M_refcount:
>1. _M_is_leaked loads _M_refcount concurrently with mutations of
>_M_refcount. This loads needs to be memory_order_relaxed load, as
>_M_is_leaked predicate does not change under the feet.
>2. _M_is_shared loads _M_refcount concurrently with mutations of
>_M_refcount. This loads needs to be memory_order_acquire, as another
>thread can drop _M_refcount to zero concurrently which makes the
>string non-shared and so the current thread can mutate the string. We
>need reads of the string in another thread (while it was shared) to
>happen-before the writes to the string in this thread (now that it is
>non-shared).
>
>This patch adds __gnu_cxx::__atomic_load_dispatch function to do the
>loads of _M_refcount. The function does an acquire load. Acquire is
>non needed for _M_is_leaked, but for simplicity as still do acquire
>(hopefully the refcounted impl will go away in future).
It's unlikely to go away for a long time.
>This patch also uses the new function to do loads of _M_refcount in
>string implementation.
>
>I did non update doc/xml/manual/concurrency_extensions.xml to document
>__gnu_cxx::__atomic_load_dispatch, because I am not sure we want to
>extend that api now that we have language-level atomics. If you still
>want me to update it, please say how to regenerate
>doc/html/manual/ext_concurrency.html.
I don't know if we need the new __atomic_load_dispatch function at all,
we could just use atomic loads directly e.g.
bool
_M_is_leaked() const _GLIBCXX_NOEXCEPT
#if defined(__GTHREADS) && defined(_GLIBCXX_ATOMIC_BUILTINS)
+ { return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0; }
+#else
{ return this->_M_refcount > 0; }
+#endif
bool
_M_is_shared() const _GLIBCXX_NOEXCEPT
#if defined(__GTHREADS) && defined(_GLIBCXX_ATOMIC_BUILTINS)
+ { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0; }
+#else
{ return this->_M_refcount > 0; }
+#endif
The __atomic_xxx_dispatch functions check __gthread_active_p() as an
optimisation to avoid potentially expensive atomic stores, but I don't
think we need that for atomic loads here, especially the relaxed load.
We could always add the dispatch in later if we get complaints about
single-threaded performance on targets where the loads are expensive.
This doesn't fix the problem for targets that don't define
_GLIBCXX_ATOMIC_BUILTINS but I don't know how many of them there are.
We could make it work on more targets by adding a new configure check
just for __atomic_load(int*, ...), because _GLIBCXX_ATOMIC_BUILTINS
requires several builtins to support various object sizes, but here we
don't need all of that.
>The race was detected with ThreadSanitizer on the following program:
>
>#define _GLIBCXX_USE_CXX11_ABI 0
>#include <string>
>#include <thread>
>#include <iostream>
>#include <chrono>
>
>int main() {
> std::string s = "foo";
> std::thread t([=](){
> std::string x = s;
> std::cout << &x << std::endl;
> });
> std::this_thread::sleep_for(std::chrono::seconds(1));
> std::cout << &s[0] << std::endl;
> t.join();
>}
>
>$ g++ string.cc -std=c++11 -pthread -O1 -g -fsanitize=thread
>$ ./a.out
>
>WARNING: ThreadSanitizer: data race (pid=98135)
> Read of size 4 at 0x7d080000efd0 by main thread:
> #0 std::string::_Rep::_M_is_leaked() const
>include/c++/6.0.0/bits/basic_string.h:2605 (a.out+0x000000401d7c)
> #1 std::string::_M_leak()
>include/c++/6.0.0/bits/basic_string.h:2730 (a.out+0x000000401d7c)
> #2 std::string::operator[](unsigned long)
>include/c++/6.0.0/bits/basic_string.h:3274 (a.out+0x000000401d7c)
> #3 main /tmp/string.cc:14 (a.out+0x000000401d7c)
>
> Previous atomic write of size 4 at 0x7d080000efd0 by thread T1:
> #0 __tsan_atomic32_fetch_add
>../../../../libsanitizer/tsan/tsan_interface_atomic.cc:611
>(libtsan.so.0+0x00000005811f)
> #1 __exchange_and_add include/c++/6.0.0/ext/atomicity.h:59
>(a.out+0x000000401a19)
> #2 __exchange_and_add_dispatch
>include/c++/6.0.0/ext/atomicity.h:92 (a.out+0x000000401a19)
> #3 std::string::_Rep::_M_dispose(std::allocator<char> const&)
>include/c++/6.0.0/bits/basic_string.h:2659 (a.out+0x000000401a19)
> #4 std::basic_string<char, std::char_traits<char>,
>std::allocator<char> >::~basic_string()
>include/c++/6.0.0/bits/basic_string.h:2961 (a.out+0x000000401a19)
> #5 ~<lambda> /tmp/string.cc:9 (a.out+0x000000401a19)
> #6 ~_Head_base include/c++/6.0.0/tuple:102 (a.out+0x000000401a19)
> #7 ~_Tuple_impl include/c++/6.0.0/tuple:338 (a.out+0x000000401a19)
> #8 ~tuple include/c++/6.0.0/tuple:521 (a.out+0x000000401a19)
> #9 ~_Bind_simple include/c++/6.0.0/functional:1503 (a.out+0x000000401a19)
> #10 ~_Impl include/c++/6.0.0/thread:107 (a.out+0x000000401a19)
> #11 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>> > include/c++/6.0.0/ext/new_allocator.h:124 (a.out+0x0000004015c7)
> #12 _S_destroy<std::allocator<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>> >, std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()> > >
>include/c++/6.0.0/bits/alloc_traits.h:236 (a.out+0x0000004015c7)
> #13 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>> > include/c++/6.0.0/bits/alloc_traits.h:336 (a.out+0x0000004015c7)
> #14 _M_dispose include/c++/6.0.0/bits/shared_ptr_base.h:529
>(a.out+0x0000004015c7)
> #15 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release()
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:150
>(libstdc++.so.6+0x0000000b6da4)
> #16 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count()
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:662
>(libstdc++.so.6+0x0000000b6da4)
> #17 std::__shared_ptr<std::thread::_Impl_base,
>(__gnu_cxx::_Lock_policy)2>::~__shared_ptr()
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:928
>(libstdc++.so.6+0x0000000b6da4)
> #18 std::shared_ptr<std::thread::_Impl_base>::~shared_ptr()
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr.h:93
>(libstdc++.so.6+0x0000000b6da4)
> #19 execute_native_thread_routine
>libstdc++-v3/src/c++11/thread.cc:79 (libstdc++.so.6+0x0000000b6da4)
>
> Location is heap block of size 28 at 0x7d080000efc0 allocated by main thread:
> #0 operator new(unsigned long)
>../../../../libsanitizer/tsan/tsan_interceptors.cc:571
>(libtsan.so.0+0x0000000255a3)
> #1 __gnu_cxx::new_allocator<char>::allocate(unsigned long, void
>const*) /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/ext/new_allocator.h:104
>(libstdc++.so.6+0x0000000cbaa8)
> #2 std::string::_Rep::_S_create(unsigned long, unsigned long,
>std::allocator<char> const&)
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.tcc:1051
>(libstdc++.so.6+0x0000000cbaa8)
> #3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
>
> Thread T1 (tid=98137, finished) created by main thread at:
> #0 pthread_create
>../../../../libsanitizer/tsan/tsan_interceptors.cc:895
>(libtsan.so.0+0x000000026d04)
> #1 __gthread_create
>/usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:662
>(libstdc++.so.6+0x0000000b6e52)
> #2 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>,
>void (*)()) libstdc++-v3/src/c++11/thread.cc:149
>(libstdc++.so.6+0x0000000b6e52)
> #3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
>
>OK for trunk?
>Index: include/bits/basic_string.h
>===================================================================
>--- include/bits/basic_string.h (revision 227363)
>+++ include/bits/basic_string.h (working copy)
>@@ -2601,11 +2601,11 @@
>
> bool
> _M_is_leaked() const _GLIBCXX_NOEXCEPT
>- { return this->_M_refcount < 0; }
>+ { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) < 0; }
>
> bool
> _M_is_shared() const _GLIBCXX_NOEXCEPT
>- { return this->_M_refcount > 0; }
>+ { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) > 0; }
>
> void
> _M_set_leaked() _GLIBCXX_NOEXCEPT
>Index: include/ext/atomicity.h
>===================================================================
>--- include/ext/atomicity.h (revision 227363)
>+++ include/ext/atomicity.h (working copy)
>@@ -35,6 +35,16 @@
> #include <bits/gthr.h>
> #include <bits/atomic_word.h>
>
>+// Even if the CPU doesn't need a memory barrier, we need to ensure
>+// that the compiler doesn't reorder memory accesses across the
>+// barriers.
>+#ifndef _GLIBCXX_READ_MEM_BARRIER
>+#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence (__ATOMIC_ACQUIRE)
>+#endif
>+#ifndef _GLIBCXX_WRITE_MEM_BARRIER
>+#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence (__ATOMIC_RELEASE)
>+#endif
>+
> namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>@@ -50,7 +60,7 @@
>
> static inline void
> __atomic_add(volatile _Atomic_word* __mem, int __val)
>- { __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }
>+ { __atomic_fetch_add(__mem, __val, __ATOMIC_RELEASE); }
> #else
> _Atomic_word
> __attribute__ ((__unused__))
>@@ -101,17 +111,27 @@
> #endif
> }
>
>+ static inline _Atomic_word
>+ __attribute__ ((__unused__))
>+ __atomic_load_dispatch(const _Atomic_word* __mem)
>+ {
>+#ifdef __GTHREADS
>+ if (__gthread_active_p())
>+ {
>+#ifdef _GLIBCXX_ATOMIC_BUILTINS
>+ return __atomic_load_n(__mem, __ATOMIC_ACQUIRE);
>+#else
>+ // The best we can get with an old compiler.
We don't support old compilers in libstdc++ trunk, so this comment is
misleading. The fallback is needed for targets without support for all
the builtins, not for old compilers.
>+ _Atomic_word v = *(volatile _Atomic_word*)__mem;
>+ _GLIBCXX_READ_MEM_BARRIER;
If a target doesn't define _GLIBCXX_ATOMIC_BUILTINS do we know it will
define __atomic_thread_fence ?
I guess those targets should be redefining _GLIBCXX_READ_MEM_BARRIER
anyway.
>+ return v;
>+#endif
>+ }
>+#endif
>+ return *__mem;
>+ }
>+
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace
>
>-// Even if the CPU doesn't need a memory barrier, we need to ensure
>-// that the compiler doesn't reorder memory accesses across the
>-// barriers.
>-#ifndef _GLIBCXX_READ_MEM_BARRIER
>-#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence (__ATOMIC_ACQUIRE)
>-#endif
>-#ifndef _GLIBCXX_WRITE_MEM_BARRIER
>-#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence (__ATOMIC_RELEASE)
>-#endif
>-
> #endif
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 14:27 ` Jonathan Wakely
@ 2015-09-01 14:56 ` Dmitry Vyukov
2015-09-01 15:08 ` Jonathan Wakely
0 siblings, 1 reply; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-01 14:56 UTC (permalink / raw)
To: Jonathan Wakely
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
On Tue, Sep 1, 2015 at 4:27 PM, Jonathan Wakely <jwakely@redhat.com> wrote:
> On 01/09/15 14:51 +0200, Dmitry Vyukov wrote:
>>
>> Hello,
>>
>> The refcounted basic_string implementation contains several data races
>> on _M_refcount:
>> 1. _M_is_leaked loads _M_refcount concurrently with mutations of
>> _M_refcount. This loads needs to be memory_order_relaxed load, as
>> _M_is_leaked predicate does not change under the feet.
>> 2. _M_is_shared loads _M_refcount concurrently with mutations of
>> _M_refcount. This loads needs to be memory_order_acquire, as another
>> thread can drop _M_refcount to zero concurrently which makes the
>> string non-shared and so the current thread can mutate the string. We
>> need reads of the string in another thread (while it was shared) to
>> happen-before the writes to the string in this thread (now that it is
>> non-shared).
>>
>> This patch adds __gnu_cxx::__atomic_load_dispatch function to do the
>> loads of _M_refcount. The function does an acquire load. Acquire is
>> non needed for _M_is_leaked, but for simplicity as still do acquire
>> (hopefully the refcounted impl will go away in future).
>
>
> It's unlikely to go away for a long time.
ack
>> This patch also uses the new function to do loads of _M_refcount in
>> string implementation.
>>
>> I did non update doc/xml/manual/concurrency_extensions.xml to document
>> __gnu_cxx::__atomic_load_dispatch, because I am not sure we want to
>> extend that api now that we have language-level atomics. If you still
>> want me to update it, please say how to regenerate
>> doc/html/manual/ext_concurrency.html.
>
>
> I don't know if we need the new __atomic_load_dispatch function at all,
> we could just use atomic loads directly e.g.
>
> bool
> _M_is_leaked() const _GLIBCXX_NOEXCEPT
> #if defined(__GTHREADS) && defined(_GLIBCXX_ATOMIC_BUILTINS)
> + { return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0;
> }
> +#else
> { return this->_M_refcount > 0; }
> +#endif
>
> bool
> _M_is_shared() const _GLIBCXX_NOEXCEPT
> #if defined(__GTHREADS) && defined(_GLIBCXX_ATOMIC_BUILTINS)
> + { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0; }
> +#else
> { return this->_M_refcount > 0; }
> +#endif
Looks better to me.
I wasn't sure why that dispatch indirection is there at all, so I did
what the current does.
> The __atomic_xxx_dispatch functions check __gthread_active_p() as an
> optimisation to avoid potentially expensive atomic stores, but I don't
> think we need that for atomic loads here, especially the relaxed load.
> We could always add the dispatch in later if we get complaints about
> single-threaded performance on targets where the loads are expensive.
>
> This doesn't fix the problem for targets that don't define
> _GLIBCXX_ATOMIC_BUILTINS but I don't know how many of them there are.
> We could make it work on more targets by adding a new configure check
> just for __atomic_load(int*, ...), because _GLIBCXX_ATOMIC_BUILTINS
> requires several builtins to support various object sizes, but here we
> don't need all of that.
I don't understand how a new gcc may not support __atomic builtins on
ints. How it is even possible? That's a portable API provided by
recent gcc's...
>> The race was detected with ThreadSanitizer on the following program:
>>
>> #define _GLIBCXX_USE_CXX11_ABI 0
>> #include <string>
>> #include <thread>
>> #include <iostream>
>> #include <chrono>
>>
>> int main() {
>> std::string s = "foo";
>> std::thread t([=](){
>> std::string x = s;
>> std::cout << &x << std::endl;
>> });
>> std::this_thread::sleep_for(std::chrono::seconds(1));
>> std::cout << &s[0] << std::endl;
>> t.join();
>> }
>>
>> $ g++ string.cc -std=c++11 -pthread -O1 -g -fsanitize=thread
>> $ ./a.out
>>
>> WARNING: ThreadSanitizer: data race (pid=98135)
>> Read of size 4 at 0x7d080000efd0 by main thread:
>> #0 std::string::_Rep::_M_is_leaked() const
>> include/c++/6.0.0/bits/basic_string.h:2605 (a.out+0x000000401d7c)
>> #1 std::string::_M_leak()
>> include/c++/6.0.0/bits/basic_string.h:2730 (a.out+0x000000401d7c)
>> #2 std::string::operator[](unsigned long)
>> include/c++/6.0.0/bits/basic_string.h:3274 (a.out+0x000000401d7c)
>> #3 main /tmp/string.cc:14 (a.out+0x000000401d7c)
>>
>> Previous atomic write of size 4 at 0x7d080000efd0 by thread T1:
>> #0 __tsan_atomic32_fetch_add
>> ../../../../libsanitizer/tsan/tsan_interface_atomic.cc:611
>> (libtsan.so.0+0x00000005811f)
>> #1 __exchange_and_add include/c++/6.0.0/ext/atomicity.h:59
>> (a.out+0x000000401a19)
>> #2 __exchange_and_add_dispatch
>> include/c++/6.0.0/ext/atomicity.h:92 (a.out+0x000000401a19)
>> #3 std::string::_Rep::_M_dispose(std::allocator<char> const&)
>> include/c++/6.0.0/bits/basic_string.h:2659 (a.out+0x000000401a19)
>> #4 std::basic_string<char, std::char_traits<char>,
>> std::allocator<char> >::~basic_string()
>> include/c++/6.0.0/bits/basic_string.h:2961 (a.out+0x000000401a19)
>> #5 ~<lambda> /tmp/string.cc:9 (a.out+0x000000401a19)
>> #6 ~_Head_base include/c++/6.0.0/tuple:102 (a.out+0x000000401a19)
>> #7 ~_Tuple_impl include/c++/6.0.0/tuple:338 (a.out+0x000000401a19)
>> #8 ~tuple include/c++/6.0.0/tuple:521 (a.out+0x000000401a19)
>> #9 ~_Bind_simple include/c++/6.0.0/functional:1503
>> (a.out+0x000000401a19)
>> #10 ~_Impl include/c++/6.0.0/thread:107 (a.out+0x000000401a19)
>> #11 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>>>
>>> > include/c++/6.0.0/ext/new_allocator.h:124 (a.out+0x0000004015c7)
>>
>> #12
>> _S_destroy<std::allocator<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>>>
>>> >, std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()> > >
>>
>> include/c++/6.0.0/bits/alloc_traits.h:236 (a.out+0x0000004015c7)
>> #13 destroy<std::thread::_Impl<std::_Bind_simple<main()::<lambda()>()>
>>>
>>> > include/c++/6.0.0/bits/alloc_traits.h:336 (a.out+0x0000004015c7)
>>
>> #14 _M_dispose include/c++/6.0.0/bits/shared_ptr_base.h:529
>> (a.out+0x0000004015c7)
>> #15 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release()
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:150
>> (libstdc++.so.6+0x0000000b6da4)
>> #16 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count()
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:662
>> (libstdc++.so.6+0x0000000b6da4)
>> #17 std::__shared_ptr<std::thread::_Impl_base,
>> (__gnu_cxx::_Lock_policy)2>::~__shared_ptr()
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr_base.h:928
>> (libstdc++.so.6+0x0000000b6da4)
>> #18 std::shared_ptr<std::thread::_Impl_base>::~shared_ptr()
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/shared_ptr.h:93
>> (libstdc++.so.6+0x0000000b6da4)
>> #19 execute_native_thread_routine
>> libstdc++-v3/src/c++11/thread.cc:79 (libstdc++.so.6+0x0000000b6da4)
>>
>> Location is heap block of size 28 at 0x7d080000efc0 allocated by main
>> thread:
>> #0 operator new(unsigned long)
>> ../../../../libsanitizer/tsan/tsan_interceptors.cc:571
>> (libtsan.so.0+0x0000000255a3)
>> #1 __gnu_cxx::new_allocator<char>::allocate(unsigned long, void
>> const*)
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/ext/new_allocator.h:104
>> (libstdc++.so.6+0x0000000cbaa8)
>> #2 std::string::_Rep::_S_create(unsigned long, unsigned long,
>> std::allocator<char> const&)
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.tcc:1051
>> (libstdc++.so.6+0x0000000cbaa8)
>> #3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
>>
>> Thread T1 (tid=98137, finished) created by main thread at:
>> #0 pthread_create
>> ../../../../libsanitizer/tsan/tsan_interceptors.cc:895
>> (libtsan.so.0+0x000000026d04)
>> #1 __gthread_create
>>
>> /usr/local/google/home/dvyukov/src/gcc/build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:662
>> (libstdc++.so.6+0x0000000b6e52)
>> #2
>> std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>,
>> void (*)()) libstdc++-v3/src/c++11/thread.cc:149
>> (libstdc++.so.6+0x0000000b6e52)
>> #3 __libc_start_main <null> (libc.so.6+0x000000021ec4)
>>
>> OK for trunk?
>
>
>> Index: include/bits/basic_string.h
>> ===================================================================
>> --- include/bits/basic_string.h (revision 227363)
>> +++ include/bits/basic_string.h (working copy)
>> @@ -2601,11 +2601,11 @@
>>
>> bool
>> _M_is_leaked() const _GLIBCXX_NOEXCEPT
>> - { return this->_M_refcount < 0; }
>> + { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) <
>> 0; }
>>
>> bool
>> _M_is_shared() const _GLIBCXX_NOEXCEPT
>> - { return this->_M_refcount > 0; }
>> + { return __gnu_cxx::__atomic_load_dispatch(&this->_M_refcount) >
>> 0; }
>>
>> void
>> _M_set_leaked() _GLIBCXX_NOEXCEPT
>> Index: include/ext/atomicity.h
>> ===================================================================
>> --- include/ext/atomicity.h (revision 227363)
>> +++ include/ext/atomicity.h (working copy)
>> @@ -35,6 +35,16 @@
>> #include <bits/gthr.h>
>> #include <bits/atomic_word.h>
>>
>> +// Even if the CPU doesn't need a memory barrier, we need to ensure
>> +// that the compiler doesn't reorder memory accesses across the
>> +// barriers.
>> +#ifndef _GLIBCXX_READ_MEM_BARRIER
>> +#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence
>> (__ATOMIC_ACQUIRE)
>> +#endif
>> +#ifndef _GLIBCXX_WRITE_MEM_BARRIER
>> +#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence
>> (__ATOMIC_RELEASE)
>> +#endif
>> +
>> namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
>> {
>> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> @@ -50,7 +60,7 @@
>>
>> static inline void
>> __atomic_add(volatile _Atomic_word* __mem, int __val)
>> - { __atomic_fetch_add(__mem, __val, __ATOMIC_ACQ_REL); }
>> + { __atomic_fetch_add(__mem, __val, __ATOMIC_RELEASE); }
>> #else
>> _Atomic_word
>> __attribute__ ((__unused__))
>> @@ -101,17 +111,27 @@
>> #endif
>> }
>>
>> + static inline _Atomic_word
>> + __attribute__ ((__unused__))
>> + __atomic_load_dispatch(const _Atomic_word* __mem)
>> + {
>> +#ifdef __GTHREADS
>> + if (__gthread_active_p())
>> + {
>> +#ifdef _GLIBCXX_ATOMIC_BUILTINS
>> + return __atomic_load_n(__mem, __ATOMIC_ACQUIRE);
>> +#else
>> + // The best we can get with an old compiler.
>
>
> We don't support old compilers in libstdc++ trunk, so this comment is
> misleading. The fallback is needed for targets without support for all
> the builtins, not for old compilers.
>
>> + _Atomic_word v = *(volatile _Atomic_word*)__mem;
>> + _GLIBCXX_READ_MEM_BARRIER;
>
>
> If a target doesn't define _GLIBCXX_ATOMIC_BUILTINS do we know it will
> define __atomic_thread_fence ?
>
> I guess those targets should be redefining _GLIBCXX_READ_MEM_BARRIER
> anyway.
Yeah, that was my understanding.
_GLIBCXX_READ_MEM_BARRIER is already intended to be a portable thing
whatever way it is implemented.
>> + return v;
>> +#endif
>> + }
>> +#endif
>> + return *__mem;
>> + }
>> +
>> _GLIBCXX_END_NAMESPACE_VERSION
>> } // namespace
>>
>> -// Even if the CPU doesn't need a memory barrier, we need to ensure
>> -// that the compiler doesn't reorder memory accesses across the
>> -// barriers.
>> -#ifndef _GLIBCXX_READ_MEM_BARRIER
>> -#define _GLIBCXX_READ_MEM_BARRIER __atomic_thread_fence
>> (__ATOMIC_ACQUIRE)
>> -#endif
>> -#ifndef _GLIBCXX_WRITE_MEM_BARRIER
>> -#define _GLIBCXX_WRITE_MEM_BARRIER __atomic_thread_fence
>> (__ATOMIC_RELEASE)
>> -#endif
>> -
>> #endif
>
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 14:56 ` Dmitry Vyukov
@ 2015-09-01 15:08 ` Jonathan Wakely
2015-09-01 15:42 ` Dmitry Vyukov
0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Wakely @ 2015-09-01 15:08 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
On 01/09/15 16:56 +0200, Dmitry Vyukov wrote:
>I don't understand how a new gcc may not support __atomic builtins on
>ints. How it is even possible? That's a portable API provided by
>recent gcc's...
The built-in function is always defined, but it might expand to a call
to an external function in libatomic, and it would be a regression for
code using std::string to start requiring libatomic (although maybe it
would be necessary if it's the only way to make the code correct).
I don't know if there are any targets that define __GTHREADS and also
don't support __atomic_load(int*, ...) without libatomic. If such
targets exist then adding a new configure check that only depends on
__atomic_load(int*, ...) would mean we keep supporting those targets.
Another option would be to simply do:
bool
_M_is_shared() const _GLIBCXX_NOEXCEPT
#if defined(__GTHREADS)
+ { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0; }
+#else
{ return this->_M_refcount > 0; }
+#endif
and see if anyone complains!
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 15:08 ` Jonathan Wakely
@ 2015-09-01 15:42 ` Dmitry Vyukov
2015-09-02 13:17 ` Jonathan Wakely
0 siblings, 1 reply; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-01 15:42 UTC (permalink / raw)
To: Jonathan Wakely
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
[-- Attachment #1: Type: text/plain, Size: 1405 bytes --]
On Tue, Sep 1, 2015 at 5:08 PM, Jonathan Wakely <jwakely@redhat.com> wrote:
> On 01/09/15 16:56 +0200, Dmitry Vyukov wrote:
>>
>> I don't understand how a new gcc may not support __atomic builtins on
>> ints. How it is even possible? That's a portable API provided by
>> recent gcc's...
>
>
> The built-in function is always defined, but it might expand to a call
> to an external function in libatomic, and it would be a regression for
> code using std::string to start requiring libatomic (although maybe it
> would be necessary if it's the only way to make the code correct).
>
> I don't know if there are any targets that define __GTHREADS and also
> don't support __atomic_load(int*, ...) without libatomic. If such
> targets exist then adding a new configure check that only depends on
> __atomic_load(int*, ...) would mean we keep supporting those targets.
>
> Another option would be to simply do:
>
> bool
> _M_is_shared() const _GLIBCXX_NOEXCEPT
> #if defined(__GTHREADS)
> + { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0; }
> +#else
> { return this->_M_refcount > 0; }
> +#endif
>
> and see if anyone complains!
I like this option!
If a platform uses multithreading and has non-inlined atomic loads,
then the way to fix this is to provide inlined atomic loads rather
than to fix all call sites.
Attaching new patch. Please take another look.
[-- Attachment #2: patch.diff --]
[-- Type: text/plain, Size: 1449 bytes --]
Index: include/bits/basic_string.h
===================================================================
--- include/bits/basic_string.h (revision 227363)
+++ include/bits/basic_string.h (working copy)
@@ -2601,11 +2601,32 @@
bool
_M_is_leaked() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount < 0; }
+ {
+#if defined(__GTHREADS)
+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
+ // so we need to use an atomic load. However, _M_is_leaked
+ // predicate does not change concurrently (i.e. the string is either
+ // leaked or not), so a relaxed load is enough.
+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0;
+#else
+ return this->_M_refcount < 0;
+#endif
+ }
bool
_M_is_shared() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount > 0; }
+ {
+#if defined(__GTHREADS)
+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
+ // so we need to use an atomic load. Another thread can drop last
+ // but one reference concurrently with this check, so we need this
+ // load to be acquire to synchronize with release fetch_and_add in
+ // _M_dispose.
+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0;
+#else
+ return this->_M_refcount > 0;
+#endif
+ }
void
_M_set_leaked() _GLIBCXX_NOEXCEPT
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 12:52 [Patch, libstdc++] Fix data races in basic_string implementation Dmitry Vyukov
2015-09-01 14:27 ` Jonathan Wakely
@ 2015-09-02 10:58 ` Marc Glisse
2015-09-02 13:50 ` Dmitry Vyukov
1 sibling, 1 reply; 12+ messages in thread
From: Marc Glisse @ 2015-09-02 10:58 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: GCC Patches, libstdc++, Alexander Potapenko, Kostya Serebryany
On Tue, 1 Sep 2015, Dmitry Vyukov wrote:
> The refcounted basic_string implementation contains several data races
> on _M_refcount:
There are several bug reports about races in basic_string in bugzilla
(some might even have been closed as wontfix because of the new
implementation). Does this also fix some of them?
(ChangeLog entry appears to be missing)
--
Marc Glisse
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-01 15:42 ` Dmitry Vyukov
@ 2015-09-02 13:17 ` Jonathan Wakely
2015-09-02 14:02 ` Dmitry Vyukov
0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Wakely @ 2015-09-02 13:17 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
On 01/09/15 17:42 +0200, Dmitry Vyukov wrote:
>On Tue, Sep 1, 2015 at 5:08 PM, Jonathan Wakely <jwakely@redhat.com> wrote:
>> On 01/09/15 16:56 +0200, Dmitry Vyukov wrote:
>>>
>>> I don't understand how a new gcc may not support __atomic builtins on
>>> ints. How it is even possible? That's a portable API provided by
>>> recent gcc's...
>>
>>
>> The built-in function is always defined, but it might expand to a call
>> to an external function in libatomic, and it would be a regression for
>> code using std::string to start requiring libatomic (although maybe it
>> would be necessary if it's the only way to make the code correct).
>>
>> I don't know if there are any targets that define __GTHREADS and also
>> don't support __atomic_load(int*, ...) without libatomic. If such
>> targets exist then adding a new configure check that only depends on
>> __atomic_load(int*, ...) would mean we keep supporting those targets.
>>
>> Another option would be to simply do:
>>
>> bool
>> _M_is_shared() const _GLIBCXX_NOEXCEPT
>> #if defined(__GTHREADS)
>> + { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0; }
>> +#else
>> { return this->_M_refcount > 0; }
>> +#endif
>>
>> and see if anyone complains!
>
>I like this option!
>If a platform uses multithreading and has non-inlined atomic loads,
>then the way to fix this is to provide inlined atomic loads rather
>than to fix all call sites.
>
>Attaching new patch. Please take another look.
This looks good. Torvald suggested that it would be useful to add a
similar comment to the release operation in _M_dispose, so that both
sides of the release-acquire are similarly documented. Could you add
that and provide a suitable ChangeLog entry?
Thanks!
>Index: include/bits/basic_string.h
>===================================================================
>--- include/bits/basic_string.h (revision 227363)
>+++ include/bits/basic_string.h (working copy)
>@@ -2601,11 +2601,32 @@
>
> bool
> _M_is_leaked() const _GLIBCXX_NOEXCEPT
>- { return this->_M_refcount < 0; }
>+ {
>+#if defined(__GTHREADS)
>+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
>+ // so we need to use an atomic load. However, _M_is_leaked
>+ // predicate does not change concurrently (i.e. the string is either
>+ // leaked or not), so a relaxed load is enough.
>+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0;
>+#else
>+ return this->_M_refcount < 0;
>+#endif
>+ }
>
> bool
> _M_is_shared() const _GLIBCXX_NOEXCEPT
>- { return this->_M_refcount > 0; }
>+ {
>+#if defined(__GTHREADS)
>+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
>+ // so we need to use an atomic load. Another thread can drop last
>+ // but one reference concurrently with this check, so we need this
>+ // load to be acquire to synchronize with release fetch_and_add in
>+ // _M_dispose.
>+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0;
>+#else
>+ return this->_M_refcount > 0;
>+#endif
>+ }
>
> void
> _M_set_leaked() _GLIBCXX_NOEXCEPT
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-02 10:58 ` Marc Glisse
@ 2015-09-02 13:50 ` Dmitry Vyukov
2015-09-02 14:05 ` Jonathan Wakely
0 siblings, 1 reply; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-02 13:50 UTC (permalink / raw)
To: libstdc++; +Cc: GCC Patches, Alexander Potapenko, Kostya Serebryany
On Wed, Sep 2, 2015 at 12:58 PM, Marc Glisse <marc.glisse@inria.fr> wrote:
> On Tue, 1 Sep 2015, Dmitry Vyukov wrote:
>
>> The refcounted basic_string implementation contains several data races
>> on _M_refcount:
>
>
> There are several bug reports about races in basic_string in bugzilla (some
> might even have been closed as wontfix because of the new implementation).
> Does this also fix some of them?
I've tried to search for "basic_string race" with all statuses:
https://gcc.gnu.org/bugzilla/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=SUSPENDED&bug_status=WAITING&bug_status=REOPENED&bug_status=RESOLVED&bug_status=VERIFIED&bug_status=CLOSED&cf_known_to_fail_type=allwords&cf_known_to_work_type=allwords&component=libstdc%2B%2B&list_id=125385&query_format=advanced&short_desc=basic_string%20race&short_desc_type=allwordssubstr
But it does not yield anything interesting. What bugs can I reference?
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-02 13:17 ` Jonathan Wakely
@ 2015-09-02 14:02 ` Dmitry Vyukov
2015-09-02 14:08 ` Jonathan Wakely
0 siblings, 1 reply; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-02 14:02 UTC (permalink / raw)
To: Jonathan Wakely
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
[-- Attachment #1: Type: text/plain, Size: 3607 bytes --]
Added comment to _M_dispose and restored ChangeLog entry.
Please take another look.
On Wed, Sep 2, 2015 at 3:17 PM, Jonathan Wakely <jwakely@redhat.com> wrote:
> On 01/09/15 17:42 +0200, Dmitry Vyukov wrote:
>>
>> On Tue, Sep 1, 2015 at 5:08 PM, Jonathan Wakely <jwakely@redhat.com>
>> wrote:
>>>
>>> On 01/09/15 16:56 +0200, Dmitry Vyukov wrote:
>>>>
>>>>
>>>> I don't understand how a new gcc may not support __atomic builtins on
>>>> ints. How it is even possible? That's a portable API provided by
>>>> recent gcc's...
>>>
>>>
>>>
>>> The built-in function is always defined, but it might expand to a call
>>> to an external function in libatomic, and it would be a regression for
>>> code using std::string to start requiring libatomic (although maybe it
>>> would be necessary if it's the only way to make the code correct).
>>>
>>> I don't know if there are any targets that define __GTHREADS and also
>>> don't support __atomic_load(int*, ...) without libatomic. If such
>>> targets exist then adding a new configure check that only depends on
>>> __atomic_load(int*, ...) would mean we keep supporting those targets.
>>>
>>> Another option would be to simply do:
>>>
>>> bool
>>> _M_is_shared() const _GLIBCXX_NOEXCEPT
>>> #if defined(__GTHREADS)
>>> + { return __atomic_load(&this->_M_refcount, __ATOMIC_ACQUIRE) >
>>> 0; }
>>> +#else
>>> { return this->_M_refcount > 0; }
>>> +#endif
>>>
>>> and see if anyone complains!
>>
>>
>> I like this option!
>> If a platform uses multithreading and has non-inlined atomic loads,
>> then the way to fix this is to provide inlined atomic loads rather
>> than to fix all call sites.
>>
>> Attaching new patch. Please take another look.
>
>
> This looks good. Torvald suggested that it would be useful to add a
> similar comment to the release operation in _M_dispose, so that both
> sides of the release-acquire are similarly documented. Could you add
> that and provide a suitable ChangeLog entry?
>
> Thanks!
>
>
>> Index: include/bits/basic_string.h
>> ===================================================================
>> --- include/bits/basic_string.h (revision 227363)
>> +++ include/bits/basic_string.h (working copy)
>> @@ -2601,11 +2601,32 @@
>>
>> bool
>> _M_is_leaked() const _GLIBCXX_NOEXCEPT
>> - { return this->_M_refcount < 0; }
>> + {
>> +#if defined(__GTHREADS)
>> + // _M_refcount is mutated concurrently by
>> _M_refcopy/_M_dispose,
>> + // so we need to use an atomic load. However, _M_is_leaked
>> + // predicate does not change concurrently (i.e. the string is
>> either
>> + // leaked or not), so a relaxed load is enough.
>> + return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) <
>> 0;
>> +#else
>> + return this->_M_refcount < 0;
>> +#endif
>> + }
>>
>> bool
>> _M_is_shared() const _GLIBCXX_NOEXCEPT
>> - { return this->_M_refcount > 0; }
>> + {
>> +#if defined(__GTHREADS)
>> + // _M_refcount is mutated concurrently by
>> _M_refcopy/_M_dispose,
>> + // so we need to use an atomic load. Another thread can drop
>> last
>> + // but one reference concurrently with this check, so we need
>> this
>> + // load to be acquire to synchronize with release fetch_and_add
>> in
>> + // _M_dispose.
>> + return __atomic_load_n(&this->_M_refcount, __ATOMIC_ACQUIRE) >
>> 0;
>> +#else
>> + return this->_M_refcount > 0;
>> +#endif
>> + }
>>
>> void
>> _M_set_leaked() _GLIBCXX_NOEXCEPT
>
>
[-- Attachment #2: patch.diff --]
[-- Type: text/plain, Size: 2634 bytes --]
Index: ChangeLog
===================================================================
--- ChangeLog (revision 227400)
+++ ChangeLog (working copy)
@@ -1,3 +1,7 @@
+2015-09-02 Dmitry Vyukov <dvyukov@google.com>
+
+ * include/bits/basic_string.h: Fix data races on _M_refcount.
+
2015-09-02 Sebastian Huber <sebastian.huber@embedded-brains.de>
PR libstdc++/67408
Index: include/bits/basic_string.h
===================================================================
--- include/bits/basic_string.h (revision 227400)
+++ include/bits/basic_string.h (working copy)
@@ -2601,11 +2601,32 @@
bool
_M_is_leaked() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount < 0; }
+ {
+#if defined(__GTHREADS)
+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
+ // so we need to use an atomic load. However, _M_is_leaked
+ // predicate does not change concurrently (i.e. the string is either
+ // leaked or not), so a relaxed load is enough.
+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_RELAXED) < 0;
+#else
+ return this->_M_refcount < 0;
+#endif
+ }
bool
_M_is_shared() const _GLIBCXX_NOEXCEPT
- { return this->_M_refcount > 0; }
+ {
+#if defined(__GTHREADS)
+ // _M_refcount is mutated concurrently by _M_refcopy/_M_dispose,
+ // so we need to use an atomic load. Another thread can drop last
+ // but one reference concurrently with this check, so we need this
+ // load to be acquire to synchronize with release fetch_and_add in
+ // _M_dispose.
+ return __atomic_load_n(&this->_M_refcount, __ATOMIC_ACQUIRE) > 0;
+#else
+ return this->_M_refcount > 0;
+#endif
+ }
void
_M_set_leaked() _GLIBCXX_NOEXCEPT
@@ -2654,6 +2675,14 @@
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&this->_M_refcount);
+ // Decrement of _M_refcount is acq_rel, because:
+ // - all but last decrements need to release to synchronize with
+ // the last decrement that will delete the object.
+ // - the last decrement needs to acquire to synchronize with
+ // all the previous decrements.
+ // - last but one decrement needs to release to synchronize with
+ // the acquire load in _M_is_shared that will conclude that
+ // the object is not shared anymore.
if (__gnu_cxx::__exchange_and_add_dispatch(&this->_M_refcount,
-1) <= 0)
{
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-02 13:50 ` Dmitry Vyukov
@ 2015-09-02 14:05 ` Jonathan Wakely
0 siblings, 0 replies; 12+ messages in thread
From: Jonathan Wakely @ 2015-09-02 14:05 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: libstdc++, GCC Patches, Alexander Potapenko, Kostya Serebryany
On 02/09/15 15:49 +0200, Dmitry Vyukov wrote:
>On Wed, Sep 2, 2015 at 12:58 PM, Marc Glisse <marc.glisse@inria.fr> wrote:
>> On Tue, 1 Sep 2015, Dmitry Vyukov wrote:
>>
>>> The refcounted basic_string implementation contains several data races
>>> on _M_refcount:
>>
>>
>> There are several bug reports about races in basic_string in bugzilla (some
>> might even have been closed as wontfix because of the new implementation).
>> Does this also fix some of them?
>
>I've tried to search for "basic_string race" with all statuses:
>https://gcc.gnu.org/bugzilla/buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=SUSPENDED&bug_status=WAITING&bug_status=REOPENED&bug_status=RESOLVED&bug_status=VERIFIED&bug_status=CLOSED&cf_known_to_fail_type=allwords&cf_known_to_work_type=allwords&component=libstdc%2B%2B&list_id=125385&query_format=advanced&short_desc=basic_string%20race&short_desc_type=allwordssubstr
>
>But it does not yield anything interesting. What bugs can I reference?
There's https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21334 but I don't
think this fixes it. That's a race condition in the logic, not a
formal data race due to non-atomic operations like the problem being
fixed here.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-02 14:02 ` Dmitry Vyukov
@ 2015-09-02 14:08 ` Jonathan Wakely
2015-09-02 14:39 ` Dmitry Vyukov
0 siblings, 1 reply; 12+ messages in thread
From: Jonathan Wakely @ 2015-09-02 14:08 UTC (permalink / raw)
To: Dmitry Vyukov
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
On 02/09/15 16:01 +0200, Dmitry Vyukov wrote:
>Added comment to _M_dispose and restored ChangeLog entry.
>Please take another look.
Thanks, this is OK for trunk.
I assume you are covered by the Google company-wide copyright
assignment, so someone just needs to commit it, which I can do if you
like.
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [Patch, libstdc++] Fix data races in basic_string implementation
2015-09-02 14:08 ` Jonathan Wakely
@ 2015-09-02 14:39 ` Dmitry Vyukov
0 siblings, 0 replies; 12+ messages in thread
From: Dmitry Vyukov @ 2015-09-02 14:39 UTC (permalink / raw)
To: Jonathan Wakely
Cc: GCC Patches, libstdc++,
Alexander Potapenko, Kostya Serebryany, Torvald Riegel
Thank you.
Yes, I am covered by the Google copyright assignment, and I have commit access.
I don't commit frequently, hope I didn't mess up things fundamentally :)
https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=227403
On Wed, Sep 2, 2015 at 4:08 PM, Jonathan Wakely <jwakely@redhat.com> wrote:
> On 02/09/15 16:01 +0200, Dmitry Vyukov wrote:
>>
>> Added comment to _M_dispose and restored ChangeLog entry.
>> Please take another look.
>
>
> Thanks, this is OK for trunk.
>
> I assume you are covered by the Google company-wide copyright
> assignment, so someone just needs to commit it, which I can do if you
> like.
>
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2015-09-02 14:39 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-09-01 12:52 [Patch, libstdc++] Fix data races in basic_string implementation Dmitry Vyukov
2015-09-01 14:27 ` Jonathan Wakely
2015-09-01 14:56 ` Dmitry Vyukov
2015-09-01 15:08 ` Jonathan Wakely
2015-09-01 15:42 ` Dmitry Vyukov
2015-09-02 13:17 ` Jonathan Wakely
2015-09-02 14:02 ` Dmitry Vyukov
2015-09-02 14:08 ` Jonathan Wakely
2015-09-02 14:39 ` Dmitry Vyukov
2015-09-02 10:58 ` Marc Glisse
2015-09-02 13:50 ` Dmitry Vyukov
2015-09-02 14:05 ` Jonathan Wakely
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).