Windows service: console restore failure force-kills a job that is stopping gracefully

Summary

On Windows the runner sends a console control event to stop a job process, then restores its own console state. taskTerminate collects the delivery result and the three restore results into one error value. windowsKiller.Terminate reads any error as a failed termination and calls ForceKill at once, before the graceful timeout in osKillWait.KillAndWait.

A runner that runs as a Windows service has no parent console, so the restore call AttachConsole(ATTACH_PARENT_PROCESS) always fails. The job process was signaled and is shutting down gracefully, but the runner kills it anyway.

Impact

FF_USE_WINDOWS_LEGACY_PROCESS_STRATEGY = true is the only known workaround for #39297 (closed). This defect turns that workaround into a race: the job process is signaled, then killed a moment later, so it only shuts down cleanly if it finishes first.

The reporter of #39297 (closed) observed exactly this:

Interestingly, AttachConsole does not fail to attach to the console of the process running the application, so this mechanism would work if it didn't go on to kill the process immediately afterward.

Proposal

Report only a failed delivery of the console control event. Log the restore failures. Restoring the runner's console happens after delivery, so a failure there says nothing about whether the job process was signaled.

Relationship to #39297 (closed)

This is not a new report. It is the third section of #39297 (closed) ("Running as a service with legacy process strategy"), split into its own issue because it has a separate root cause, a separate fix, and can be tested on its own.

The two defects are independent:

  • This issue: the runner discards a successful graceful termination. Error classification.
  • #39297 (closed): the runner cannot deliver the event at all on the default path, because a service holds no console. Needs the event to be routed through the console of the job process.