https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98376
--- Comment #2 from Paul Groke <paul.groke at dynatrace dot com> --- I was able to reproduce the problem with the following program (see below). It won't reproduce well on a loaded machine and you may have to fiddle with the spin counts to get a successful repro. I know this is a Linux bug, but since a lot of people are using Linux it might be worth implementing a workaround in libstdc++. Output from my machine: Remaining: 0.020000000 - signal count: 0 Remaining: 0.312026100 - signal count: 6322 Remaining: 0.610834500 - signal count: 12729 Remaining: 0.907113100 - signal count: 19096 Remaining: 1.202000100 - signal count: 25452 Remaining: 1.499204500 - signal count: 31842 Remaining: 1.799767600 - signal count: 38277 Remaining: 2.095204700 - signal count: 44636 (...snip...) Remaining: 86.829101800 - signal count: 1865855 Remaining: 87.126722500 - signal count: 1872251 Remaining: 87.427912300 - signal count: 1878704 Remaining: 87.730148000 - signal count: 1885171 Remaining: 88.029529500 - signal count: 1891599 Remaining: 88.329424200 - signal count: 1898039 Remaining: 88.629499900 - signal count: 1904482 Remaining: 88.926003300 - signal count: 1910861 ERROR: timeout. sleep thread seems hung. --- #include <signal.h> #include <time.h> #include <atomic> #include <chrono> #include <iomanip> #include <iostream> #include <thread> using namespace std::chrono_literals; constexpr auto acq = std::memory_order_acquire; constexpr auto rlx = std::memory_order_relaxed; constexpr uint64_t sigHandlerSpin = 10'000; constexpr uint64_t sigSenderSpin = 100; constexpr int64_t sleepTimeNs = 20'000'000; // 20ms constexpr int64_t reportIntervalNs = 200'000'000; // 200ms constexpr auto timeout = 60s; thread_local bool tls_isSleepThread{false}; std::atomic<uint32_t> s_signalCount{0}; std::atomic<bool> s_signalPending{false}; struct timespec makeTs(time_t sec, int64_t nsec) { struct timespec ts = {}; ts.tv_sec = sec; ts.tv_nsec = nsec; return ts; } void spinWait(uint64_t spinCount) { [[maybe_unused]] volatile int32_t dummy = 0; volatile int32_t divisor = 123456789; volatile int32_t dividend = 33; for (uint64_t i = 0; i < spinCount; i++) dummy = divisor / dividend; } void signalHandler(int) { if (tls_isSleepThread) { s_signalCount++; spinWait(sigHandlerSpin); } s_signalPending = false; }; void spinBarrier(std::atomic<uint32_t>& barrier) { barrier--; while (barrier.load(acq)) { // wait... } } struct State { std::atomic<uint32_t> startBarrier{3}; std::atomic<bool> stop{false}; std::atomic<uint32_t> reportGen{0}; std::atomic<int64_t> reportSec{0}; std::atomic<int64_t> reportNsec{0}; }; [[maybe_unused]] void nanosleepThreadFn(State& state) { tls_isSleepThread = true; auto sleepTime = makeTs(0, sleepTimeNs); state.reportSec.store(sleepTime.tv_sec, rlx); state.reportNsec.store(sleepTime.tv_nsec, rlx); spinBarrier(state.startBarrier); while (!state.stop) { if (::nanosleep(&sleepTime, &sleepTime) != -1) break; if (errno != EINTR) break; auto gen = state.reportGen.load(acq); state.reportSec.store(sleepTime.tv_sec, rlx); state.reportNsec.store(sleepTime.tv_nsec, rlx); state.reportGen.store(gen + 1); } state.stop = true; } [[maybe_unused]] void stdSleepThreadFn(State& state) { tls_isSleepThread = true; auto sleepTime = 1ns * sleepTimeNs; spinBarrier(state.startBarrier); std::this_thread::sleep_for(sleepTime); state.stop = true; } void reportThreadFn(State& state) { auto const delay = makeTs(0, reportIntervalNs); spinBarrier(state.startBarrier); while (!state.stop) { // try to get consistent snapshot... int64_t sec; int64_t nsec; for (size_t i = 0; i < 10; i++) { auto const gen = state.reportGen.load(acq); spinWait(10); sec = state.reportSec.load(rlx); nsec = state.reportNsec.load(rlx); spinWait(10); auto const gen2 = state.reportGen.load(acq); if (gen2 == gen) break; } std::cout << "Remaining: " << sec << "." << std::setfill('0') << std::setw(9) << nsec << " - signal count: " << s_signalCount << std::endl; nanosleep(&delay, nullptr); } } int runTest() { s_signalCount = 0; s_signalPending = false; // install SIGURG handler... struct sigaction oldSigUrgAction = {}; struct sigaction sigUrgAction = {}; sigUrgAction.sa_handler = signalHandler; sigaction(SIGURG, &sigUrgAction, &oldSigUrgAction); auto* const state = new State{}; std::thread sleepThread{[state] { nanosleepThreadFn(*state); }}; std::thread reportThread{[state] { reportThreadFn(*state); }}; auto const sleepThreadHandle = sleepThread.native_handle(); spinBarrier(state->startBarrier); using Clock = std::chrono::steady_clock; auto const t0 = Clock::now(); // Hammer the sleep thread with signals... bool isTimedOut = false; while (!state->stop && !isTimedOut) { if (!s_signalPending) { s_signalPending = true; spinWait(sigSenderSpin); pthread_kill(sleepThreadHandle, SIGURG); } if (Clock::now() - t0 > timeout) { isTimedOut = true; break; } } state->stop = true; reportThread.join(); if (isTimedOut) { std::cout << "ERROR: timeout. sleep thread seems hung." << std::endl; // Leak sleepThread and state to avoid crash sleepThread.detach(); } else { sleepThread.join(); delete state; } // Restore old SIGCHLD action. sigaction(SIGURG, &oldSigUrgAction, nullptr); return isTimedOut ? 1 : 0; } int main() { return runTest(); }