public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread
@ 2021-04-29 13:22 m.cencora at gmail dot com
  2021-04-29 13:49 ` [Bug libstdc++/100334] " m.cencora at gmail dot com
                   ` (12 more replies)
  0 siblings, 13 replies; 14+ messages in thread
From: m.cencora at gmail dot com @ 2021-04-29 13:22 UTC (permalink / raw)
  To: gcc-bugs

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

            Bug ID: 100334
           Summary: atomic<T>::notify_one() sometimes wakes wrong thread
           Product: gcc
           Version: 11.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: m.cencora at gmail dot com
  Target Milestone: ---

If waiter pool implementation is used in std::atomic<T>::wait/notify for given
T, then notify_one must underneath call notify_all to make sure that proper
thread is awaken.
I.e. if multiple threads call atomic<T>::wait() on different atomic<T>
instances, but all of them share same waiter, then notify_one on only one of
atomics will possibly wake the wrong thread.
This can lead to program hangs, deadlocks, etc.

Following test app reproduces the bug:
g++-11 -std=c++20 -lpthread

#include <atomic>
#include <future>
#include <iostream>
#include <source_location>
#include <thread>
#include <vector>

void verify(bool cond, std::source_location loc =
std::source_location::current())
{
    if (!cond)
    {
        std::cout << "Failed at line " << loc.line() << '\n';
        std::abort();
    }
}

template <typename T>
struct atomics_sharing_same_waiter
{
   std::unique_ptr<std::atomic<T>> a[4];
};

unsigned get_waiter_key(void * ptr)
{
   return std::_Hash_impl::hash(ptr) & 0xf;
}

template <typename T>
atomics_sharing_same_waiter<T> create_atomics()
{
   std::vector<std::unique_ptr<std::atomic<T>>> non_matching_atomics;

   atomics_sharing_same_waiter<T> atomics;
   atomics.a[0] = std::make_unique<std::atomic<T>>(0);

   auto key = get_waiter_key(atomics.a[0].get());
   for (auto i = 1u; i < 4u; ++i)
   {
      while (true)
      {
         auto atom = std::make_unique<std::atomic<T>>(0);
         if (get_waiter_key(atom.get()) == key)
         {
            atomics.a[i] = std::move(atom);
            break;
         }
         else
         {
            non_matching_atomics.push_back(std::move(atom));
         }
      }
   }

   return atomics;
}


