public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible
@ 2021-03-02 13:11 redi at gcc dot gnu.org
  2021-03-02 13:12 ` [Bug libstdc++/99341] " redi at gcc dot gnu.org
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: redi at gcc dot gnu.org @ 2021-03-02 13:11 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

            Bug ID: 99341
           Summary: [11 Regression] new std::call_once is not backwards
                    compatible
           Product: gcc
           Version: 11.0
            Status: UNCONFIRMED
          Keywords: ABI
          Severity: blocker
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: redi at gcc dot gnu.org
  Target Milestone: ---
            Target: *-*-*linux*

Creating a new bug for bug 66146 comment 41 increased visibility, and to deal
with it separately from the exception-handling bug that is the topic of bug
66146.

Quoting from there:

The new std::call_once using a futex is not backwards compatible, so I think it
needs to be reverted, or hidden behind an ABI-breaking flag.

The new std::once_flag::_M_activate() function sets _M_once=1 when an
initialization is in progress.

With glibc, if a call to the new std::call_once happens before a call to the
old version of std::call_once, then glibc's pthread_once will find no fork
generation value in _M_once and so will think it should run the init_function
itself. Both threads will run their init_function, instead of the second one
waiting for the first to finish.

With musl, if a call to the old std::call_once happens before a call to the new
std::call_once, then the second thread won't set _M_once=3 and so musl's
pthread_once won't wake the second thread when the first finishes. The second
thread will sleep forever (or until a spurious wake from the futex wait).

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
@ 2021-03-02 13:12 ` redi at gcc dot gnu.org
  2021-03-02 13:23 ` redi at gcc dot gnu.org
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: redi at gcc dot gnu.org @ 2021-03-02 13:12 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

Jonathan Wakely <redi at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
      Known to fail|                            |11.0
      Known to work|                            |10.2.1
           Priority|P3                          |P1
             Status|UNCONFIRMED                 |ASSIGNED
   Target Milestone|---                         |11.0
           Assignee|unassigned at gcc dot gnu.org      |redi at gcc dot gnu.org
     Ever confirmed|0                           |1
   Last reconfirmed|                            |2021-03-02

--- Comment #1 from Jonathan Wakely <redi at gcc dot gnu.org> ---
More details ...

Although libstdc++ ignores any fork generation bits, glibc doesn't and
it *requires* a (possibly zero) fork generation to be present.

The __pthread_once_slow function uses CAS to set the once_control
variable, and if that succeeds and the IN_PROGRESS bit is set, it
compares the high bits with the current fork generation.

If the IN_PROGRESS bit was set by the new libstdc++ std::call_once
there won't be a fork generation. If the process' fork generation is
not zero then this check in glibc will be false:

          /* Check whether the initializer execution was interrupted by a
             fork.  We know that for both values, __PTHREAD_ONCE_INPROGRESS
             is set and __PTHREAD_ONCE_DONE is not.  */
          if (val == newval)
            {
              /* Same generation, some other thread was faster.  Wait and
                 retry.  */
              futex_wait_simple ((unsigned int *) once_control,
                                 (unsigned int) newval, FUTEX_PRIVATE);
              continue;
            }

If std::call_once in another thread set val=2 but the fork gen is 4,
then newval=4|1 and so val == newval is false. That means glibc
assumes that an in-progress initialization was interrupted by a fork
and so this thread should run it. That means two threads will be
running it at once.

Demo:

#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <iostream>
#include <mutex>

extern std::once_flag once_flag;
extern void old();

#if __GNUC__ >= 11
std::once_flag once_flag;

int main()
{
  // increase for generation:
  switch (fork())
  {
  case -1:
    abort();
  case 0:
    break;
  default:
    wait(nullptr);
    return 0;
  }

  // This is the child process, fork generation is non-zero.

  std::call_once(once_flag, [] {
    std::cout << "Active execution started using new code\n";
    old();
    std::cout << "Active execution finished using new code\n";
  });
}
#else
void old()
{
  std::call_once(once_flag, [] {
    std::cout << "Active execution started using old code\n";
    std::cout << "Active execution finished using old code\n";
  });
}
#endif

Compile this once with GCC 11 and once with GCC 10 and link with GCC
11, and instead of deadlocking (as it should do) you'll get:

Active execution started using new code
Active execution started using old code
Active execution finished using old code
Active execution finished using new code

And using a libstdc++ build with _GLIBCXX_ASSERTIONS you'll get:
/home/jwakely/src/gcc/gcc/libstdc++-v3/src/c++11/mutex.cc:79: void
std::once_flag::_M_finish(bool): Assertion 'prev & _Bits::_Active' failed.

because the "inner" execution changes the state to _Done when it
finishes, and the assertion in the "outer" execution fails.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
  2021-03-02 13:12 ` [Bug libstdc++/99341] " redi at gcc dot gnu.org
@ 2021-03-02 13:23 ` redi at gcc dot gnu.org
  2021-03-03 16:59 ` jakub at gcc dot gnu.org
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: redi at gcc dot gnu.org @ 2021-03-02 13:23 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

--- Comment #2 from Jonathan Wakely <redi at gcc dot gnu.org> ---
The new implementation added these new symbols to libstdc++.so:

_ZNSt9once_flag11_M_activateEv
_ZNSt9once_flag9_M_finishEb

I think I'd like to keep them, but have the new implementation disabled by
default (except for the ABI-changing gnu-versioned-namespace build).

