Skip to content

fix(test): scope the notification-copy ordering triggers to the destination open - #8992

Merged
iamwhatever merged 1 commit into
kirodotdev:mainfrom
javenciu:fix/snapshot-notification-copy-flake
Sep 6, 2026
Merged

fix(test): scope the notification-copy ordering triggers to the destination open#8992
iamwhatever merged 1 commit into
kirodotdev:mainfrom
javenciu:fix/snapshot-notification-copy-flake

Conversation

@javenciu

@javenciu javenciu commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Problem / Motivation

Two ordering tests in TestNotificationCopyWhenNoLiveFileExists (test/test_snapshot.py) failed on a loaded Backend Tests (3.12, 4) shard for PR #8887, whose diff never touches the snapshot/notification path:

AssertionError: the append ran while the copy was still writing, so the two are concurrent rather than ordered
AssertionError: a delivery on a fresh gateway ran concurrently with the copy: 'no pool' was read as 'no writer'

The class passes locally on the same commit. A second occurrence fired on shard 4 for PR #8896 (run 34014923783) while the same test passed 5/5 locally at that PR's exact sha — and a third on the very next round of the same PR (run 34017247801, test_a_FRESH_gateway_still_orders_the_copy_against_a_delivery again, taking the Coverage Gate down with it via the missing shard upload). Three firings across three unrelated diffs in under 24 hours: the failure follows shard load, not the diff.

Why it matters