int main()
{
    // all atomic share the same waiter
    auto atomics = create_atomics<char>();

    auto fut0 = std::async(std::launch::async, [&] {
        atomics.a[0]->wait(0);
    });

    auto fut1 = std::async(std::launch::async, [&] {
        atomics.a[1]->wait(0);
    });

    auto fut2 = std::async(std::launch::async, [&] {
        atomics.a[2]->wait(0);
    });

    auto fut3 = std::async(std::launch::async, [&] {
        atomics.a[3]->wait(0);
    });

    // make sure the all threads already await
    std::this_thread::sleep_for(std::chrono::milliseconds{100});

    atomics.a[2]->store(1);
    atomics.a[2]->notify_one(); // changing to notify_all() allows this test to
pass

    verify(std::future_status::timeout ==
fut0.wait_for(std::chrono::milliseconds{100}));
    verify(std::future_status::timeout ==
fut1.wait_for(std::chrono::milliseconds{100}));
    verify(std::future_status::ready ==
fut2.wait_for(std::chrono::milliseconds{100}));
    verify(std::future_status::timeout ==
fut3.wait_for(std::chrono::milliseconds{100}));

    atomics.a[0]->store(1);
    atomics.a[0]->notify_one();
    atomics.a[1]->store(1);
    atomics.a[1]->notify_one();
    atomics.a[3]->store(1);
    atomics.a[3]->notify_one();
}

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
@ 2021-04-29 13:49 ` m.cencora at gmail dot com
  2021-04-29 14:33 ` m.cencora at gmail dot com
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: m.cencora at gmail dot com @ 2021-04-29 13:49 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #1 from m.cencora at gmail dot com ---
This test assumes previous waiter implementation (I used gcc-11 available from
Ubuntu 21.04), latest atomic_wait impl has the same problem, it is just that
waiter is selected in a different way, and create_atomics would have to be
adjusted to accordingly (e.g. allocate 49 atomics in a single vector, and
0,16,32,48 instances should share same waiter instance).

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
  2021-04-29 13:49 ` [Bug libstdc++/100334] " m.cencora at gmail dot com
@ 2021-04-29 14:33 ` m.cencora at gmail dot com
  2021-04-29 14:42 ` m.cencora at gmail dot com
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: m.cencora at gmail dot com @ 2021-04-29 14:33 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #2 from m.cencora at gmail dot com ---
I have adapted the test to gcc trunk, but I am not entirely sure it is correct,
because I don't have gcc trunk locally, I was just testing this on wandbox.org

The problem is even bigger here, because it seems that besides wrong thread is
woken up,
also wrong std::atomic<T> instance finishes the wait() call!

#include <atomic>
#include <future>
#include <iostream>
#include <source_location>
#include <thread>
#include <vector>

void verify(bool cond, std::source_location loc =
std::source_location::current())
{
    if (!cond)
    {
        std::cout << "Failed at line " << loc.line() << '\n';
        //std::abort();
    }
}


void emptyDeleter(void *)
{}

template <typename T>
struct atomics_sharing_same_waiter
{
   std::atomic<T> tmp[49 * 4] = {};
   std::shared_ptr<std::atomic<T>> a[4] = {
      { &tmp[0], emptyDeleter },
      { &tmp[16 * 4], emptyDeleter },
      { &tmp[32 * 4], emptyDeleter },
      { &tmp[48 * 4], emptyDeleter }
   };
};

constexpr unsigned key(void * a)
{
    constexpr uintptr_t __ct = 16;
        return (uintptr_t(a) >> 2) % __ct;
}

int main()
{
    // all atomic share the same waiter
    atomics_sharing_same_waiter<char> atomics;
    for (auto& atom : atomics.a)
    {
       atom->store(0);
    }

    std::cout << "atom0 " << atomics.a[0].get() << " key " <<
key(atomics.a[0].get()) << '\n';
    std::cout << "atom1 " << atomics.a[1].get() << " key " <<
key(atomics.a[1].get()) << '\n';
    std::cout << "atom2 " << atomics.a[2].get() << " key " <<
key(atomics.a[2].get()) << '\n';
    std::cout << "atom3 " << atomics.a[3].get() << " key " <<
key(atomics.a[3].get()) << '\n';


    verify(&std::__detail::__waiter_pool_base::_S_for(reinterpret_cast<char
*>(atomics.a[0].get())) == 
           &std::__detail::__waiter_pool_base::_S_for(reinterpret_cast<char
*>(atomics.a[1].get())));

    auto fut0 = std::async(std::launch::async, [&] {
        atomics.a[0]->wait(0);
    });

    auto fut1 = std::async(std::launch::async, [&] {
        atomics.a[1]->wait(0);
    });

    auto fut2 = std::async(std::launch::async, [&] {
        atomics.a[2]->wait(0);
    });

    auto fut3 = std::async(std::launch::async, [&] {
        atomics.a[3]->wait(0);
    });

    // make sure the all threads already await
    std::this_thread::sleep_for(std::chrono::milliseconds{100});

    atomics.a[2]->store(1);
    atomics.a[2]->notify_one(); // changing to notify_all() doesn't help on gcc
trunk

    verify(std::future_status::timeout ==
fut0.wait_for(std::chrono::milliseconds{100}));
    verify(atomics.a[0]->load() == 0);
    verify(std::future_status::timeout ==
fut1.wait_for(std::chrono::milliseconds{100}));
    verify(atomics.a[1]->load() == 0);
    verify(std::future_status::ready ==
fut2.wait_for(std::chrono::milliseconds{100}));
    verify(atomics.a[2]->load() == 1);
    verify(std::future_status::timeout ==
fut3.wait_for(std::chrono::milliseconds{100}));
    verify(atomics.a[3]->load() == 0);

    atomics.a[0]->store(1);
    atomics.a[0]->notify_one();
    atomics.a[1]->store(1);
    atomics.a[1]->notify_one();
    atomics.a[3]->store(1);
    atomics.a[3]->notify_one();
}

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
  2021-04-29 13:49 ` [Bug libstdc++/100334] " m.cencora at gmail dot com
  2021-04-29 14:33 ` m.cencora at gmail dot com
@ 2021-04-29 14:42 ` m.cencora at gmail dot com
  2021-04-29 16:28 ` rodgertq at gcc dot gnu.org
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: m.cencora at gmail dot com @ 2021-04-29 14:42 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #3 from m.cencora at gmail dot com ---
If my analysis is correct then:
 - we need to force __all = true param in __waiter_pool_base::_M_notify,
 - protect from spurious wakeups in __waiter_pool::_M_do_wait by rechecking if
the value has changed from old, if not then wait again

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (2 preceding siblings ...)
  2021-04-29 14:42 ` m.cencora at gmail dot com
