Windows: a failed console takeover leaves the runner's console state unrestored

Summary

On Windows the legacy strategy takes over the console of the job process before it signals that process. The takeover is three steps, and each step returns early when it fails, before the block that puts the runner's console back:

terminateViaConsole

Two of those early returns leave the runner in a changed console state:

  • attachConsole fails after freeConsole succeeded. The runner then holds no console at all.
  • setConsoleCtrlHandler fails after the first two succeeded. The runner then holds the console of the job process, with its own Ctrl-C handler still enabled.

The restore block sits after the delivery call, so neither path reaches it. The state persists after the function returns.

Impact

A console is process-wide, so the state outlives the job that caused it:

  • A runner left with no console cannot raise a control event for any later job, because GenerateConsoleCtrlEvent needs the caller to hold one. That is the symptom of #39297 (closed), reached from a different direction. One failed takeover degrades every later cancellation on that runner to a force kill.
  • A runner left on the console of a terminated job holds a console that goes away with that process.

Severity is low. Both paths need the takeover to fail partway, which no report describes. The defect is that the code has no way back to a known console state once a takeover starts.

Proposal

Restore the console on every exit from the takeover, not only on the successful one. Undo the steps that already succeeded, and keep reporting the failure to the caller so it still force-kills.

Scope

Testable through the consoleAPI seam that !7275 (merged) adds. No Windows service and no new CI infrastructure needed.

Notes

Found while reviewing !7275 (merged). That merge request keeps a failed console restore from force-killing a healthy job, which is a separate concern, so this gap was left out of it.