From 701288fa2245821761337b22f3fe5170b86c3d55 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Tue, 21 Jul 2026 14:50:44 +0530 Subject: [PATCH] feat(async-config-poll): add BLOCK_BOOT_ON_WATCH to block boot on the config watch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Aditya Sharma --- .../config/ConfigWatchService.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/async-config-poll/src/main/java/com/example/asyncconfig/config/ConfigWatchService.java b/async-config-poll/src/main/java/com/example/asyncconfig/config/ConfigWatchService.java index dd6364b2..609c0329 100644 --- a/async-config-poll/src/main/java/com/example/asyncconfig/config/ConfigWatchService.java +++ b/async-config-poll/src/main/java/com/example/asyncconfig/config/ConfigWatchService.java @@ -58,8 +58,24 @@ public void init() { log.info("ConfigWatchService initialized; featuresEnabled={} appConfigVersion={}", featuresEnabled, appConfigVersion); - // (2) Background watch long-poll. - startWatchPoller(); + // (2) Config watch. BLOCK_BOOT_ON_WATCH (replay-only) makes boot BLOCK + // synchronously on a watch=true long-poll — the Flipkart shape. With the + // old anchor-hold async engine this poll is parked at replay (its + // delivery anchored past the reachable window), so the app never becomes + // ready = boot deadlock; the value-epoch engine serves the startup epoch + // immediately so boot proceeds. When unset (the record path), the watch + // runs on a background daemon and does not block boot. + if ("true".equalsIgnoreCase(System.getenv("BLOCK_BOOT_ON_WATCH"))) { + String url = baseUrl + "/v1/buckets/app-config?watch=true&version=" + appConfigVersion; + log.info("BLOCK_BOOT_ON_WATCH: blocking boot on synchronous config watch {}", url); + @SuppressWarnings("unchecked") + Map resp = rt.getForObject(url, Map.class); + log.info("BLOCK_BOOT_ON_WATCH: synchronous config watch returned (version now {}); boot continues", + intFrom(resp, "version", appConfigVersion)); + } else { + // Background watch long-poll (record path). + startWatchPoller(); + } } private void startWatchPoller() {