Fix Tentacle: switch SCM-launch to legacy Host.CreateDefaultBuilder().UseWindowsService()
Summary
-
Production fix: PR #274's
Host.CreateApplicationBuilder+services.AddWindowsService()doesn't actually work — SCMsc starttimes out 1053. Switch to the legacyHost.CreateDefaultBuilder(args).UseWindowsService()API which is battle-tested for SCM -
Diagnostic file logging: writes critical-path events to
%ProgramData%\Squid\Tentacle\scm-diagnostic.logsince SCM-launched binaries have no console -
Re-introduces PR #276's E2E test
R3hScm(closed-without-merge) — now harvests the diagnostic log on failure
Root cause analysis
PR #274 used the .NET 8+ minimal builder API:
var builder = Host.CreateApplicationBuilder(args); // registers ConsoleLifetime
builder.Services.AddWindowsService(...); // registers WindowsServiceLifetime (BOTH live)
AddWindowsService does NOT remove the pre-existing ConsoleLifetime. On a SCM-launched binary with no console, ConsoleLifetime initialization paths likely interfere with WindowsServiceLifetime's SCM connect. The result: SCM sees no SetServiceStatus(RUNNING) within 30s → sc.exe StartService FAILED 1053.
The legacy .UseWindowsService() extension explicitly does the right thing:
public static IHostBuilder UseWindowsService(this IHostBuilder hostBuilder, ...)
{
return hostBuilder.ConfigureServices((hostContext, services) =>
{
if (WindowsServiceHelpers.IsWindowsService())
{
services.RemoveAll<IHostLifetime>(); // ← KEY: removes ConsoleLifetime
services.AddSingleton<IHostLifetime, WindowsServiceLifetime>();
}
});
}
What this fixes
| State | Before | After |
|---|---|---|
sc start squid-tentacle |
30s timeout, [SC] StartService FAILED 1053
|
Reaches RUNNING within ~3-10s |
sc stop squid-tentacle |
(never tested — couldn't get past start) | Cleanly transitions to STOPPED |
Documented operator workflow service install + sc start
|
BROKEN | WORKS |
Diagnostic file logging
Even with the fix, future regressions in this code path are hard to debug because SCM-launched binaries have no console. Added critical-path file logging:
- Path:
%ProgramData%\Squid\Tentacle\scm-diagnostic.log(falls back to%TEMP%if ProgramData unwritable) - Events: SCM lifetime entry, host build start, host build complete,
host.RunAsyncstart,host.RunAsyncreturn, hosted service entered, hosted service exit code, exceptions - Best-effort: never throws; lock-protected; append-only
- Test harvests this log on failure → actionable assertion messages even when CI logs are opaque
E2E test re-introduction
TentacleWindowsScmLaunchedRealBinaryE2ETests.R3hScm_RealBinary_LaunchedThroughSCM_ScriptDispatchRoundTripsAndStopsCleanly (originally PR #276):
- Stub server start
- Pre-create instance
- Register polling
-
service install(sc create + sc start) -
Wait SCM state == RUNNING (45s budget) ← THE PIN — pre-fix this would fail with
1053 - Wait polling channel queryable
- Halibut script dispatch round-trip ← THE OTHER PIN
service stop- Wait SCM state == STOPPED
- Cleanup
On failure of steps 5/6/9, the test reads the production diagnostic log + includes contents in the assertion message.
Test plan
-
dotnet buildall projects — 0 errors -
dotnet test tests/Squid.Tentacle.Tests— 1415/1415 passing locally -
CI on windows-latestTentacle Windows E2E:-
R3hScm_RealBinary_LaunchedThroughSCM_ScriptDispatchRoundTripsAndStopsCleanlypasses (THE pin for the production fix) -
All other R1h/R2h/smoke tests still green
-
-
If R3hScm fails, the diagnostic log harvest will show exactly where the SCM lifetime hung — actionable next-step
Notes
This is the production fix that PR #276 was waiting for. Operators on Windows shipping 1.6.4 currently see service-install fail; this PR closes that gap. Should land before any 1.6.5 release.