@ 2021-04-29 16:28 ` rodgertq at gcc dot gnu.org
  2021-04-29 22:28 ` m.cencora at gmail dot com
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-04-29 16:28 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #4 from Thomas Rodgers <rodgertq at gcc dot gnu.org> ---
This analysis is likely correct, except for -

"- protect from spurious wakeups in __waiter_pool::_M_do_wait by rechecking if
the value has changed from old, if not then wait again"

An earlier version of this code did this, but was subject to ABA problems, and 
the standard doesn't require that we do this -

"(23.3) — Blocks until it is unblocked by an atomic notifying operation or is
unblocked spuriously."

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (3 preceding siblings ...)
  2021-04-29 16:28 ` rodgertq at gcc dot gnu.org
@ 2021-04-29 22:28 ` m.cencora at gmail dot com
  2021-05-01 20:03 ` rodgertq at gcc dot gnu.org
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: m.cencora at gmail dot com @ 2021-04-29 22:28 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #5 from m.cencora at gmail dot com ---
Actually standard seems to require it - at least to my understanding of wait()
description in  in chapter 31.8.1: it explicitly states that waiting is
performed in a loop, and loop is exited only if value has changed from old.

So when we force notify_all for pooled waiters, then it may happen that a
thread for which value didn't change from 'old' will be woken, so we must
recheck the value.

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (4 preceding siblings ...)
  2021-04-29 22:28 ` m.cencora at gmail dot com
@ 2021-05-01 20:03 ` rodgertq at gcc dot gnu.org
  2021-05-01 21:49 ` rodgertq at gcc dot gnu.org
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-01 20:03 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
     Ever confirmed|0                           |1
   Last reconfirmed|                            |2021-05-01
             Status|UNCONFIRMED                 |ASSIGNED

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (5 preceding siblings ...)
  2021-05-01 20:03 ` rodgertq at gcc dot gnu.org
@ 2021-05-01 21:49 ` rodgertq at gcc dot gnu.org
  2021-05-03 16:42 ` rodgertq at gcc dot gnu.org
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-01 21:49 UTC (permalink / raw)
  To: gcc-bugs

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

--- Comment #6 from Thomas Rodgers <rodgertq at gcc dot gnu.org> ---
Created attachment 50728
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=50728&action=edit
Fix wrong thread getting notification

I am testing this patch

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (6 preceding siblings ...)
  2021-05-01 21:49 ` rodgertq at gcc dot gnu.org
@ 2021-05-03 16:42 ` rodgertq at gcc dot gnu.org
  2021-05-03 22:56 ` rodgertq at gcc dot gnu.org
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-03 16:42 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
  Attachment #50728|0                           |1
        is obsolete|                            |

