test(e2e): retry while a forward heals, and two checks that meant less than they read - #16
Merged
Conversation
A port-forward session the Pod ended is reopened on the same local port. The reopen rebinds that port before its stream to the Pod works, so for the whole window in between the port accepts a connection and resets it, and client-go gives up on a stream it cannot create only after 30 seconds. Nothing in the suite waited that long. send retried for dialTimeout, which is five seconds and is sized for one connection attempt, and dialForward retried a refused dial for the same five. A scenario therefore failed on a forward that was about to heal: one CI run lost console-oidc to "GET https://gateway/v1/collections/...: read: connection reset by peer", twenty-five seconds before the forward came back. Both budgets are now forwardHealDeadline, sized by the reopen rather than by a dial. send also repeats a request that carries an Idempotency-Key, which it never did before. The gateway answers a repeat of a keyed request from the receipt it wrote for the first, so the repeat is something it cannot tell apart from a retransmission. A method with no key is still sent once. The body is read into memory so a second attempt has one to send. TestSendOutlastsAPortThatResets is the budget written down: a listener that resets every connection for ten seconds and then answers. Against the five-second budget it fails with the CI run's own message.
The console renders "<selection> is not listed" whenever nsListed is false. That is false while the namespace list is still empty, and false again when the selection is absent from a list that arrived. The scenario checked the text with no evidence of which state the page was in, so it could only ever pass: a page that had fetched nothing showed the same words as the page under test. The check now waits for the namespace select to carry options beyond its placeholder, which is the page saying the list landed, and reads the panels again after that. Measured across three runs, the list had already landed at the old check, so the wait costs nothing and closes a hole rather than a slowdown. awaitRow's refetch says whether it asked. refetchCollections returned quietly when the Service control named nothing, which left awaitRow's failure blaming a list that never carried a row when the cause was a page that never named a Service to ask about. The failure now names which of the two happened.
arloliu
force-pushed
the
fix/e2e-forward-heal
branch
from
August 30, 2026 13:11
546f64d to
790c8a9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three repairs to the end-to-end harness, all from evidence gathered this session. No product code changes.
The forward heal budget
One CI run lost
console-oidcto this, twenty-five seconds before the forward came back:A reopen rebinds the local port before its stream to the Pod works. For the whole window in between the port accepts a connection and resets it, so
dialForward'sECONNREFUSEDretry never fires, and client-go gives up on a stream it cannot create only after 30 seconds.sendretried fordialTimeout— five seconds, sized for one connection attempt — anddialForwardfor the same five. Both are nowforwardHealDeadline, sized by the reopen.TestSendOutlastsAPortThatResetswrites the budget down: a listener that resets every connection for ten seconds and then answers. Against the five-second budget it fails with the CI run's own message,read: connection reset by peer.The bound this does not cover: 60s is one client-go stream timeout plus slack.
harness_test.goloopsopenForwardwithpollIntervalbetween attempts, so a dead path to the API server could burn 30s per attempt more than once. A future red run showingport-forward reopen failedmore than once means the budget, not the mechanism, is what to revisit.Repeating a request that carries an idempotency key
sendnever repeated anything but a GET or a HEAD. It now also repeats a request carrying anIdempotency-Key, which the gateway answers from the receipt it wrote for the first rather than acting twice. Nosendcaller sends a key today — the pgo scenario that exercises a repeated key builds its own request throughdo, notsend— so this changes no current behaviour. It is the mechanism the open POST thread needs, unit-tested byTestRepeatableOnlyWhereTheGatewayCannotTell, not a fix that fires today.This does not close the open POST thread. The
auth-oidc-browserPOSTs are login form submissions to the issuer over its NodePort — they never touch the forward, and repeating one could consume an authorization code. They stay unrepeated, correctly.Two checks that meant less than they read
"<selection> is not listed"renders whenevernsListedis false, which it is while the namespace list is still empty and when the selection is absent from a list that arrived. The scenario checked that text with no evidence of which state it was looking at. It now waits for the namespace select to carry options beyond its placeholder first.Honest framing: measured across three runs, the list had already landed at the old check point (4 options). This closes a hole; it did not catch a false pass.
refetchCollectionsreturned quietly when the Service control named nothing, soawaitRow's failure blamed a list that never carried a row when the cause could be a page that never named a Service. It now reports whether it asked, and the failure names which happened.What was checked and found not to be a defect
The handoff this work came from listed two missing
returns inapp.js—boot()andrequest()schedule anot_readyretry and then fall through to record the error.docs/specs/ui.mdasks for both halves: the errors table givesnot_readythe hint "the gateway is still syncing; the page retries every 2 seconds", which only makes sense on an error the page is showing, and the states table says an unsynced gateway leaves the shell loaded and retrying until the first 200. Adding areturnwould delete the only place a stuck operator learns why. Left alone.Verification
Full suite against a live cluster:
ok github.com/arloliu/profgate/test/e2e 695.394s.mise run check,lint, andproseclean.