fix(e2e): accept the stored attempt counter as proof of a retryable pause - #1335
Conversation
…ause The data plane reclaims a failed_retryable apply as soon as a driver's claim poll sees it, so the pause can leave the wire between two observation ticks. The k8s retryable-pause test then kept injecting kills into the recovery it was waiting on and timed out. The stored attempt counter advances only on a claim out of failed_retryable, so it is a durable witness of the same pause; observing either signal ends the injection.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
🟢 Approval recommended
The change is confined to E2E test logic and uses an authoritative storage-side signal to eliminate a known transient-wire-state flake without weakening existing assertions.
Pull request overview
This PR deflakes the Kubernetes E2E that verifies the data plane’s retryable pause behavior by accepting a durable storage-side signal (the data plane apply’s applies.attempt counter) as proof the pause occurred, even if the transient STATE_FAILED_RETRYABLE wire state is missed between polling ticks.
Changes:
- Extends the test’s polling stop condition to succeed when either the wire reports
STATE_FAILED_RETRYABLEor the storedattemptcounter advances past its pre-injection baseline. - Adds a small helper to read the data plane apply’s stored
attemptvalue and improves the timeout message to include both wire and attempt observations.
File summaries
| File | Description |
|---|---|
| e2e/k8s/retryable_pause_test.go | Updates the retryable-pause observation loop to treat an incremented stored attempt counter as durable evidence the retryable pause occurred, reducing flakiness when the wire state is too brief to observe. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
🤖 Adversarial correctness review — The diagnosis is right and the fix is the right shape: a reclaim with no cooldown can consume the pause faster than a 250 ms observation tick, so waiting on the wire alone is a race the test can't win, and a durable counter is the correct second witness. Observing before injecting, and keeping Everything here rests on one claim, so that's what I went after: does an advance in It holds, but not for the reason the PR states. A full census of the writers (11 Two increment paths, not one — but both require
1 — the header asserts what the test no longer proves (low)The bullet list at and the paragraph added at This matters a little more than a stale-comment nit because the coverage really does go away. I don't think that argues against the change — the test is named for the control plane holding open, the pause-visibility bullet was always incidental to that, and a race you can't win isn't coverage. But the header should stop claiming it. Rewording bullet 1 to the capability ("the data plane distinguishes a pause from a settled failure, so the control plane can tell them apart without inspecting per-table statuses") keeps the point that matters and drops the assertion about what this test observes. If wire rendering of the pause is worth pinning on its own, it wants a test that can win that race — a stopped apply, or a fake, rather than a 250 ms poll against a live reclaim. 2 — the stated mechanism is narrower than the guarantee (low)
// The counter advances only when a driver claims the apply out of
// failed_retryable, so an increase is durable proof that a retryable pause
// happened and recovery has already picked it upand the PR body's "written in exactly two places: the insert at create time and the claim transition out of The conclusion is unaffected: that path is gated on What's worth fixing is the shape of the claim, because the comment is the thing a future maintainer will trust. The guarantee the test depends on isn't "one specific transition writes this column" — it's "every path that advances Verified — the false-green guards, the reader that can't be killed, and why I couldn't mutation-test this one
The new reader cannot be killed by the injector, which I checked because it would have been a fresh flake.
The invariant the test now leans on is guarded by storage tests, so a regression fails CI instead of degrading this e2e into a tautology: Invariants. This test is a guard for ST-9 ( One aside, clearly not this PR's business: ST-9's Mutation testing: not possible for this one, and I'd rather say so than imply otherwise. This review was generated by Claude Code (claude-opus-5). |
aparajon
left a comment
There was a problem hiding this comment.
🤖 Approving. The deflake is correctly diagnosed and the second witness is the right one: an advance in applies.attempt really does prove the apply passed through failed_retryable, and require.Positive(t, kills) keeps a pause the test didn't cause from becoming a false green. Two low findings in the review comment, both about comments rather than code — the header still asserts the wire property the change retracts, and the "advances only when a driver claims the apply out of failed_retryable" claim misses a third writer (the operation-redispatch bump in apply_operations.go) whose gate happens to preserve the conclusion. Upholds ST-9 and RC-1; test-only, no *Enforced:* line moves.
This stamp was left by Claude Code (claude-opus-5).
… poll Also reconciles the scenario docstring with the either-witness exit and states the attempt counter's guard as it is written: it only advances out of failed_retryable, on the claim and on the sibling-operation redispatch.
|
Review response from Kiran's (@Kiran01bm) code review assessment agent (Amp / Claude Opus 4.5) Four of five findings on #1335 are fixed in the follow-up commit (plus a PR-body correction); the apply-log witness and the e2e lint gap are out of scope for this PR.
"The one thing that could have broken, verified" (buffered ticker explains the sub-tick pause) and the "Verified correct" section: no action. Source: adversarial review of #1335 at 085113b, generated by Kiran's code review agent (Claude Code / claude-opus-5); not posted on the PR. |
Deflakes the k8s retryable-pause e2e by accepting the data plane's stored attempt counter, alongside the wire state, as proof that the injected pause happened.
Why
TestK8s_DataPlaneRetryablePauseHoldsControlPlaneOpenUntilRecoverykills the data plane's target connections every 250 ms until it seesSTATE_FAILED_RETRYABLEon the wire, then stops injecting and waits for recovery. The pause is transient by design: a driver's claim poll reclaims afailed_retryableapply as soon as it sees it, and with several drivers polling there is no cooldown, so the wire can show the pause for less than one observation tick. When the test misses it, every subsequent kill lands on the recovery attempt it is supposed to be waiting for, and the loop times out after three minutes with the wire still reporting running.What
Every write that advances the data plane's
applies.attemptcolumn is gated on the apply being infailed_retryable: the claim transition out of it, and the sibling-operation redispatch under the same guard, each increment atomically inside their own transaction; the create-time insert seeds it. An increase past its pre-kill value is therefore durable proof that a retryable pause occurred and recovery has already claimed it. The poll now ends when either the wire shows the pause or the stored counter has advanced. The per-tick assertions are unchanged: the control plane must stay non-terminal, the wire must never settlefailed, and at least one kill must have landed. The 3-minute budget and 250 ms interval are unchanged.Before / after