--- Comment #7 from Thomas Rodgers <rodgertq at gcc dot gnu.org> ---
Created attachment 50740
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=50740&action=edit
Fix wrong thread getting notification, take 2

This should fix the issue, submitting patch to mailing list.

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (7 preceding siblings ...)
  2021-05-03 16:42 ` rodgertq at gcc dot gnu.org
@ 2021-05-03 22:56 ` rodgertq at gcc dot gnu.org
  2021-05-04  1:14 ` rodgertq at gcc dot gnu.org
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-03 22:56 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|ASSIGNED                    |WAITING

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (8 preceding siblings ...)
  2021-05-03 22:56 ` rodgertq at gcc dot gnu.org
@ 2021-05-04  1:14 ` rodgertq at gcc dot gnu.org
  2021-05-04  1:21 ` rodgertq at gcc dot gnu.org
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-04  1:14 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|WAITING                     |ASSIGNED

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (9 preceding siblings ...)
  2021-05-04  1:14 ` rodgertq at gcc dot gnu.org
@ 2021-05-04  1:21 ` rodgertq at gcc dot gnu.org
  2021-05-17 20:20 ` rodgertq at gcc dot gnu.org
  2021-05-17 22:31 ` redi at gcc dot gnu.org
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-04  1:21 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
           Keywords|                            |patch

--- Comment #8 from Thomas Rodgers <rodgertq at gcc dot gnu.org> ---
Candidate patch submitted to mailing list -

https://gcc.gnu.org/pipermail/libstdc++/2021-May/052464.html

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (10 preceding siblings ...)
  2021-05-04  1:21 ` rodgertq at gcc dot gnu.org
@ 2021-05-17 20:20 ` rodgertq at gcc dot gnu.org
  2021-05-17 22:31 ` redi at gcc dot gnu.org
  12 siblings, 0 replies; 14+ messages in thread
From: rodgertq at gcc dot gnu.org @ 2021-05-17 20:20 UTC (permalink / raw)
  To: gcc-bugs

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

Thomas Rodgers <rodgertq at gcc dot gnu.org> changed:

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

--- Comment #9 from Thomas Rodgers <rodgertq at gcc dot gnu.org> ---
Fixed on master, backported to releases/gcc-11

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

* [Bug libstdc++/100334] atomic<T>::notify_one() sometimes wakes wrong thread
  2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
                   ` (11 preceding siblings ...)
  2021-05-17 20:20 ` rodgertq at gcc dot gnu.org
@ 2021-05-17 22:31 ` redi at gcc dot gnu.org
  12 siblings, 0 replies; 14+ messages in thread
From: redi at gcc dot gnu.org @ 2021-05-17 22:31 UTC (permalink / raw)
  To: gcc-bugs

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

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

           What    |Removed                     |Added
----------------------------------------------------------------------------
   Target Milestone|---                         |11.2

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

end of thread, other threads:[~2021-05-17 22:31 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-29 13:22 [Bug libstdc++/100334] New: atomic<T>::notify_one() sometimes wakes wrong thread m.cencora at gmail dot com
2021-04-29 13:49 ` [Bug libstdc++/100334] " m.cencora at gmail dot com
2021-04-29 14:33 ` m.cencora at gmail dot com
2021-04-29 14:42 ` m.cencora at gmail dot com
2021-04-29 16:28 ` rodgertq at gcc dot gnu.org
2021-04-29 22:28 ` m.cencora at gmail dot com
2021-05-01 20:03 ` rodgertq at gcc dot gnu.org
2021-05-01 21:49 ` rodgertq at gcc dot gnu.org
2021-05-03 16:42 ` rodgertq at gcc dot gnu.org
2021-05-03 22:56 ` rodgertq at gcc dot gnu.org
2021-05-04  1:14 ` rodgertq at gcc dot gnu.org
2021-05-04  1:21 ` rodgertq at gcc dot gnu.org
2021-05-17 20:20 ` rodgertq at gcc dot gnu.org
2021-05-17 22:31 ` redi 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).