feat(async-config-poll): add BLOCK_BOOT_ON_WATCH to block boot on the config watch#148
Merged
Merged
Conversation
… config watch Env-gated (replay-only) mode: init() does a synchronous GET /v1/buckets/app-config?watch=true and blocks boot until it returns, the shape a synchronously-blocking config client hits. Lets the keploy async-config-poll e2e reproduce the boot deadlock — the old anchor-hold async engine parks the poll (app never ready) while the value-epoch engine serves the startup epoch immediately (app boots). When unset (default / record path, and the periodic + httppoll scenarios) the watch keeps running on the background daemon, unchanged. Pairs with keploy/keploy#4379. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com>
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 21, 2026
Adds a third async-config-poll cell that records the config watch as a background poll (as httppoll) but REPLAYS with the app boot-blocking synchronously on the watch=true poll (BLOCK_BOOT_ON_WATCH). This exercises the boot-deadlock shape: the value-epoch engine must serve the startup epoch so the app boots and the ingress tests pass; the old anchor-hold engine would park the poll and the app would never become ready. - java-linux.sh: add the boot_blocking scenario (REPLAY_APP_ENV override so record and replay can drive the app differently), assert the app's boot-watch was served (not parked), and reuse the poll:true/no-pollDurationMs record-side check. Also corrects the stale matrix comment. - java_linux.yml: add boot_blocking to the scenario matrix. Requires keploy/samples-java#148 (BLOCK_BOOT_ON_WATCH app support) in the checked-out sample. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com>
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 21, 2026
…h (temporary) The boot_blocking async-config-poll cell needs the app's BLOCK_BOOT_ON_WATCH support (keploy/samples-java#148), which isn't on samples-java's default branch yet. Pin the async-config-poll job's samples-java checkout to that branch so the cell is exercised now; repoint to the default branch once #148 merges. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com>
gouravkrosx
approved these changes
Jul 22, 2026
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 22, 2026
…merged) keploy/samples-java#148 is merged, so the async-config-poll job no longer needs the temporary branch pin — checkout the default branch, which now carries BLOCK_BOOT_ON_WATCH for the boot_blocking scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com>
Aditya-eddy
added a commit
to keploy/keploy
that referenced
this pull request
Jul 22, 2026
…t deadlock) (#4379) * feat(async): per-lane throttleMs with 1s default Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): ResponseValueKey for record-time change detection Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): value-epoch selection in Decide (serve current epoch by completed) Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): throttle-bounded poll hold with wake-early-on-change Decide now holds each poll up to lane.ThrottleDuration() via holdThrottle, a bounded cond.Wait that wakes early on an AdvanceWindow/OnTestComplete broadcast or ctx cancel -- pacing unchanged polls without a busy loop. Also sets ThrottleMs:10 on the Task 3 tests' lanes so the suite stays fast under the reintroduced default 1s hold. Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * feat(async): record poll lanes as collapsed value epochs (drop pollDurationMs) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * test(async): record->replay epoch contract (boot answered, change at anchor) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * fix(async): gate replay throttle hold to poll lanes only Engine.Decide called e.holdThrottle unconditionally for every lane with loaded epochs, so a non-poll async lane (Type not ending in "Poll") paid up to ThrottleDuration() (default 1s) before serving its current epoch, even though only poll lanes need pacing against busy-looping an unchanged long-poll answer. Gate the hold behind lane.IsPoll() so non-poll lanes serve immediately. Add TestDecideNonPollLaneSkipsThrottle proving a non-poll lane with ThrottleMs: 5000 returns in well under 500ms. The three pre-existing throttle tests (TestDecideHeldUpToThrottle, TestDecideWakesEarlyOnAdvance, TestDecideReturnsOnCtxCancel) used Type: "fake", which was never actually a poll lane; switch them to "fakePoll" (BaseType still resolves to "fake") so they keep exercising real hold/wake-early/ctx-cancel behavior instead of silently passing without ever entering the hold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * docs(async): fix stale LogReport comment for the value-epoch model LogReport's doc comment still described an "armed-but-never-polled (not-exercised)" state, which cannot occur under the value-epoch model: every epoch is always re-selectable/serveable once effective, so nothing arms and then goes unpolled. Update the comment to say what the verdict actually reports (served/shape_flags) and note that not_exercised/held are retained in the emitted log line only for output-format stability (the periodic CI cell greps the line) and are always 0 now. Comment-only: the emitted fields, their names, and Report()'s values are unchanged, since CI greps "not_exercised": N in the log line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * test(proxy): give TestLoadAsyncMocksForwardsToEngine an explicit ThrottleMs Its lane (Type: "fake") is non-poll, so IsPoll() gates it out of the throttle hold entirely now; ThrottleMs: 10 documents that intent explicitly instead of relying on the omitted-field default (1s) never being reached. Test still asserts Decide serves "b", the newest same-AnchorPos epoch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * ci(java): update async_config_poll httppoll assertions for the epoch model Two httppoll-scenario assertions were written against the retired open-duration model and no longer hold: - Record-side: pollDurationMs no longer exists on AsyncMeta, so grep -aq 'pollDurationMs:' always fails now. Replace with proof that the poll mock is recorded as a value epoch: 'poll: true' present AND pollDurationMs: absent. - Replay-side: Engine.Decide no longer holds a poll's connection open waiting for its anchor (Report()/LogReport hard-code held=0 under the value-epoch model), so held >= 1 always fails now. Replace with the served >= 1 proof already established generically above (reusing the served shell variable already parsed from the "async egress verdict" log line) — proving the recorded poll value was served on replay. The periodic cell's checks are untouched. Updated the surrounding doc comments to match. Verified with `bash -n` (no syntax error); the Java e2e itself cannot run locally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * review(async): document all-lanes epoch selection; pin non-poll behavior Address PR #4379 review: - Major: Decide's current-epoch SELECTION applies to every async lane (poll + non-poll), not just poll lanes — documented explicitly, and non-poll re-selectable behavior pinned by TestNonPollServesReselectableCurrentEpoch so a future change can't silently revert to one-shot delivery. - holdThrottle comment: guarantee is 'answered within at most d', not 'spaced d apart'; shared cond wakes all parked polls; paces all polls (not only unchanged). - epoch_contract_test: use Type 'fakePoll' so the boot-answered assertion genuinely covers the poll holdThrottle path it narrates. - ResponseValueKey: note the required-interface bump; verified no external AsyncParser implementer in keploy-enterprise or k8s-proxy; document that a header-only value signal is out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * ci(async): add boot_blocking scenario to async-config-poll e2e Adds a third async-config-poll cell that records the config watch as a background poll (as httppoll) but REPLAYS with the app boot-blocking synchronously on the watch=true poll (BLOCK_BOOT_ON_WATCH). This exercises the boot-deadlock shape: the value-epoch engine must serve the startup epoch so the app boots and the ingress tests pass; the old anchor-hold engine would park the poll and the app would never become ready. - java-linux.sh: add the boot_blocking scenario (REPLAY_APP_ENV override so record and replay can drive the app differently), assert the app's boot-watch was served (not parked), and reuse the poll:true/no-pollDurationMs record-side check. Also corrects the stale matrix comment. - java_linux.yml: add boot_blocking to the scenario matrix. Requires keploy/samples-java#148 (BLOCK_BOOT_ON_WATCH app support) in the checked-out sample. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * ci(async): pin samples-java checkout to the BLOCK_BOOT_ON_WATCH branch (temporary) The boot_blocking async-config-poll cell needs the app's BLOCK_BOOT_ON_WATCH support (keploy/samples-java#148), which isn't on samples-java's default branch yet. Pin the async-config-poll job's samples-java checkout to that branch so the cell is exercised now; repoint to the default branch once #148 merges. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> * ci(async): checkout samples-java default branch (BLOCK_BOOT_ON_WATCH merged) keploy/samples-java#148 is merged, so the async-config-poll job no longer needs the temporary branch pin — checkout the default branch, which now carries BLOCK_BOOT_ON_WATCH for the boot_blocking scenario. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Aditya Sharma <aditya282003@gmail.com> --------- Signed-off-by: Aditya Sharma <aditya282003@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
What & why
Adds an env-gated
BLOCK_BOOT_ON_WATCHmode toConfigWatchServiceso the app can boot-block on the config-watch long-poll — the shape that exposes the async value-epoch fix in keploy.Today the sample's config watch runs on a background daemon (boot never depends on it), so it can't reproduce the real-world boot deadlock a synchronously-blocking config client hits: at replay the old anchor-hold async engine parks a poll whose delivery is anchored past the reachable test window, and the app never becomes ready.
With
BLOCK_BOOT_ON_WATCH=true,init()performs a synchronousGET /v1/buckets/app-config?watch=trueand blocks boot until it returns:connection refused(the deadlock).When the env is unset (the default / record path, and the existing
periodic+httppollscenarios), the watch keeps running on the background daemon exactly as before — no behavior change.Pairs with
keploy/keploy#4379 (async value-epoch replay). That PR adds a
boot_blockingcell to theasync-config-polle2e which records the app normally and replays it withBLOCK_BOOT_ON_WATCH=true, asserting the app boots and the ingress tests pass — i.e. the value-epoch engine serves the startup epoch instead of parking it. That CI cell needs this env support, so this PR should merge first.🤖 Generated with Claude Code