Skip to content

Fix Tentacle: switch SCM-launch to legacy Host.CreateDefaultBuilder().UseWindowsService()

Placeholder ppxd requested to merge fix/use-windows-service-scm-start into main

Summary

  • Production fix: PR #274's Host.CreateApplicationBuilder + services.AddWindowsService() doesn't actually work — SCM sc start times out 1053. Switch to the legacy Host.CreateDefaultBuilder(args).UseWindowsService() API which is battle-tested for SCM
  • Diagnostic file logging: writes critical-path events to %ProgramData%\Squid\Tentacle\scm-diagnostic.log since 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.RunAsync start, host.RunAsync return, 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):

  1. Stub server start
  2. Pre-create instance
  3. Register polling
  4. service install (sc create + sc start)
  5. Wait SCM state == RUNNING (45s budget) ← THE PIN — pre-fix this would fail with 1053
  6. Wait polling channel queryable
  7. Halibut script dispatch round-trip ← THE OTHER PIN
  8. service stop
  9. Wait SCM state == STOPPED
  10. Cleanup

On failure of steps 5/6/9, the test reads the production diagnostic log + includes contents in the assertion message.

Test plan

  • dotnet build all projects — 0 errors
  • dotnet test tests/Squid.Tentacle.Tests — 1415/1415 passing locally
  • CI on windows-latest Tentacle Windows E2E:
    • R3hScm_RealBinary_LaunchedThroughSCM_ScriptDispatchRoundTripsAndStopsCleanly passes (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.

🤖 Generated with Claude Code

Merge request reports

Loading