fix(update): Windows progress server hands out its URL only once it is serving
`Start-UiServer` printed the -SelfTestUi URL (and opened the browser window) as soon as the TcpListener was bound, but the runspace that answers /progress starts asynchronously — BeginInvoke returns before the pipeline is open and the script block is JIT'd, which is seconds on a loaded runner. The kernel accepted connections into the backlog during that gap and nobody answered them. The self-test hit it three times (#90371 and two follow-ups each widened a timeout instead of removing the race) and it just failed an unrelated hermes_state.py PR (run 33591547099, two 5s stale-backlog timeouts = red). - windows.ps1: readiness handshake after BeginInvoke — one /progress round-trip must succeed (≤15s) before the server is returned; on failure tear the listener down and continue without UI. The URL now means "serving", not "bound". Also fixes the browser opening to a page that never loads on a slow machine. - test: 1s per-attempt probe timeout so a single dead backlog socket cannot consume half the readiness budget. - CI: new `desktop_updater` classifier lane. tests/test_desktop_update_windows_*.py spawn the real PowerShell script; the Windows-only job now runs them only when scripts/desktop-update/**, the Electron updater launcher, conftest, pyproject, or those tests change (push/dispatch fail open). A PR that never touched that surface cannot be failed by its process timing.
This commit is contained in:
@@ -35,17 +35,24 @@ def _read_progress(url: str, deadline: float) -> dict[str, object]:
|
||||
``urlopen(timeout=5)`` propagating TimeoutError was exactly the Aug 2026
|
||||
flake (run 32440286339). Only a listener that stays unresponsive until
|
||||
the deadline fails the test.
|
||||
|
||||
Per-attempt timeout is 1s, not 5s: a connection the kernel accepted into
|
||||
the backlog before the runspace was serving never gets answered, and a 5s
|
||||
wait on it burned half the readiness budget per attempt (two stale
|
||||
attempts = red, run 33591547099). The script's own readiness handshake
|
||||
now keeps that gap from reaching us, but the probe should not be able to
|
||||
lose the whole budget to one dead socket either way.
|
||||
"""
|
||||
last_exc: Exception | None = None
|
||||
attempted = False
|
||||
while not attempted or time.monotonic() < deadline:
|
||||
attempted = True
|
||||
try:
|
||||
with urlopen(f"{url}progress", timeout=5) as response:
|
||||
with urlopen(f"{url}progress", timeout=1) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
except (TimeoutError, OSError) as exc: # transient stall — retry
|
||||
last_exc = exc
|
||||
time.sleep(0.2)
|
||||
time.sleep(0.1)
|
||||
raise AssertionError(
|
||||
f"/progress unresponsive until deadline (last error: {last_exc!r})"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user