diff --git a/.agents/skills/lib_py/delegation/wrapper.py b/.agents/skills/lib_py/delegation/wrapper.py index 7051e46..4f5a589 100644 --- a/.agents/skills/lib_py/delegation/wrapper.py +++ b/.agents/skills/lib_py/delegation/wrapper.py @@ -143,11 +143,14 @@ def _check_owner(a: argparse.Namespace) -> None: def _sleep(a: argparse.Namespace, seconds: float) -> None: - """time.sleep that looks at the wrapper every 0.5 s.""" - end = time.time() + seconds + """time.sleep that looks at the wrapper every 0.5 s. The span is measured + on the monotonic clock, as bash's `sleep N` is: a wall clock stepped back + during the wait must not stretch it (review R1, codex). Deadlines stay on + the wall clock (_now).""" + end = time.monotonic() + seconds while True: _check_owner(a) - left = end - time.time() + left = end - time.monotonic() if left <= 0: return time.sleep(min(left, 0.5)) diff --git a/tests/test_issue17_item4_decisions.py b/tests/test_issue17_item4_decisions.py index fdd68e2..5580f99 100644 --- a/tests/test_issue17_item4_decisions.py +++ b/tests/test_issue17_item4_decisions.py @@ -591,3 +591,40 @@ def test_c_an_unreaped_wrapper_leaves_the_real_record_alone(tmp_path): time.sleep(6) states.append((rc, took < 4.5, json.loads((reg / f"{jid}.json").read_text())["status"])) assert states[0] == states[1] == (-15, True, "pending"), states + + +# --- review R1 (codex): a wall clock stepped back does not stretch a wait ---- + +class _SteppedClock: + """A `time` stand-in: wall and monotonic both advance on sleep(), and the + wall clock is stepped back once, during the first sleep.""" + + def __init__(self, step_back: float): + self.wall, self.mono, self.step_back, self.slept = 1000.0, 50.0, step_back, 0.0 + + def time(self): + return self.wall + + def monotonic(self): + return self.mono + + def sleep(self, dt): + self.wall += dt + self.mono += dt + self.slept += dt + if self.step_back: + self.wall -= self.step_back + self.step_back = 0 + + +@pytest.mark.parametrize("seconds", [1, 3]) +def test_u_a_wait_is_not_stretched_by_a_wall_clock_step_back(monkeypatch, seconds): + """The broker re-observation (up to 3 s) and the disk-heartbeat grace (1 s) + re-read the record after `seconds`, as bash's `sleep N` did, even when the + wall clock steps back 10 s meanwhile (on the old wall-clock arithmetic the + wait became seconds + 10).""" + import argparse + clock = _SteppedClock(step_back=10) + monkeypatch.setattr(decide, "time", clock) + decide._sleep(argparse.Namespace(watch_pid=None), seconds) + assert seconds <= clock.slept <= seconds + 0.5, clock.slept