The new implementation is superior (it fixes PR 66146, and performs better in
some cases due to inlining an initial check to see if executions should be
passive) so I'd like to retain it for users who explicitly request it. By
default we need to be compatible with the old version based on pthread_once.

At some point (maybe when there are further ABI changes queued) maybe we'll do
another painful transition like the cxx11-abi one and switch to the new
std::once_flag by default.

Maybe the new std::once_flag should be moved to an inline namespace, and then
the symbols above defined as aliases for the equivalents using the inline
namespace in the mangled name (the aliases are needed if we want to support
binaries that are already using them, e.g. packages in the upcoming Fedora 34
which were compiled with gcc-11.0.1 already).

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
  2021-03-02 13:12 ` [Bug libstdc++/99341] " redi at gcc dot gnu.org
  2021-03-02 13:23 ` redi at gcc dot gnu.org
@ 2021-03-03 16:59 ` jakub at gcc dot gnu.org
  2021-03-04 14:26 ` jakub at gcc dot gnu.org
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: jakub at gcc dot gnu.org @ 2021-03-03 16:59 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

Jakub Jelinek <jakub at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |jakub at gcc dot gnu.org

--- Comment #3 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
A glibc fix has been posted:
https://sourceware.org/pipermail/libc-alpha/2021-March/123167.html

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
                   ` (2 preceding siblings ...)
  2021-03-03 16:59 ` jakub at gcc dot gnu.org
@ 2021-03-04 14:26 ` jakub at gcc dot gnu.org
  2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: jakub at gcc dot gnu.org @ 2021-03-04 14:26 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

--- Comment #4 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
Fixed on the glibc side:
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=f0419e6a10740a672b28e112c409ae24f5e890ab

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
                   ` (3 preceding siblings ...)
  2021-03-04 14:26 ` jakub at gcc dot gnu.org
@ 2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
  2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
  2021-03-16 13:58 ` jakub at gcc dot gnu.org
  6 siblings, 0 replies; 8+ messages in thread
From: cvs-commit at gcc dot gnu.org @ 2021-03-16 12:39 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

--- Comment #5 from CVS Commits <cvs-commit at gcc dot gnu.org> ---
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:6ee24638ed0ad51e568c799bacf149ba9bd7628b

commit r11-7688-g6ee24638ed0ad51e568c799bacf149ba9bd7628b
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Mar 12 11:47:20 2021 +0000

    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.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
                   ` (4 preceding siblings ...)
  2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
@ 2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
  2021-03-16 13:58 ` jakub at gcc dot gnu.org
  6 siblings, 0 replies; 8+ messages in thread
From: cvs-commit at gcc dot gnu.org @ 2021-03-16 12:39 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

--- Comment #6 from CVS Commits <cvs-commit at gcc dot gnu.org> ---
The master branch has been updated by Jonathan Wakely <redi@gcc.gnu.org>:

https://gcc.gnu.org/g:995a740cb01a0671a2082cb1ae13d0c356d4b568

commit r11-7689-g995a740cb01a0671a2082cb1ae13d0c356d4b568
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Fri Mar 12 11:47:20 2021 +0000

    libstdc++: Remove symbols for new std::call_once implementation [PR 99341]

    This removes the new symbols added for the new futex-based
    std::call_once implementation. These symbols were new on trunk, so not
    in any released version. However, they are already present in some
    beta distro releases (Fedora Linux 34) and in Fedora Linux rawhide. This
    change can be locally reverted by distros that need to keep the symbols
    present until affected packages have been rebuilt.

    libstdc++-v3/ChangeLog:

            PR libstdc++/99341
            * config/abi/post/aarch64-linux-gnu/baseline_symbols.txt: Remove
            std::once_flag symbols.
            * config/abi/post/ia64-linux-gnu/baseline_symbols.txt: Likewise.
            * config/abi/post/m68k-linux-gnu/baseline_symbols.txt: Likewise.
            * config/abi/post/riscv64-linux-gnu/baseline_symbols.txt:
            Likewise.
            * config/abi/pre/gnu.ver: Likewise.
            * src/c++11/mutex.cc [_GLIBCXX_HAVE_LINUX_FUTEX]
            (struct __once_flag_compat): Remove.
            (_ZNSt9once_flag11_M_activateEv): Remove.
            (_ZNSt9once_flag9_M_finishEb): Remove.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [Bug libstdc++/99341] [11 Regression] new std::call_once is not backwards compatible
  2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
                   ` (5 preceding siblings ...)
  2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
@ 2021-03-16 13:58 ` jakub at gcc dot gnu.org
  6 siblings, 0 replies; 8+ messages in thread
From: jakub at gcc dot gnu.org @ 2021-03-16 13:58 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99341

Jakub Jelinek <jakub at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|ASSIGNED                    |RESOLVED
         Resolution|---                         |FIXED

--- Comment #7 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
Fixed then.

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2021-03-16 13:58 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-02 13:11 [Bug libstdc++/99341] New: [11 Regression] new std::call_once is not backwards compatible redi at gcc dot gnu.org
2021-03-02 13:12 ` [Bug libstdc++/99341] " redi at gcc dot gnu.org
2021-03-02 13:23 ` redi at gcc dot gnu.org
2021-03-03 16:59 ` jakub at gcc dot gnu.org
2021-03-04 14:26 ` jakub at gcc dot gnu.org
2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
2021-03-16 12:39 ` cvs-commit at gcc dot gnu.org
2021-03-16 13:58 ` jakub at gcc dot gnu.org

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).