diff options
author | Jonathan Wakely <jwakely@redhat.com> | 2021-03-12 11:47:20 +0000 |
---|---|---|
committer | Jonathan Wakely <jwakely@redhat.com> | 2021-03-16 12:25:28 +0000 |
commit | 6ee24638ed0ad51e568c799bacf149ba9bd7628b (patch) | |
tree | 4066b8acc330b4f0f62fc4a1392908988306e160 /libstdc++-v3/include | |
parent | 7b900dca607dceaae2db372365f682a4979c7826 (diff) | |
download | gcc-6ee24638ed0ad51e568c799bacf149ba9bd7628b.tar.gz |
libstdc++: Revert to old std::call_once implementation [PR 99341]
The new std::call_once implementation is not backwards compatible,
contrary to my intention. Because std::once_flag::_M_active() doesn't
write glibc's "fork generation" into the pthread_once_t object, it's
possible for glibc and libstdc++ to run two active executions
concurrently. This violates the primary invariant of the feature!
This patch reverts std::once_flag and std::call_once to the old
implementation that uses pthread_once. This means PR 66146 is a problem
again, but glibc has been changed to solve that. A new API similar to
pthread_once but supporting failure and resetting the pthread_once_t
will be proposed for inclusion in glibc and other C libraries.
This change doesn't simply revert r11-4691 because I want to retain the
new implementation for non-ghtreads targets (which didn't previously
support std::call_once at all, so there's no backwards compatibility
concern). This also leaves the new std::call_once::_M_activate() and
std::call_once::_M_finish(bool) symbols present in libstdc++.so.6 so
that code already compiled against GCC 11 can still use them. Those
symbols will be removed in a subsequent commit (which distros can choose
to temporarily revert if needed).
libstdc++-v3/ChangeLog:
PR libstdc++/99341
* include/std/mutex [_GLIBCXX_HAVE_LINUX_FUTEX] (once_flag):
Revert to pthread_once_t implementation.
[_GLIBCXX_HAVE_LINUX_FUTEX] (call_once): Likewise.
* src/c++11/mutex.cc [_GLIBCXX_HAVE_LINUX_FUTEX]
(struct __once_flag_compat): New type matching the reverted
implementation of once_flag using futexes.
(once_flag::_M_activate): Remove, replace with ...
(_ZNSt9once_flag11_M_activateEv): ... alias symbol.
(once_flag::_M_finish): Remove, replace with ...
(_ZNSt9once_flag9_M_finishEb): ... alias symbol.
* testsuite/30_threads/call_once/66146.cc: Removed.
Diffstat (limited to 'libstdc++-v3/include')
-rw-r--r-- | libstdc++-v3/include/std/mutex | 242 |
1 files changed, 125 insertions, 117 deletions
diff --git a/libstdc++-v3/include/std/mutex b/libstdc++-v3/include/std/mutex index f96c48e88ec..b6a595237bf 100644 --- a/libstdc++-v3/include/std/mutex +++ b/libstdc++-v3/include/std/mutex @@ -669,6 +669,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; #endif // C++17 +#ifdef _GLIBCXX_HAS_GTHREADS /// Flag type used by std::call_once struct once_flag { @@ -680,125 +681,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION once_flag& operator=(const once_flag&) = delete; private: - // There are two different std::once_flag interfaces, abstracting four - // different implementations. - // The preferred interface uses the _M_activate() and _M_finish(bool) - // member functions (introduced in GCC 11), which start and finish an - // active execution respectively. See [thread.once.callonce] in C++11 - // for the definition of active/passive/returning/exceptional executions. - // This interface is supported for Linux (using atomics and futexes) and - // for single-threaded targets with no gthreads support. - // For other targets a pthread_once_t is used with pthread_once, but that - // doesn't work correctly for exceptional executions. That interface - // uses an object of type _Prepare_execution and a lambda expression. -#if defined _GLIBCXX_HAVE_LINUX_FUTEX || ! defined _GLIBCXX_HAS_GTHREADS - enum _Bits : int { _Init = 0, _Active = 1, _Done = 2 }; - - int _M_once = _Bits::_Init; - - // Non-blocking check to see if all executions will be passive now. - bool - _M_passive() const noexcept; - - // Attempts to begin an active execution. Blocks until it either: - // - returns true if an active execution has started on this thread, or - // - returns false if a returning execution happens on another thread. - bool _M_activate(); - - // Must be called to complete an active execution. - // The argument is true if the active execution was a returning execution, - // false if it was an exceptional execution. - void _M_finish(bool __returning) noexcept; - - // RAII helper to call _M_finish. - struct _Active_execution - { - explicit _Active_execution(once_flag& __flag) : _M_flag(__flag) { } - - ~_Active_execution() { _M_flag._M_finish(_M_returning); } - - _Active_execution(const _Active_execution&) = delete; - _Active_execution& operator=(const _Active_execution&) = delete; - - once_flag& _M_flag; - bool _M_returning = false; - }; -#else + // For gthreads targets a pthread_once_t is used with pthread_once, but + // for most targets this doesn't work correctly for exceptional executions. __gthread_once_t _M_once = __GTHREAD_ONCE_INIT; struct _Prepare_execution; -#endif // ! GTHREADS template<typename _Callable, typename... _Args> friend void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args); }; -#if ! defined _GLIBCXX_HAS_GTHREADS - // Inline definitions of std::once_flag members for single-threaded targets. - - inline bool - once_flag::_M_passive() const noexcept - { return _M_once == _Bits::_Done; } - - inline bool - once_flag::_M_activate() - { - if (_M_once == _Bits::_Init) [[__likely__]] - { - _M_once = _Bits::_Active; - return true; - } - else if (_M_passive()) // Caller should have checked this already. - return false; - else - __throw_system_error(EDEADLK); - } - - inline void - once_flag::_M_finish(bool __returning) noexcept - { _M_once = __returning ? _Bits::_Done : _Bits::_Init; } - -#elif defined _GLIBCXX_HAVE_LINUX_FUTEX - - // Define this inline to make passive executions fast. - inline bool - once_flag::_M_passive() const noexcept - { - if (__gnu_cxx::__is_single_threaded()) - return _M_once == _Bits::_Done; - else - return __atomic_load_n(&_M_once, __ATOMIC_ACQUIRE) == _Bits::_Done; - } - -#else // GTHREADS && ! FUTEX - /// @cond undocumented # ifdef _GLIBCXX_HAVE_TLS // If TLS is available use thread-local state for the type-erased callable // that is being run by std::call_once in the current thread. extern __thread void* __once_callable; extern __thread void (*__once_call)(); -# else - // Without TLS use a global std::mutex and store the callable in a - // global std::function. - extern function<void()> __once_functor; - - extern void - __set_once_functor_lock_ptr(unique_lock<mutex>*); - - extern mutex& - __get_once_mutex(); -# endif - - // This function is passed to pthread_once by std::call_once. - // It runs __once_call() or __once_functor(). - extern "C" void __once_proxy(void); // RAII type to set up state for pthread_once call. struct once_flag::_Prepare_execution { -#ifdef _GLIBCXX_HAVE_TLS template<typename _Callable> explicit _Prepare_execution(_Callable& __c) @@ -815,7 +718,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __once_callable = nullptr; __once_call = nullptr; } -#else // ! TLS + + _Prepare_execution(const _Prepare_execution&) = delete; + _Prepare_execution& operator=(const _Prepare_execution&) = delete; + }; + +# else + // Without TLS use a global std::mutex and store the callable in a + // global std::function. + extern function<void()> __once_functor; + + extern void + __set_once_functor_lock_ptr(unique_lock<mutex>*); + + extern mutex& + __get_once_mutex(); + + // RAII type to set up state for pthread_once call. + struct once_flag::_Prepare_execution + { template<typename _Callable> explicit _Prepare_execution(_Callable& __c) @@ -832,21 +753,120 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } private: + // XXX This deadlocks if used recursively (PR 97949) unique_lock<mutex> _M_functor_lock{__get_once_mutex()}; -#endif // ! TLS _Prepare_execution(const _Prepare_execution&) = delete; _Prepare_execution& operator=(const _Prepare_execution&) = delete; }; +# endif /// @endcond -#endif + + // This function is passed to pthread_once by std::call_once. + // It runs __once_call() or __once_functor(). + extern "C" void __once_proxy(void); /// Invoke a callable and synchronize with other calls using the same flag template<typename _Callable, typename... _Args> void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args) { -#if defined _GLIBCXX_HAVE_LINUX_FUTEX || ! defined _GLIBCXX_HAS_GTHREADS + // Closure type that runs the function + auto __callable = [&] { + std::__invoke(std::forward<_Callable>(__f), + std::forward<_Args>(__args)...); + }; + + once_flag::_Prepare_execution __exec(__callable); + + // XXX pthread_once does not reset the flag if an exception is thrown. + if (int __e = __gthread_once(&__once._M_once, &__once_proxy)) + __throw_system_error(__e); + } + +#else // _GLIBCXX_HAS_GTHREADS + + /// Flag type used by std::call_once + struct once_flag + { + constexpr once_flag() noexcept = default; + + /// Deleted copy constructor + once_flag(const once_flag&) = delete; + /// Deleted assignment operator + once_flag& operator=(const once_flag&) = delete; + + private: + // There are two different std::once_flag interfaces, abstracting four + // different implementations. + // The single-threaded interface uses the _M_activate() and _M_finish(bool) + // functions, which start and finish an active execution respectively. + // See [thread.once.callonce] in C++11 for the definition of + // active/passive/returning/exceptional executions. + enum _Bits : int { _Init = 0, _Active = 1, _Done = 2 }; + + int _M_once = _Bits::_Init; + + // Check to see if all executions will be passive now. + bool + _M_passive() const noexcept; + + // Attempts to begin an active execution. + bool _M_activate(); + + // Must be called to complete an active execution. + // The argument is true if the active execution was a returning execution, + // false if it was an exceptional execution. + void _M_finish(bool __returning) noexcept; + + // RAII helper to call _M_finish. + struct _Active_execution + { + explicit _Active_execution(once_flag& __flag) : _M_flag(__flag) { } + + ~_Active_execution() { _M_flag._M_finish(_M_returning); } + + _Active_execution(const _Active_execution&) = delete; + _Active_execution& operator=(const _Active_execution&) = delete; + + once_flag& _M_flag; + bool _M_returning = false; + }; + + template<typename _Callable, typename... _Args> + friend void + call_once(once_flag& __once, _Callable&& __f, _Args&&... __args); + }; + + // Inline definitions of std::once_flag members for single-threaded targets. + + inline bool + once_flag::_M_passive() const noexcept + { return _M_once == _Bits::_Done; } + + inline bool + once_flag::_M_activate() + { + if (_M_once == _Bits::_Init) [[__likely__]] + { + _M_once = _Bits::_Active; + return true; + } + else if (_M_passive()) // Caller should have checked this already. + return false; + else + __throw_system_error(EDEADLK); + } + + inline void + once_flag::_M_finish(bool __returning) noexcept + { _M_once = __returning ? _Bits::_Done : _Bits::_Init; } + + /// Invoke a callable and synchronize with other calls using the same flag + template<typename _Callable, typename... _Args> + inline void + call_once(once_flag& __once, _Callable&& __f, _Args&&... __args) + { if (__once._M_passive()) return; else if (__once._M_activate()) @@ -861,20 +881,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // __f(__args...) did not throw __exec._M_returning = true; } -#else - // Closure type that runs the function - auto __callable = [&] { - std::__invoke(std::forward<_Callable>(__f), - std::forward<_Args>(__args)...); - }; - - once_flag::_Prepare_execution __exec(__callable); - - // XXX pthread_once does not reset the flag if an exception is thrown. - if (int __e = __gthread_once(&__once._M_once, &__once_proxy)) - __throw_system_error(__e); -#endif } +#endif // _GLIBCXX_HAS_GTHREADS // @} group mutexes _GLIBCXX_END_NAMESPACE_VERSION |