}
static _GLIBCXX_ALWAYS_INLINE bool
- _S_do_try_acquire(__count_type* __counter, __count_type __old) noexcept
+ _S_do_try_acquire(__count_type* __counter, __count_type& __old) noexcept
{
if (__old == 0)
return false;
memory_order::relaxed);
}
- _GLIBCXX_ALWAYS_INLINE void
+ void
_M_acquire() noexcept
{
auto const __vfn = [this]{ return _S_get_current(&this->_M_counter); };
- auto const __pred = [this](__count_type __cur) {
- return _S_do_try_acquire(&this->_M_counter, __cur);
+ auto __val = __vfn();
+ auto const __pred = [&__val](__count_type __cur) {
+ if (__cur > 0)
+ {
+ __val = __cur;
+ return true;
+ }
+ return false;
};
- std::__atomic_wait_address(&_M_counter, __pred, __vfn, true);
+ while (!_S_do_try_acquire(&_M_counter, __val))
+ if (__val == 0)
+ std::__atomic_wait_address(&_M_counter, __pred, __vfn, true);
}
bool
_M_try_acquire() noexcept
{
- auto const __vfn = [this]{ return _S_get_current(&this->_M_counter); };
- auto const __pred = [this](__count_type __cur) {
- return _S_do_try_acquire(&this->_M_counter, __cur);
- };
- using __detail::__wait_clock_t;
- return std::__atomic_wait_address_for(&_M_counter, __pred, __vfn,
- __wait_clock_t::duration(),
- true);
+ // The fastest implementation of this function is just _S_do_try_acquire
+ // but that can fail under contention even when _M_count > 0.
+ // Using _M_try_acquire_for(0ns) will retry a few times in a loop.
+ return _M_try_acquire_for(__detail::__wait_clock_t::duration{});
}
template<typename _Clock, typename _Duration>
- _GLIBCXX_ALWAYS_INLINE bool
+ bool
_M_try_acquire_until(const chrono::time_point<_Clock, _Duration>& __atime) noexcept
{
auto const __vfn = [this]{ return _S_get_current(&this->_M_counter); };
- auto const __pred = [this](__count_type __cur) {
- return _S_do_try_acquire(&this->_M_counter, __cur);
+ auto __val = __vfn();
+ auto const __pred = [&__val](__count_type __cur) {
+ if (__cur > 0)
+ {
+ __val = __cur;
+ return true;
+ }
+ return false;
};
- return std::__atomic_wait_address_until(&_M_counter, __pred, __vfn,
- __atime, true);
+ while (!_S_do_try_acquire(&this->_M_counter, __val))
+ if (__val == 0)
+ {
+ if (!std::__atomic_wait_address_until(&_M_counter, __pred,
+ __vfn, __atime, true))
+ return false; // timed out
+ }
+ return true;
}
template<typename _Rep, typename _Period>
- _GLIBCXX_ALWAYS_INLINE bool
+ bool
_M_try_acquire_for(const chrono::duration<_Rep, _Period>& __rtime) noexcept
{
auto const __vfn = [this]{ return _S_get_current(&this->_M_counter); };
- auto const __pred = [this](__count_type __cur) {
- return _S_do_try_acquire(&this->_M_counter, __cur);
+ auto __val = __vfn();
+ auto const __pred = [&__val](__count_type __cur) {
+ if (__cur > 0)
+ {
+ __val = __cur;
+ return true;
+ }
+ return false;
};
- return std::__atomic_wait_address_for(&_M_counter, __pred, __vfn,
- __rtime, true);
+ while (!_S_do_try_acquire(&this->_M_counter, __val))
+ if (__val == 0)
+ {
+ if (!std::__atomic_wait_address_for(&_M_counter, __pred,
+ __vfn, __rtime, true))
+ return false; // timed out
+ }
+ return true;
}
_GLIBCXX_ALWAYS_INLINE ptrdiff_t
--- /dev/null
+// { dg-do run { target c++20 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+// { dg-add-options libatomic }
+
+// Bug libstdc++/104928 - std::counting_semaphore on Linux can sleep forever
+
+#include <semaphore>
+#include <thread>
+#include <chrono>
+#include <atomic>
+
+std::binary_semaphore t1(1);
+std::binary_semaphore sem2(0);
+std::atomic<int> room1 = 0;
+int room2 = 0;
+
+std::atomic<bool> run{true};
+
+enum class AcquireKind { Acquire, Try, TryFor };
+
+template<std::ptrdiff_t N, AcquireKind Kind>
+struct Morris
+{
+ using Semaphore = std::counting_semaphore<N>;
+
+ Semaphore sem1{1};
+ Semaphore sem2{0};
+ unsigned counter = 0;
+
+ void operator()()
+ {
+ while (run)
+ {
+ room1 += 1;
+
+ acquire(sem1);
+ room2 += 1;
+ room1 -= 1;
+ if (room1 == 0)
+ sem2.release();
+ else
+ sem1.release();
+
+ acquire(sem2);
+ room2 -= 1;
+
+ // critical region
+ ++counter;
+ // end critical region
+
+ if (room2 == 0)
+ sem1.release();
+ else
+ sem2.release();
+ }
+ }
+
+ void acquire(Semaphore& sem)
+ {
+ using enum AcquireKind;
+ using namespace std::chrono;
+ if constexpr (Kind == Acquire)
+ sem.acquire();
+ else if constexpr (Kind == Try)
+ while (!sem.try_acquire()) { }
+ else if constexpr (Kind == TryFor)
+ while (!sem.try_acquire_for(1h)) { }
+ }
+};
+
+template<std::ptrdiff_t N, AcquireKind Kind>
+void
+test_morris_kind()
+{
+ Morris<N, Kind> algo;
+ std::thread t1(std::ref(algo));
+ std::thread t2(std::ref(algo));
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ run = false;
+ t1.join();
+ t2.join();
+}
+
+template<std::ptrdiff_t N>
+void
+test_morris()
+{
+ test_morris_kind<N, AcquireKind::Acquire>();
+ test_morris_kind<N, AcquireKind::Try>();
+ test_morris_kind<N, AcquireKind::TryFor>();
+}
+
+int main()
+{
+ test_morris<1>(); // std::binary_semaphore
+ test_morris<1000>(); // std::counting_semaphore that can use futex
+#if PTRDIFF_MAX > INT_MAX
+ // test_morris<PTRDIFF_MAX>(); // std::counting_semaphore that cannot use futex
+#endif
+}
--- /dev/null
+// { dg-do run { target c++20 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+// { dg-add-options libatomic }
+// { dg-options "-DSIMULATOR_TEST" { target simulator } }
+
+// Bug libstdc++/104928 - std::counting_semaphore on Linux can sleep forever
+
+#include <semaphore>
+#include <thread>
+#include <chrono>
+#include <climits>
+
+#ifdef SIMULATOR_TEST
+const int loop_count = 100;
+const int thread_count = 6;
+#else
+const int loop_count = 1000000;
+const int thread_count = 20;
+#endif
+
+template<std::ptrdiff_t N, typename Acquire>
+void
+test_acquire(Acquire acq_func)
+{
+ std::counting_semaphore<N * loop_count> s{0};
+ std::thread threads[thread_count];
+ for (int i = 0; i < thread_count; i += 2) {
+ threads[i] = std::thread([&s, &acq_func]() {
+ for (int i = 0; i < loop_count; ++i)
+ acq_func(s);
+ });
+ threads[i+1] = std::thread([&s]() {
+ for (int i = 0; i < loop_count; ++i)
+ s.release();
+ });
+ }
+ for (auto& t : threads)
+ t.join();
+}
+
+template<typename Acquire>
+void
+test_all(Acquire f)
+{
+ const int max = INT_MAX / loop_count;
+ test_acquire<max>(f); // can use futex
+#if PTRDIFF_MAX > INT_MAX
+ test_acquire<max * 10>(f); // cannot use futex
+#endif
+}
+
+int main()
+{
+ test_all([](auto& sem) { sem.acquire(); });
+
+ test_all([](auto& sem) { while (!sem.try_acquire()) { } });
+
+ using namespace std::chrono;
+
+ test_all([](auto& sem) { while (!sem.try_acquire_for(1h)) { } });
+
+ auto try_acquire_until = [](auto& sem, auto time) {
+ while (!sem.try_acquire_until(time + 1h))
+ { }
+ };
+ test_all([&](auto& sem) { try_acquire_until(sem, system_clock::now()); });
+ test_all([&](auto& sem) { try_acquire_until(sem, steady_clock::now()); });
+ test_all([&](auto& sem) { try_acquire_until(sem, utc_clock::now()); });
+}