From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail-ed1-x52e.google.com (mail-ed1-x52e.google.com [IPv6:2a00:1450:4864:20::52e]) by sourceware.org (Postfix) with ESMTPS id F0EFA385B516 for ; Wed, 9 Aug 2023 12:38:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org F0EFA385B516 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-ed1-x52e.google.com with SMTP id 4fb4d7f45d1cf-51e2a6a3768so9349645a12.0 for ; Wed, 09 Aug 2023 05:38:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20221208; t=1691584706; x=1692189506; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=TiICMz3fCI3tKQtwSCLX73sJpCUXY65qhM9CTPMIJII=; b=kqmaATaKI/Dw/twcORvkV0uLF8VIV4EBthmDJbM7QcSzoHi1vdKT+LYL9luYsRDwWK ncr4+WlCjR0IVjujOQf6HouPIw+xVsfDOPK/9+YEJovOfUJNOSbPzah3LjNdmKk/qOnU RxnmFp7sl1XpwPBwKRhiLgx/VmLniErjRisHudn/r5rWyyHdvBNyOFpioi+lwAJGh2K7 C72/gUhIyDqyEjaLcuP1t/JTKthFVH1NNRJuc9ZsOyqmo4J5Uwqw/Dx44ndZWgxfuW7a krt7THtJTvETZw+pMc5Ow5lr8roWL+BAZAnbCO0C+/OGmG3A3OrQYMZS+jcxNwdx0VKA +fRQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1691584706; x=1692189506; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=TiICMz3fCI3tKQtwSCLX73sJpCUXY65qhM9CTPMIJII=; b=Md5X8zNVyOz+Pw6qjfFVzjskk9ToCMNG7rjccGaKNPYaLAr74dN+BzuefjNsz1PaJr iwpu2tbdeAMDKWSzaBBWNJLT72Sdi5eegfOOx+qk+OvIjgxyFNB0qspXLiz1iZyTtr0/ pRlcEQyCzqV6SHTUXag9EsBNkWsReQICVbGPAaDXGzdeIxcdoBGJyPuB63HdMLnraqX6 E58LSpXgadIIccwiIUEr7eIprMxnsEN3a/f2N09jbSxc1NYj19TbhXFhIUwEcjzejVEL cl1dusJAO13K8PGtHOkQNqyeM2xhKHoYV7HeEWcFrou0XgCdryFcFgpPXmwIhIgYEGL4 m5Gg== X-Gm-Message-State: AOJu0YzwV57rZtYRAe0zwXJJNGucYqCZvN58b0LR8DBPy6wIXTURJc0f IS20hB73UGbo/Dew8tq/kDCNxTXjdp+ygJZMBuQ= X-Google-Smtp-Source: AGHT+IHatxBGQbLZvbt/+bo1kjnkCEmxKAQQCGe4ykd+awXlhvZeghBXVdLmXWc9qWr+wQw/tMsdR1wzUBLekrY70YA= X-Received: by 2002:a17:906:9bca:b0:99b:cc2f:c47c with SMTP id de10-20020a1709069bca00b0099bcc2fc47cmr1941983ejc.53.1691584706359; Wed, 09 Aug 2023 05:38:26 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Jonathan Wakely Date: Wed, 9 Aug 2023 13:38:15 +0100 Message-ID: Subject: Re: [RFA/C] Reimplementation of std::call_once. To: Iain Sandoe Cc: libstdc++@gcc.gnu.org Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-0.5 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS,TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: On Wed, 9 Aug 2023 at 13:22, Iain Sandoe wrote: > > Hello. > > With the current [posix-y] implementation of std::call_once, we are seein= g significant issues for Darwin. The issues vary in strength from =E2=80=9C= hangs 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 fro= m 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 pla= ying 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, a= t 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 ... > (c) ..or if I should figure out how to make this a darwin-specific impl. It's an ABI break. I already tried to replace std::call_once once, see r11-4691-g93e79ed391b9c6 and r11-7688-g6ee24638ed0ad5 and PR 99341. > > =E2=80=94=E2=80=94 here is the prototype implementation as a non-patch .= . (patch attached). > > 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 f= lag > 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 arg= s. > 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 addr= ess. > void (*__oc_tramp)(void*) > =3D [] (void *ca) { (*static_cast(ca))(); }; > // Attempt to do it and synchronize with any other threads that are a= lso > // 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 complete= d > // 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 woul= d > // correspond to an exception throw by the previous thread that tried t= o > // 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 possibl= e > // to trigger a sequence of events where wake-ups are lost - unless= the > // mutex associated with the condition var is locked around the rel= evant > // 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 > the implementation in mutex.cc can sit togethe with the old version so th= at the symbols for that remain available (or, for versioned libraries, the = old code can be deleted). If you have old and new code sharing a std::once_flag object you're dead. Something like the abi_tag transition for std::string in GCC 5 would be nee= ded.