Every PR in the repo runs these tests; a scheduling-dependent trigger turns busy CI days into false-negative red boards and burns contributor CI rounds on failures their diffs cannot cause. The class is one day old (#8576), so this is its first hardening pass — left alone, it becomes a standing flake with a growing rerun tax.

What changed (motivation → approach → change)

  • Symptom: ordering assertions fail only on contended shards, for unrelated diffs.
  • Root cause (confirmed with a deterministic reproducer, hypothesis from the auto-triage comment on flaky: TestNotificationCopyWhenNoLiveFileExists ordering asserts race on loaded CI shards #8893): both tests patch os.open process-wide and fire their delivery trigger on the first O_CREAT open observed anywhere (if flags & os.O_CREAT and not fired). Locally that first open is the copy's own destination — taken while the copy occupies the single notification worker, so the queued append cannot run and the bounded wait times out exactly as intended. On a loaded shard, the first creating open can come from a foreign thread while the worker is still free: the append runs immediately, ran_during_copy sets, and the assertion reads a scheduling accident as a broken ordering guarantee.
  • Change: scope both triggers to the copy's own destination open (os.fspath(path) == dst). The destination is opened by full path at a single site (snapshot.py::_install_notifications, os.open(dst_path, O_CREAT|O_EXCL|...)), and it is the one open the copy performs while it holds the worker — so it is the only trigger that measures the serialisation these tests exist to prove. Per the issue's ask and the testing conventions: no retry, no sleep — the foreign-trigger case is removed, not tolerated.

Tests

  • test_the_ordering_trigger_ignores_a_foreign_O_CREAT_open (new): keeps the loaded-shard condition as a deterministic test. A churn thread is confirmed to have completed a creating open before the merge starts, so under the old first-O_CREAT-anywhere shape the trigger is guaranteed foreign and the test fails every run (reproduced before the fix: the churn open fires the trigger and the pin fails deterministically). Under the scoped shape the churn is invisible however the scheduler interleaves it; asserts the trigger fired on exactly the destination, the worker-held measurement stayed false (captured inside the trigger from the wait's return value, where the append's legitimate post-copy run cannot race it), and the note survived. Soaked 20/20 solo runs; an event-re-read variant of the same assertion failed the identical soak, which is what forced the in-trigger capture.
  • test_a_note_delivered_during_the_copy_survives_the_READER and test_a_FRESH_gateway_still_orders_the_copy_against_a_delivery (updated): destination-scoped triggers; their assertions — including the assert fired proved-something guard — are unchanged.
  • Full file green: 130 passed. Seam neighbors (test_dashboard.py, test_notification_phase5.py): 68 passed.

Manual verification

N/A — unit coverage sufficient: the flake condition itself is now a deterministic in-suite test, and the affected surface is test-only (no production code changed).

Related Issues

Fixes #8893

Pattern harvest

Rule candidate: review-prompt
Pattern: "a process-wide patch of a filesystem/syscall entry point must scope its trigger to the specific path or descriptor under test — 'the first call observed' is a scheduling assertion on loaded runners, not a behavioral one". Flag new tests that monkeypatch os.open/builtins.open and branch on flags alone without binding to the path they mean.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

Contribution License Agreement

Per the template placeholder (CLA text pending): offered under the same terms as my prior merged contributions to this repository (#8835).

…nation open (kirodotdev#8893)

Both ordering tests in TestNotificationCopyWhenNoLiveFileExists patch os.open
process-wide and fired their trigger on the FIRST O_CREAT open seen anywhere.
Locally that open is the copy's own destination, taken while the copy occupies
the single notification worker, so the queued append cannot run and the wait
times out as the test intends. On a loaded CI shard the first creating open can
belong to a foreign thread at a moment when the worker is still free -- the
append then runs immediately and the assertion reads a scheduling accident as
a broken ordering guarantee (observed on shards for PRs whose diffs never touch
this path).

Scope the trigger to the destination path itself: it is the one open the copy
performs while it holds the worker, so it is the only trigger that measures the
serialisation these tests are about. No retry, no sleep -- the fix removes the
foreign-trigger case instead of tolerating it, and a new test keeps the
loaded-shard condition deterministic: a churn thread is confirmed to have done
a creating open before the merge starts, guaranteed-foreign under the old
shape, invisible under the scoped one.
@javenciu
javenciu requested a review from a team as a code owner September 6, 2026 10:41
@javenciu
javenciu requested a review from patrigao September 6, 2026 10:41
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of 599c5cfc228bd559786df29c2ba8b0a244d4347e via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The base code confirms the claims: _install_notifications opens the destination by full path with O_CREAT|O_EXCL|... at a single site (snapshot.py:3009), the modified tests match the described trigger shape, and the fix scopes the process-wide os.open patch to that one open. The assert fired guard means a path-spelling mismatch fails loudly rather than passing vacuously, and the new regression test makes the loaded-shard condition deterministic (churn thread confirmed-started, stopped in finally) with no retries or sleeps — consistent with the repo's testing conventions. Test-only, root-cause fix, proportionate.

Design-Verdict: PASS

Root-cause fix at the right layer: the trigger now measures the destination open it was always meant to, and the flake condition itself became a deterministic test.

[DESIGN-REVIEWED] 599c5cf

@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 599c5cfc228bd559786df29c2ba8b0a244d4347e via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 599c5cf

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 599c5cfc228bd559786df29c2ba8b0a244d4347e via the fork AI-review pipeline; updated in place on each push.

Review details

Everything checks out. The production path confirms _copy_notifications_serialise_with_notification_writes submits _install_notifications onto the single-worker notif-io executor and blocks on .result(), so the destination O_CREAT open fires on the worker thread while it's occupied — the scoped trigger correctly measures serialisation. The destination home/notifications.jsonl matches dst, the churn thread writes only under tmp_path and is joined in finally, and the single-worker executor makes the ordering deterministic. The discovery pass's "no candidates" holds, and I found nothing groundable to add.

No findings.

[OPUS-REVIEWED] 599c5cf

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 6, 2026
@iamwhatever
iamwhatever enabled auto-merge (squash) September 6, 2026 12:05

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: test (1 file). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: test-only change to test/test_snapshot.py scoping the process-wide os.open trigger to the copy's own destination path so a foreign creating open on a loaded shard no longer fires the ordering assertion (#8893); no production file touched. CodeQL is not applicable on this fork PR (default-setup emits no check-run); SAST coverage is Semgrep only, latest run success with 0 annotations.

@iamwhatever
iamwhatever merged commit 36ffa67 into kirodotdev:main Sep 6, 2026
67 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 6, 2026
iamwhatever pushed a commit that referenced this pull request Sep 6, 2026
Both notification-copy ordering tests measured the guarantee AFTER
`_do_merge` returned, with `assert not ran_during_copy.is_set()`. By
that point the copy has released the single `notif-io` worker and the
worker runs the queued append -- which is the CORRECT behaviour the
tests exist to prove, and it sets the same event. So the assertion was
really measuring whether the main thread reached it before that
legitimate run: a footrace between the main thread finishing `_do_merge`
and the worker draining one trivial job. On a contended shard the main
thread loses, and shard 4 reddened for PRs whose diffs never touch this
path.

#8893/#8992 found and fixed a different cause in the same two tests --
the trigger fired on the first `O_CREAT` seen anywhere, so a foreign
thread's open submitted the append while the worker was still free. That
scoping is correct and is kept. It narrowed the flake without closing it
because the observation point was left alone.

The fix is the shape #8992 already introduced in
`test_the_ordering_trigger_ignores_a_foreign_O_CREAT_open` and did not
retrofit: capture the wait's own return value INSIDE the trigger, while
the copy still holds the worker, and assert on that. In that window only
the defect can set the event, so the measurement no longer competes with
correct behaviour.

Production is unchanged and sound: `_copy_notifications` wraps its whole
body in `_serialise_with_notification_writes`, so the destination
`O_CREAT|O_EXCL` open genuinely happens on the worker the copy occupies.
The assertion is not weakened -- with the serialisation removed all
three tests fail `[True] == [False]`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flaky: TestNotificationCopyWhenNoLiveFileExists ordering asserts race on loaded CI shards

2 participants