From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp001-out.apm-internet.net (smtp001-out.apm-internet.net [85.119.248.222]) by sourceware.org (Postfix) with ESMTPS id 8DD713857B98 for ; Wed, 9 Aug 2023 12:21:31 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 8DD713857B98 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=sandoe.co.uk Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=sandoe.co.uk Received: (qmail 67857 invoked from network); 9 Aug 2023 12:21:29 -0000 X-APM-Out-ID: 16915836896785 X-APM-Authkey: 257869/1(257869/1) 2 Received: from unknown (HELO smtpclient.apple) (81.138.1.83) by smtp001.apm-internet.net with SMTP; 9 Aug 2023 12:21:29 -0000 From: Iain Sandoe Content-Type: multipart/mixed; boundary="Apple-Mail=_64B72F7C-5929-466B-A0DA-9B74D2977189" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3696.120.41.1.4\)) Subject: [RFA/C] Reimplementation of std::call_once. Message-Id: Date: Wed, 9 Aug 2023 13:21:29 +0100 To: libstdc++@gcc.gnu.org X-Mailer: Apple Mail (2.3696.120.41.1.4) X-Spam-Status: No, score=-2.4 required=5.0 tests=BAYES_00,KAM_COUK,KAM_DMARC_STATUS,RCVD_IN_DNSWL_LOW,SPF_HELO_NONE,SPF_PASS,TXREP autolearn=no autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: --Apple-Mail=_64B72F7C-5929-466B-A0DA-9B74D2977189 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Hello. With the current [posix-y] implementation of std::call_once, we are = seeing significant issues for Darwin. The issues vary in strength from = =E2=80=9Changs or segvs sometimes depending on optimisation and the day = of the week=E2=80=9D (newer OS, faster h/w) to =E2=80=9Csegvs or = std::terminates every time=E2=80=9D older OS slower h/w. Actually, I think that in the presence of exceptions it is not working = on Linux either. As a use-case: this problem prevents us from building working LLVM/clang = tools on Darwin using GCC as the bootstrap compiler. the test code I am using is: https://godbolt.org/z/Mxcnv9YEx (derived = from a cppreference example, IIRC). =E2=80=94=E2=80=94 So I was investigating what/why there might be issues and... * ISTM that using the underlying pthread_once implementation is not = playing at all well with exceptions (since pthread_once is oblivious to = such). * the current implementation cannot support nested cases. =E2=80=94=E2=80=94 Here is a possible replacement implementation with some advantages: * does not require TLS, it is all stack-based. * does not use global state (so it can support nested cases). * it actually works reliably in real use-cases (on old and new Darwin, = at least). And one immediately noted disadvantage: * that once_flag is significantly larger with this implementation = (since it gains a mutex and a condition var). ----- This is a prototype, so I=E2=80=99m looking for advice/comment on: (a) if this can be generalised to be part of libstdc++ (b) what that would take ...=20 (c) ..or if I should figure out how to make this a darwin-specific = impl. =E2=80=94=E2=80=94 here is the prototype implementation as a non-patch = .. (patch attached). =20 mutex: #ifdef _GLIBCXX_HAS_GTHREADS /// Flag type used by std::call_once struct once_flag { /// Constructor constexpr once_flag() noexcept : _M_state_(0), _M_mutx_(__GTHREAD_MUTEX_INIT), _M_condv_(__GTHREAD_COND_INIT) {} void __do_call_once(void (*)(void*), void*); template friend void call_once(once_flag& __once, _Callable&& __f, _Args&&... __args); private: __gthread_mutex_t _M_mutx_; __gthread_cond_t _M_condv_; // call state: 0 =3D init, 1 =3D someone is trying, 2 =3D done. atomic_uint _M_state_; /// Deleted copy constructor once_flag(const once_flag&) =3D delete; /// Deleted assignment operator once_flag& operator=3D(const once_flag&) =3D delete; }; /// Invoke a callable and synchronize with other calls using the same = flag template void call_once (once_flag& __flag, _Callable&& __f, _Args&&... __args) { if (__flag._M_state_.load (std::memory_order_acquire) =3D=3D 2) return; // Closure type that runs the original function with the supplied = args. auto __callable =3D [&] { std::__invoke(std::forward<_Callable>(__f), std::forward<_Args>(__args)...); }; // Trampoline to call the actual fn; we will pass in the closure = address. void (*__oc_tramp)(void*) =3D [] (void *ca) { (*static_cast(ca))(); = }; // Attempt to do it and synchronize with any other threads that are = also // trying. __flag.__do_call_once (__oc_tramp, std::__addressof(__callable)); } #else // _GLIBCXX_HAS_GTHREADS =E2=80=94=E2=80=94 mutex.cc: // This calls the trampoline lambda, passing the address of the closure // repesenting the original function and its arguments. void once_flag::__do_call_once (void (*func)(void*), void *arg) { __gthread_mutex_lock(&_M_mutx_); while (this->_M_state_.load (std::memory_order_relaxed) =3D=3D 1) __gthread_cond_wait(&_M_condv_, &_M_mutx_); // mutex locked, the most likely outcome is that the once-call = completed // on some other thread, so we are done. if (_M_state_.load (std::memory_order_acquire) =3D=3D 2) { __gthread_mutex_unlock(&_M_mutx_); return; } // mutex locked; if we get here, we expect the state to be 0, this = would // correspond to an exception throw by the previous thread that tried = to // do the once_call. __glibcxx_assert (_M_state_.load (std::memory_order_acquire) =3D=3D = 0); try { // mutex locked. _M_state_.store (1, std::memory_order_relaxed); __gthread_mutex_unlock (&_M_mutx_); func (arg); // We got here without an exception, so the call is done. // If the underlying implementation is pthreads, then it is = possible // to trigger a sequence of events where wake-ups are lost - = unless the // mutex associated with the condition var is locked around the = relevant // broadcast (or signal). __gthread_mutex_lock(&_M_mutx_); _M_state_.store (2, std::memory_order_release); __gthread_cond_broadcast (&_M_condv_); __gthread_mutex_unlock (&_M_mutx_); } catch (...) { // mutex unlocked. // func raised an exception, let someone else try ... // See above. __gthread_mutex_lock(&_M_mutx_); _M_state_.store (0, std::memory_order_release); __gthread_cond_broadcast (&_M_condv_); __gthread_mutex_unlock (&_M_mutx_); // ... and pass the exeception to our caller. throw; } } =3D=3D=3D=3D=3D=20 the implementation in mutex.cc can sit togethe with the old version so = that the symbols for that remain available (or, for versioned libraries, = the old code can be deleted). thanks Iain --Apple-Mail=_64B72F7C-5929-466B-A0DA-9B74D2977189 Content-Disposition: attachment; filename=0001-libstdc-Possible-reimplementation-of-std-call_once.patch Content-Type: application/octet-stream; x-unix-mode=0644; name="0001-libstdc-Possible-reimplementation-of-std-call_once.patch" Content-Transfer-Encoding: quoted-printable =46rom=205b08bec16094cd6937e8b8473f24289618adcfc2=20Mon=20Sep=2017=20= 00:00:00=202001=0AFrom:=20Iain=20Sandoe=20=0ADate:=20= Wed,=209=20Aug=202023=2012:13:54=20+0100=0ASubject:=20[PATCH]=20= libstdc++:=20Possible=20reimplementation=20of=20std::call_once.=0A=0A= This=20reimplements=20std::call_once=20using=20a=20stack-based=20= approach=20which=0Adoes=20not=20depend=20on=20pthread_once=20for=20its=20= action.=0A=0ASigned-off-by:=20Iain=20Sandoe=20=0A=0A= libstdc++-v3/ChangeLog:=0A=0A=09*=20config/abi/pre/gnu.ver:=20Export=20= once_flag::__do_call_once.=0A=09*=20include/std/mutex:=20Reimplement=20= std::call_once=20for=20threaded=20cases.=0A=09*=20src/c++11/mutex.cc=20= (once_flag::__do_call_once):=20New.=0A---=0A=20= libstdc++-v3/config/abi/pre/gnu.ver=20|=20=20=202=20+=0A=20= libstdc++-v3/include/std/mutex=20=20=20=20=20=20|=20127=20= +++++++---------------------=0A=20libstdc++-v3/src/c++11/mutex.cc=20=20=20= =20=20|=20=2060=20++++++++++++-=0A=203=20files=20changed,=2092=20= insertions(+),=2097=20deletions(-)=0A=0Adiff=20--git=20= a/libstdc++-v3/config/abi/pre/gnu.ver=20= b/libstdc++-v3/config/abi/pre/gnu.ver=0Aindex=20a2e5f3b4e74..c34058d5ed7=20= 100644=0A---=20a/libstdc++-v3/config/abi/pre/gnu.ver=0A+++=20= b/libstdc++-v3/config/abi/pre/gnu.ver=0A@@=20-1315,6=20+1315,8=20@@=20= GLIBCXX_3.4.11=20{=0A=20=20=20=20=20_ZNKSt10lock_error4whatEv;=0A=20=0A=20= =20=20=20=20_ZSt11__once_call;=0A+=20=20=20=20= _ZNSt9once_flag14__do_call_onceEPFvPvES0_;=0A+=20=20=20=20#=20old=20= implementation=0A=20=20=20=20=20_ZSt15__once_callable;=0A=20=20=20=20=20= _ZSt14__once_functor;=0A=20=20=20=20=20_ZSt23__get_once_functor_lockv;=0A= diff=20--git=20a/libstdc++-v3/include/std/mutex=20= b/libstdc++-v3/include/std/mutex=0Aindex=202b0059fcfe8..f47fe9e9459=20= 100644=0A---=20a/libstdc++-v3/include/std/mutex=0A+++=20= b/libstdc++-v3/include/std/mutex=0A@@=20-50,6=20+50,7=20@@=0A=20#=20= include=20=0A=20#=20include=20=0A=20#endif=0A= +#include=20=0A=20#include=20=20=20=20=20=20//=20= __gnu_cxx::__is_single_threaded=0A=20=0A=20#if=20defined=20= _GLIBCXX_HAS_GTHREADS=20&&=20!=20defined=20_GLIBCXX_HAVE_TLS=0A@@=20= -796,119=20+797,53=20@@=20_GLIBCXX_BEGIN_NAMESPACE_VERSION=0A=20#endif=20= //=20C++17=0A=20=0A=20#ifdef=20_GLIBCXX_HAS_GTHREADS=0A+=0A=20=20=20///=20= Flag=20type=20used=20by=20std::call_once=0A=20=20=20struct=20once_flag=0A= =20=20=20{=0A-=20=20=20=20constexpr=20once_flag()=20noexcept=20=3D=20= default;=0A-=0A-=20=20=20=20///=20Deleted=20copy=20constructor=0A-=20=20=20= =20once_flag(const=20once_flag&)=20=3D=20delete;=0A-=20=20=20=20///=20= Deleted=20assignment=20operator=0A-=20=20=20=20once_flag&=20= operator=3D(const=20once_flag&)=20=3D=20delete;=0A-=0A-=20=20private:=0A= -=20=20=20=20//=20For=20gthreads=20targets=20a=20pthread_once_t=20is=20= used=20with=20pthread_once,=20but=0A-=20=20=20=20//=20for=20most=20= targets=20this=20doesn't=20work=20correctly=20for=20exceptional=20= executions.=0A-=20=20=20=20__gthread_once_t=20_M_once=20=3D=20= __GTHREAD_ONCE_INIT;=0A+=20=20=20=20///=20Constructor=0A+=20=20=20=20= constexpr=20once_flag()=20=20noexcept=0A+=20=20=20=20:=20_M_state_(0),=20= _M_mutx_(__GTHREAD_MUTEX_INIT),=0A+=20=20=20=20=20=20= _M_condv_(__GTHREAD_COND_INIT)=20{}=0A=20=0A-=20=20=20=20struct=20= _Prepare_execution;=0A+=20=20=20=20void=20__do_call_once(void=20= (*)(void*),=20void*);=0A=20=0A=20=20=20=20=20template=0A=20=20=20=20=20=20=20friend=20void=0A= =20=20=20=20=20=20=20call_once(once_flag&=20__once,=20_Callable&&=20__f,=20= _Args&&...=20__args);=0A-=20=20};=0A-=0A-=20=20///=20@cond=20= undocumented=0A-#=20ifdef=20_GLIBCXX_HAVE_TLS=0A-=20=20//=20If=20TLS=20= is=20available=20use=20thread-local=20state=20for=20the=20type-erased=20= callable=0A-=20=20//=20that=20is=20being=20run=20by=20std::call_once=20= in=20the=20current=20thread.=0A-=20=20extern=20__thread=20void*=20= __once_callable;=0A-=20=20extern=20__thread=20void=20(*__once_call)();=0A= -=0A-=20=20//=20RAII=20type=20to=20set=20up=20state=20for=20pthread_once=20= call.=0A-=20=20struct=20once_flag::_Prepare_execution=0A-=20=20{=0A-=20=20= =20=20template=0A-=20=20=20=20=20=20explicit=0A-=20= =20=20=20=20=20_Prepare_execution(_Callable&=20__c)=0A-=20=20=20=20=20=20= {=0A-=09//=20Store=20address=20in=20thread-local=20pointer:=0A-=09= __once_callable=20=3D=20std::__addressof(__c);=0A-=09//=20Trampoline=20= function=20to=20invoke=20the=20closure=20via=20thread-local=20pointer:=0A= -=09__once_call=20=3D=20[]=20{=20= (*static_cast<_Callable*>(__once_callable))();=20};=0A-=20=20=20=20=20=20= }=0A-=0A-=20=20=20=20~_Prepare_execution()=0A-=20=20=20=20{=0A-=20=20=20=20= =20=20//=20PR=20libstdc++/82481=0A-=20=20=20=20=20=20__once_callable=20=3D= =20nullptr;=0A-=20=20=20=20=20=20__once_call=20=3D=20nullptr;=0A-=20=20=20= =20}=0A-=0A-=20=20=20=20_Prepare_execution(const=20_Prepare_execution&)=20= =3D=20delete;=0A-=20=20=20=20_Prepare_execution&=20operator=3D(const=20= _Prepare_execution&)=20=3D=20delete;=0A-=20=20};=0A-=0A-#=20else=0A-=20=20= //=20Without=20TLS=20use=20a=20global=20std::mutex=20and=20store=20the=20= callable=20in=20a=0A-=20=20//=20global=20std::function.=0A-=20=20extern=20= function=20__once_functor;=0A-=0A-=20=20extern=20void=0A-=20=20= __set_once_functor_lock_ptr(unique_lock*);=0A-=0A-=20=20extern=20= mutex&=0A-=20=20__get_once_mutex();=0A-=0A-=20=20//=20RAII=20type=20to=20= set=20up=20state=20for=20pthread_once=20call.=0A-=20=20struct=20= once_flag::_Prepare_execution=0A-=20=20{=0A-=20=20=20=20= template=0A-=20=20=20=20=20=20explicit=0A-=20=20=20= =20=20=20_Prepare_execution(_Callable&=20__c)=0A-=20=20=20=20=20=20{=0A-=09= //=20Store=20the=20callable=20in=20the=20global=20std::function=0A-=09= __once_functor=20=3D=20__c;=0A-=09= __set_once_functor_lock_ptr(&_M_functor_lock);=0A-=20=20=20=20=20=20}=0A= -=0A-=20=20=20=20~_Prepare_execution()=0A-=20=20=20=20{=0A-=20=20=20=20=20= =20if=20(_M_functor_lock)=0A-=09__set_once_functor_lock_ptr(nullptr);=0A= -=20=20=20=20}=0A=20=0A=20=20=20private:=0A-=20=20=20=20//=20XXX=20This=20= deadlocks=20if=20used=20recursively=20(PR=2097949)=0A-=20=20=20=20= unique_lock=20_M_functor_lock{__get_once_mutex()};=0A+=20=20=20=20= __gthread_mutex_t=20_M_mutx_;=0A+=20=20=20=20__gthread_cond_t=20= _M_condv_;=0A+=20=20=20=20//=20call=20state:=200=20=3D=20init,=201=20=3D=20= someone=20is=20trying,=202=20=3D=20done.=0A+=20=20=20=20atomic_uint=20= _M_state_;=0A=20=0A-=20=20=20=20_Prepare_execution(const=20= _Prepare_execution&)=20=3D=20delete;=0A-=20=20=20=20_Prepare_execution&=20= operator=3D(const=20_Prepare_execution&)=20=3D=20delete;=0A+=20=20=20=20= ///=20Deleted=20copy=20constructor=0A+=20=20=20=20once_flag(const=20= once_flag&)=20=3D=20delete;=0A+=20=20=20=20///=20Deleted=20assignment=20= operator=0A+=20=20=20=20once_flag&=20operator=3D(const=20once_flag&)=20=3D= =20delete;=0A=20=20=20};=0A-#=20endif=0A-=20=20///=20@endcond=0A-=0A-=20=20= //=20This=20function=20is=20passed=20to=20pthread_once=20by=20= std::call_once.=0A-=20=20//=20It=20runs=20__once_call()=20or=20= __once_functor().=0A-=20=20extern=20"C"=20void=20__once_proxy(void);=0A=20= =0A=20=20=20///=20Invoke=20a=20callable=20and=20synchronize=20with=20= other=20calls=20using=20the=20same=20flag=0A=20=20=20template=0A-=20=20=20=20void=0A-=20=20=20=20= call_once(once_flag&=20__once,=20_Callable&&=20__f,=20_Args&&...=20= __args)=0A-=20=20=20=20{=0A-=20=20=20=20=20=20//=20Closure=20type=20that=20= runs=20the=20function=0A-=20=20=20=20=20=20auto=20__callable=20=3D=20[&]=20= {=0A+=20=20void=0A+=20=20call_once=20(once_flag&=20__flag,=20_Callable&&=20= __f,=20_Args&&...=20__args)=0A+=20=20{=0A+=20=20=20=20if=20= (__flag._M_state_.load=20(std::memory_order_acquire)=20=3D=3D=202)=0A+=20= =20=20=20=20=20return;=0A+=0A+=20=20=20=20//=20Closure=20type=20that=20= runs=20the=20original=20function=20with=20the=20supplied=20args.=0A+=20=20= =20=20auto=20__callable=20=3D=20[&]=20{=0A=20=09=20=20= std::__invoke(std::forward<_Callable>(__f),=0A=20=09=09=09= std::forward<_Args>(__args)...);=0A-=20=20=20=20=20=20};=0A-=0A-=20=20=20= =20=20=20once_flag::_Prepare_execution=20__exec(__callable);=0A-=0A-=20=20= =20=20=20=20//=20XXX=20pthread_once=20does=20not=20reset=20the=20flag=20= if=20an=20exception=20is=20thrown.=0A-=20=20=20=20=20=20if=20(int=20__e=20= =3D=20__gthread_once(&__once._M_once,=20&__once_proxy))=0A-=09= __throw_system_error(__e);=0A-=20=20=20=20}=0A+=20=20=20=20};=0A+=20=20=20= =20//=20Trampoline=20to=20call=20the=20actual=20fn;=20we=20will=20pass=20= in=20the=20closure=20address.=0A+=20=20=20=20void=20(*__oc_tramp)(void*)=0A= +=20=20=20=20=20=20=3D=20[]=20(void=20*ca)=20{=20= (*static_cast(ca))();=20};=0A+=20=20=20=20//=20= Attempt=20to=20do=20it=20and=20synchronize=20with=20any=20other=20= threads=20that=20are=20also=0A+=20=20=20=20//=20trying.=0A+=20=20=20=20= __flag.__do_call_once=20(__oc_tramp,=20std::__addressof(__callable));=0A= +}=0A=20=0A=20#else=20//=20_GLIBCXX_HAS_GTHREADS=0A=20=0Adiff=20--git=20= a/libstdc++-v3/src/c++11/mutex.cc=20b/libstdc++-v3/src/c++11/mutex.cc=0A= index=201b1caa3bf0f..948a4b1b387=20100644=0A---=20= a/libstdc++-v3/src/c++11/mutex.cc=0A+++=20= b/libstdc++-v3/src/c++11/mutex.cc=0A@@=20-22,6=20+22,7=20@@=0A=20//=20= see=20the=20files=20COPYING3=20and=20COPYING.RUNTIME=20respectively.=20=20= If=20not,=20see=0A=20//=20.=0A=20=0A= +#include=20=0A=20#include=20=0A=20=0A=20#ifdef=20= _GLIBCXX_HAS_GTHREADS=0A@@=20-30,6=20+31,63=20@@=20namespace=20std=20= _GLIBCXX_VISIBILITY(default)=0A=20{=0A=20= _GLIBCXX_BEGIN_NAMESPACE_VERSION=0A=20=0A+//=20This=20calls=20the=20= trampoline=20lambda,=20passing=20the=20address=20of=20the=20closure=0A= +//=20repesenting=20the=20original=20function=20and=20its=20arguments.=0A= +void=0A+once_flag::__do_call_once=20(void=20(*func)(void*),=20void=20= *arg)=0A+{=0A+=20=20__gthread_mutex_lock(&_M_mutx_);=0A+=20=20while=20= (this->_M_state_.load=20(std::memory_order_relaxed)=20=3D=3D=201)=0A+=20=20= =20=20__gthread_cond_wait(&_M_condv_,=20&_M_mutx_);=0A+=0A+=20=20//=20= mutex=20locked,=20the=20most=20likely=20outcome=20is=20that=20the=20= once-call=20completed=0A+=20=20//=20on=20some=20other=20thread,=20so=20= we=20are=20done.=0A+=20=20if=20(_M_state_.load=20= (std::memory_order_acquire)=20=3D=3D=202)=0A+=20=20=20=20{=0A+=20=20=20=20= =20=20__gthread_mutex_unlock(&_M_mutx_);=0A+=20=20=20=20=20=20return;=0A= +=20=20=20=20}=0A+=0A+=20=20//=20mutex=20locked;=20if=20we=20get=20here,=20= we=20expect=20the=20state=20to=20be=200,=20this=20would=0A+=20=20//=20= correspond=20to=20an=20exception=20throw=20by=20the=20previous=20thread=20= that=20tried=20to=0A+=20=20//=20do=20the=20once_call.=0A+=20=20= __glibcxx_assert=20(_M_state_.load=20(std::memory_order_acquire)=20=3D=3D=20= 0);=0A+=0A+=20=20try=0A+=20=20=20=20{=0A+=20=20=20=20=20=20//=20mutex=20= locked.=0A+=20=20=20=20=20=20_M_state_.store=20(1,=20= std::memory_order_relaxed);=0A+=20=20=20=20=20=20__gthread_mutex_unlock=20= (&_M_mutx_);=0A+=20=20=20=20=20=20func=20(arg);=0A+=20=20=20=20=20=20//=20= We=20got=20here=20without=20an=20exception,=20so=20the=20call=20is=20= done.=0A+=20=20=20=20=20=20//=20If=20the=20underlying=20implementation=20= is=20pthreads,=20then=20it=20is=20possible=0A+=20=20=20=20=20=20//=20to=20= trigger=20a=20sequence=20of=20events=20where=20wake-ups=20are=20lost=20-=20= unless=20the=0A+=20=20=20=20=20=20//=20mutex=20associated=20with=20the=20= condition=20var=20is=20locked=20around=20the=20relevant=0A+=20=20=20=20=20= =20//=20broadcast=20(or=20signal).=0A+=20=20=20=20=20=20= __gthread_mutex_lock(&_M_mutx_);=0A+=20=20=20=20=20=20_M_state_.store=20= (2,=20std::memory_order_release);=0A+=20=20=20=20=20=20= __gthread_cond_broadcast=20(&_M_condv_);=0A+=20=20=20=20=20=20= __gthread_mutex_unlock=20(&_M_mutx_);=0A+=20=20=20=20}=0A+=20=20catch=20= (...)=0A+=20=20=20=20{=0A+=20=20=20=20=20=20//=20mutex=20unlocked.=0A+=20= =20=20=20=20=20//=20func=20raised=20an=20exception,=20let=20someone=20= else=20try=20...=0A+=20=20=20=20=20=20//=20See=20above.=0A+=20=20=20=20=20= =20__gthread_mutex_lock(&_M_mutx_);=0A+=20=20=20=20=20=20_M_state_.store=20= (0,=20std::memory_order_release);=0A+=20=20=20=20=20=20= __gthread_cond_broadcast=20(&_M_condv_);=0A+=20=20=20=20=20=20= __gthread_mutex_unlock=20(&_M_mutx_);=0A+=20=20=20=20=20=20//=20...=20= and=20pass=20the=20exeception=20to=20our=20caller.=0A+=20=20=20=20=20=20= throw;=0A+=20=20=20=20}=0A+}=0A+=0A+#if=20!_GLIBCXX_INLINE_VERSION=0A+=0A= +//=20Unless=20we=20have=20a=20versioned=20library,=20provide=20the=20= symbols=20for=20the=20previous=0A+//=20once=20call=20impl.=0A+=0A=20= #ifdef=20_GLIBCXX_HAVE_TLS=0A=20=20=20__thread=20void*=20= __once_callable;=0A=20=20=20__thread=20void=20(*__once_call)();=0A@@=20= -101,7=20+159,7=20@@=20namespace=0A=20=20=20=20=20callable();=0A=20=20=20= }=0A=20#endif=20//=20!=20TLS=0A-=0A+#endif=20//=20!=20= _GLIBCXX_INLINE_VERSION=0A=20_GLIBCXX_END_NAMESPACE_VERSION=0A=20}=20//=20= namespace=20std=0A=20=0A--=20=0A2.39.2=20(Apple=20Git-143)=0A=0A= --Apple-Mail=_64B72F7C-5929-466B-A0DA-9B74D2977189 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset=us-ascii --Apple-Mail=_64B72F7C-5929-466B-A0DA-9B74D2977189--