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

            Bug ID: 127523
           Summary: 30_threads/condition_variable/members/68519.cc:
                    test_wait_until fails when a timed wait wakes within
                    the clock tick equal to the deadline
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: gokbeykeskin at gmail dot com
  Target Milestone: ---

Created attachment 65645
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65645&action=edit
Suggested patch

On a POSIX compliant OS whose CLOCK_REALTIME and CLOCK_MONOTONIC advance in
coarse
ticks (reproduced with 2 ms and with 500 us clock ticks), test_wait_until() in
68519.cc fails intermittently at

  VERIFY( result == std::cv_status::timeout );   // line 86

test_wait_for() in the same file always passes. The target has no
pthread_cond_clockwait, so wait_until converts the deadline to system_clock
and use pthread_cond_timedwait.

The OS is conforming. cv_timedwait_diag.c replays the library's sequence in
C (its output is attached): pthread_cond_timedwait always returns ETIMEDOUT,
a deadline between ticks is rounded up to the next tick, and there are no
early returns. The woken thread reads the clock within the same tick, so
clock_gettime() returns exactly the deadline value. On Linux the thread
instead observes the clock 50 to 150 us after the deadline.

The cause of the failure is float rounding in the test's clock.
recent_epoch_float_clock stores time in duration<float>. A float near 4 s can
only hold values about 0.5 us apart, so every conversion snaps to the nearest
such value. The deadline (start + 1.0f) and the clock reading after the wait (a
nanosecond count converted to float) are snapped separately, so they can land
on
different values although they describe the same instant. Example with a 2 ms
tick, start 3.308 s after the epoch:

  wait_time = 3.308000088f + 1.0f                 = 4.308000088f
  now()     = float(4308000000 ns) / 1e9f
            = 4307999744.f / 1e9f                  = 4.307999611f
                              (floats near 4e9 are spaced 512 apart)

now() < wait_time, so the final check in the generic wait_until overload

  if (_Clock::now() < __atime) return cv_status::no_timeout;

reports no_timeout. The caller's clock has not reached the deadline, so this is
a spurious wakeup as seen by that clock. libstdc++ decides the same way. The
test's comment "In theory we could get a spurious wakeup, but in practice we
won't" only holds where wake-up latency exceeds the rounding.

How often: 68519_float_rounding_sim.cc evaluates the test's arithmetic for
every tick-aligned start between 3 s and 7 s after the epoch.

  exact-tick wakeup, tick 100 us / 500 us / 1 ms / 2 ms / 10 ms :
      4.1% / 4.1% / 5.9% / 9.6% / 9.5% of iterations return no_timeout
  same with a 70 us wake latency (Linux) : 0%

Reproduce on Linux: 68519_tick_clock_repro.cc is test_wait_until() with the
steady_clock reading rounded down to a tick.

  g++ -std=c++17 -pthread 68519_tick_clock_repro.cc -o repro
  ./repro 2000 24    # 2 ms tick   : 6 of 24 iterations returned no_timeout
  ./repro 500 24     # 500 us tick : 4 of 24 iterations returned no_timeout

Proposed fix (attached patch): in the test, accept a no_timeout result only
if clock::now() is within 2 us of wait_time (a few float ULPs there), then
wait again and require timeout. The patch applies to trunk and the modified
test passes on x86_64-linux-gnu.

libstdc++-v3/ChangeLog:

        * testsuite/30_threads/condition_variable/members/68519.cc
        (test_wait_until): Wait again if wait_until returns no_timeout
        within a few float ULPs of the deadline.

Reply via email to