-
Notifications
You must be signed in to change notification settings - Fork 942
fix(service): do not fail a Windows cold start that is still coming up #3039
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
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 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { pathToFileURL } from "node:url"; | |||||||||
| import * as serviceModule from "../src/service"; | ||||||||||
| import { saveConfig } from "../src/config"; | ||||||||||
| import { windowsEnvIndirectBatchValue } from "../src/lib/win-paths"; | ||||||||||
| import { assertServiceAuthEnvironment, assertServiceEnvironmentMatchesInstall, bakedServicePathsDiagnostic, confirmServiceServing, launchdListenPort, systemdListenPort, buildPlist, buildUnit, buildWindowsLauncherVbs, buildWindowsSchtasksCreateArgs, buildWindowsSchtasksCreateArgsForXml, buildWindowsServiceScript, buildWindowsTaskXml, deriveWindowsServiceDiagnostic, deriveWindowsServiceDiagnosticForCurrentUser, installFreshWindowsSchedulerSafely, installServiceSafely, launchctlLoadFailed, launchdJobMatchesPlist, normalizeServiceSubcommand, parseServiceArgs, parseServiceInstallState, planServiceCommand, prepareServiceInstall, probeServiceInstallation, readWindowsSchedulerXmlState, registerFreshWindowsSchedulerTask, removeNativeWindowsServiceForScheduler, repairService, resolveServiceListenPort, runLaunchctl, selectServiceSubcommand, serviceLogPath, serviceStartableFromTray, serviceStatusReport, serviceRetryCommand, serviceStatusSummary, stableLauncherEntry, systemdNeedsDaemonReload, systemdServiceInstallCleanupOps, uninstallSystemd, windowsListenPort, winswListenPort, startLaunchd, windowsTaskRegistrationHealthy } from "../src/service"; | ||||||||||
| import { assertServiceAuthEnvironment, assertServiceEnvironmentMatchesInstall, bakedServicePathsDiagnostic, buildPlist, buildUnit, buildWindowsLauncherVbs, buildWindowsSchtasksCreateArgs, buildWindowsSchtasksCreateArgsForXml, buildWindowsServiceScript, buildWindowsTaskXml, confirmServiceServing, deriveWindowsServiceDiagnostic, deriveWindowsServiceDiagnosticForCurrentUser, installFreshWindowsSchedulerSafely, installServiceSafely, launchctlLoadFailed, launchdJobMatchesPlist, launchdListenPort, normalizeServiceSubcommand, parseServiceArgs, parseServiceInstallState, planServiceCommand, prepareServiceInstall, probeServiceInstallation, readWindowsSchedulerXmlState, registerFreshWindowsSchedulerTask, removeNativeWindowsServiceForScheduler, repairService, resolveServiceListenPort, runLaunchctl, selectServiceSubcommand, SERVICE_INSTALL_HEALTH_MS, serviceInstallHealthMs, serviceLogPath, serviceRetryCommand, serviceStartableFromTray, serviceStatusReport, serviceStatusSummary, stableLauncherEntry, startLaunchd, systemdListenPort, systemdNeedsDaemonReload, systemdServiceInstallCleanupOps, uninstallSystemd, windowsListenPort, windowsTaskRegistrationHealthy, winswListenPort } from "../src/service"; | ||||||||||
| import type { ServiceDiagnostic } from "../src/service"; | ||||||||||
| import { definitionCarriesCredential, resolvedProxyEnv, writeServiceDefinitionFile } from "../src/service"; | ||||||||||
| import { buildWinswXml } from "../src/lib/winsw"; | ||||||||||
|
|
@@ -3142,9 +3142,52 @@ describe("service serving confirmation", () => { | |||||||||
| now: () => 0, | ||||||||||
| timeoutMs: 0, | ||||||||||
| }); | ||||||||||
| expect(probes).toBe(1); | ||||||||||
| // At least once, which is what the name asks: a zero budget must not | ||||||||||
| // return without knocking. The exact count is not the contract — the | ||||||||||
| // deadline grace probe adds one when the caller did give it time. | ||||||||||
| expect(probes).toBeGreaterThanOrEqual(1); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| // #3009: a Windows cold start does NTFS ACL hardening and journal recovery | ||||||||||
| // before the listener exists, so the service can bind seconds after the | ||||||||||
| // deadline and then stay healthy. `ocx service repair` reported that as a | ||||||||||
| // terminal failure with exit 1, and the caller's fallback is to start a | ||||||||||
| // second proxy against a port that is about to be taken. | ||||||||||
| test("accepts a service that binds during the grace after the deadline", async () => { | ||||||||||
| let now = 0; | ||||||||||
| let probes = 0; | ||||||||||
| const out = await confirmServiceServing({ | ||||||||||
| port: 10100, | ||||||||||
| // Answers only once the clock is past the deadline, which is the shape | ||||||||||
| // the report describes: healthy, just not within the budget. | ||||||||||
| probe: async () => { probes += 1; return now > 2_000; }, | ||||||||||
| sleep: async ms => { now += ms; }, | ||||||||||
| now: () => now, | ||||||||||
| timeoutMs: 2_000, | ||||||||||
| }); | ||||||||||
| expect(out).toEqual({ ok: true, port: 10100 }); | ||||||||||
| expect(probes).toBeGreaterThan(1); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| test("still fails a service that never binds", async () => { | ||||||||||
| let now = 0; | ||||||||||
| const out = await confirmServiceServing({ | ||||||||||
| port: 10100, | ||||||||||
| probe: async () => false, | ||||||||||
| sleep: async ms => { now += ms; }, | ||||||||||
| now: () => now, | ||||||||||
| timeoutMs: 2_000, | ||||||||||
| }); | ||||||||||
| expect(out).toEqual({ ok: false, port: 10100 }); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| // Windows is the platform the extra budget exists for; everything else keeps | ||||||||||
| // the original 20s so this cannot slow a healthy Linux install down. | ||||||||||
| test("gives Windows a longer cold-start budget than the other platforms", () => { | ||||||||||
| expect(serviceInstallHealthMs("win32")).toBeGreaterThan(serviceInstallHealthMs("linux")); | ||||||||||
|
Comment on lines
+3186
to
+3187
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Assert the required Windows budget. Line 3187 accepts any value above 20 seconds, including an incorrect 30-second budget. The PR contract requires 45 seconds. Assert that Proposed test fix- expect(serviceInstallHealthMs("win32")).toBeGreaterThan(serviceInstallHealthMs("linux"));
+ expect(serviceInstallHealthMs("win32")).toBe(45_000);As per path instructions, tests under 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Path instructions |
||||||||||
| expect(serviceInstallHealthMs("linux")).toBe(SERVICE_INSTALL_HEALTH_MS); | ||||||||||
| expect(serviceInstallHealthMs("darwin")).toBe(SERVICE_INSTALL_HEALTH_MS); | ||||||||||
| }); | ||||||||||
| // A service reinstall invalidates the pidfile, so resolving the target through | ||||||||||
| // it (findLiveProxy) would report a serving service as dead. Ask the baked port. | ||||||||||
| test("probes the port it was given rather than resolving one", async () => { | ||||||||||
|
|
||||||||||
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restore the exact zero-budget assertion.
At Line 3148,
toBeGreaterThanOrEqual(1)also passes ifconfirmServiceServing({ timeoutMs: 0 })incorrectly performs a grace sleep and a second probe. The zero-budget contract requires only the immediate probe. UsetoBe(1)here, and keep the greater-than-one assertion in the positive-timeout grace test.Proposed test fix
As per path instructions, tests under
tests/**must provide focused regression coverage for behavior changes insrc/.📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Path instructions