Skip to content

Commit d0c5661

Browse files
authored
Merge pull request #216 from CTRLRun/fix/t558b-startup-confound
T558b measured its own patience, not the child's blocking
2 parents 42ca114 + 000e0e9 commit d0c5661

1 file changed

Lines changed: 19 additions & 7 deletions

File tree

‎tests/test_retention.py‎

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,9 +1437,16 @@ def test_T558b_a_hold_cannot_land_while_a_prune_holds_the_lock() -> None:
14371437
"from ctrlrun.postgres import PostgresStateStore\n"
14381438
"from ctrlrun.retention import Hold\n"
14391439
f"s = PostgresStateStore({POSTGRES_URL!r}, schema={schema!r})\n"
1440+
# **The handshake, and why it is not optional.** Until this line the child is importing
1441+
# `ctrlrun` and opening a Postgres connection, and on a loaded runner that can outlast
1442+
# the parent's whole sleep. Without it the parent measures its own patience rather than
1443+
# the child's blocking: CI saw `waited 0.135s` on a run where the child reached the lock
1444+
# only after the prune had released it, and the test failed because it could not prove
1445+
# the property rather than because the property was false.
1446+
"print('ready', flush=True)\n"
14401447
"start = time.time()\n"
14411448
"s.put_hold(Hold('late', 1, 3, 'litigation', 'cli:bob', datetime.now(UTC)))\n"
1442-
"print('%.3f' % (time.time() - start))\n"
1449+
"print('%.3f' % (time.time() - start), flush=True)\n"
14431450
"s.close()\n"
14441451
)
14451452
try:
@@ -1459,7 +1466,12 @@ def test_T558b_a_hold_cannot_land_while_a_prune_holds_the_lock() -> None:
14591466
stdout=subprocess.PIPE,
14601467
stderr=subprocess.STDOUT,
14611468
)
1462-
time.sleep(1.5)
1469+
# Read the child's `ready` before starting the clock: after this it is connected
1470+
# and its next statement is the one that must block.
1471+
handshake = child.stdout.readline().strip() if child.stdout else ""
1472+
assert handshake == "ready", f"the child never reached the lock: {handshake!r}"
1473+
held_for = 1.0
1474+
time.sleep(held_for)
14631475
still_waiting = child.poll() is None
14641476
output, _ = child.communicate(timeout=60)
14651477
parent.close()
@@ -1468,12 +1480,12 @@ def test_T558b_a_hold_cannot_land_while_a_prune_holds_the_lock() -> None:
14681480
"a hold landed while a prune held the lock, so it can be placed over receipts the "
14691481
f"prune is about to delete: {output}"
14701482
)
1471-
# The child's own measurement starts after its interpreter and imports, so it is
1472-
# necessarily less than the parent's sleep. `still_waiting` above is the real proof;
1473-
# this is the coarse floor that separates blocking from an unblocked call, which takes
1474-
# single-digit milliseconds.
1483+
# With the handshake above, the child's clock starts at the lock rather than at its
1484+
# interpreter, so this is now a measurement of blocking and not of startup. The floor is
1485+
# half the hold, which leaves room for scheduling on a busy runner while staying far
1486+
# above the single-digit milliseconds an uncontended `put_hold` takes.
14751487
waited = float(output.strip().splitlines()[-1])
1476-
assert waited >= 0.25, f"the child did not block on the lock: waited {waited}s"
1488+
assert waited >= held_for / 2, f"the child did not block on the lock: waited {waited}s"
14771489
finally:
14781490
PostgresStateStore.drop_schema(POSTGRES_URL, schema)
14791491

0 commit comments

Comments
 (0)