-
Notifications
You must be signed in to change notification settings - Fork 252
fix(release): bound the Windows process probe so its poll deadlines hold #3241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7f61f1e
3ecb005
6532f7a
090158d
8a4a8d6
119d9da
55ce142
af023a7
5543fba
7eeb843
c611cfd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,10 @@ function delay(milliseconds) { | |
| return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)); | ||
| } | ||
|
|
||
| export async function listInstalledProcesses(installDirectory, { run = runCommand } = {}) { | ||
| export async function listInstalledProcesses( | ||
| installDirectory, | ||
| { run = runCommand, timeoutMs = 10_000 } = {}, | ||
| ) { | ||
| const root = `${resolve(installDirectory)}${sep}`; | ||
| const script = String.raw` | ||
| $root = [IO.Path]::GetFullPath(${powerShellLiteral(root)}) | ||
|
|
@@ -37,7 +40,21 @@ $matches = @( | |
| ) | ||
| $matches | ConvertTo-Json -Compress | ||
| `; | ||
| const { stdout } = await run('powershell', ['-NoProfile', '-NonInteractive', '-Command', script]); | ||
| // Bounded because this probe runs inside deadline-bounded poll loops, and a | ||
| // loop only checks its deadline between probes: an unbounded one makes the | ||
| // deadline a lie. A `Get-CimInstance Win32_Process` query hung for the rest | ||
| // of the job during an auto-update relaunch, so the 120s relaunch deadline | ||
| // never fired and the runner cancelled the run an hour later. | ||
| // | ||
| // Well under the 60s and 120s budgets above it, so a stalled probe leaves | ||
| // room for several more attempts rather than consuming the whole window: a | ||
| // loop that can only try twice cannot survive a stall, which is the entire | ||
| // point of tolerating one. | ||
| const { stdout } = await run( | ||
| 'powershell', | ||
| ['-NoProfile', '-NonInteractive', '-Command', script], | ||
| { timeoutMs }, | ||
| ); | ||
| if (!stdout.trim()) return []; | ||
| const parsed = JSON.parse(stdout); | ||
| return Array.isArray(parsed) ? parsed : [parsed]; | ||
|
|
@@ -53,16 +70,38 @@ export async function waitForInstalledProcessesToExit( | |
| } = {}, | ||
| ) { | ||
| const deadline = Date.now() + timeoutMs; | ||
| let processes = await listProcesses(installDirectory); | ||
| while (processes.length > 0) { | ||
| let processes = []; | ||
| let observed = false; | ||
| let probeError; | ||
| for (;;) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Land the injected-probe tests you describe in the PR body, or correct the checklist. There is no |
||
| try { | ||
| processes = await listProcesses(installDirectory); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] Derive the probe's bound from the time the loop has left, rather than letting the callee's default decide. This call passes no options, so each attempt is bounded only by |
||
| observed = true; | ||
| probeError = undefined; | ||
| if (processes.length === 0) return; | ||
| } catch (error) { | ||
| // A bounded probe that fails says nothing about the processes, so the | ||
| // loop's own deadline stays the authority — the Windows process query | ||
| // stalls while an installer is churning the process table, and one | ||
| // stalled probe must not decide a verification. | ||
| probeError = error; | ||
| } | ||
| if (Date.now() >= deadline) { | ||
| // Never having seen the process table is a different fault from seeing | ||
| // processes that would not exit, and only one of them is about Maka. | ||
| if (!observed) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Key this branch on whether the LAST probe succeeded, not on whether any probe ever did. |
||
| throw new Error( | ||
| `Could not read the process table within ${timeoutMs}ms, so whether installed Maka processes exited is unknown.\nLast process probe failed: ${probeError?.message}`, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] Truncate the probe error before interpolating it. |
||
| ); | ||
| } | ||
| const summary = processes.map(({ processId, name }) => `${name} (${processId})`).join(', '); | ||
| throw new Error( | ||
| `Installed Maka processes did not exit within ${timeoutMs}ms: ${summary || '<unknown>'}.`, | ||
| `Installed Maka processes did not exit within ${timeoutMs}ms: ${summary || '<unknown>'}.${ | ||
| probeError ? `\nThe last probe also failed: ${probeError.message}` : '' | ||
| }`, | ||
| ); | ||
| } | ||
| await sleep(pollIntervalMs); | ||
| processes = await listProcesses(installDirectory); | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Scope this claim, and fix or hand off the loop it does not hold for. "A stalled probe leaves room for several more attempts" is true of
waitForInstalledProcessesToExitbelow, because you gave it a try/catch. It is not true of the other caller:scripts/verify-windows-autoupdate.mjs:405awaitslistInstalledProcesses(installDirectory)bare inside its 120s relaunch loop, with no try/catch anywhere inverifyWindowsAutoupdate, so one rejected probe propagates out and fails the whole auto-update verification on the first stall. That loop is the one the incident in lines 45–47 actually happened in. This PR still improves it — an hour-long hang becomes a 10s failure — but it converts a hang into a hard failure rather than into the tolerated retry this comment describes, and a healthy-but-slow probe that would previously have completed now fails a release verification instead. Confirmed by reading both files at this head; also confirmed that #3265 at23e8a8bc9does not touch lines 401–413, so neither PR currently owns it. Either wrap that probe in the same try/catch and let the 120s deadline decide, or narrow this comment to the function it describes and say who picks up the relaunch loop. Regression test: makelistInstalledProcessesinjectable intoverifyWindowsAutoupdate, have it reject for the first N calls and then report the relaunched process, and assert the relaunch wait still succeeds.