fix(update): wait for the old process, not for a fixed window
The relaunch after an update stopped working, and the regression is mine: removing the PowerShell helper — which is what Defender was reading as a dropper — also removed the wait it was doing. Nothing took over the job. The numbers made it certain rather than unlucky. The instance being replaced is allowed THIRTY seconds to shut down (armExitWatchdog forces it out at that point) because it closes a remote logbook, a CAT session and sometimes a backup. The new instance was patient with the single-instance mutex for TWENTY. On any station where shutting down ran past that, the new process gave up and exited in silence: no window after an update, and the previous OpsLog still in the task manager. Exactly the report. Both relaunch paths now pass --wait-pid, and the new process waits on that process's handle — a plain kernel wait, which ends the instant the old one ends, however long or short that is, and looks nothing like a script starting another program. The mutex retry stays as a backstop and goes to forty-five seconds, so it is longer than the wait it exists for rather than shorter. And when the old process really has not gone, the message says that instead of "OpsLog is already running" — after an update the operator did not start a second copy, and what they need to know is which one to close. A test keeps the two spawn sites honest: a relaunch added without --wait-pid is this bug again.
This commit is contained in:
@@ -10,6 +10,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -68,3 +69,35 @@ func focusExistingWindow() {
|
||||
showWindow.Call(hwnd, swRestore)
|
||||
setForeground.Call(hwnd)
|
||||
}
|
||||
|
||||
// waitForProcessExit blocks until the process with this pid is gone, or the
|
||||
// timeout runs out. Reports whether it actually went.
|
||||
//
|
||||
// This is what the auto-update relaunch needs, and it is the piece that was
|
||||
// lost when the PowerShell helper went. The old instance is allowed thirty
|
||||
// seconds to shut down (see armExitWatchdog) — it closes a remote logbook, a
|
||||
// CAT session, sometimes a backup — while the new one was only patient with the
|
||||
// mutex for twenty. On a station where shutdown took longer than that, the new
|
||||
// process gave up and exited, and the operator was left with the old one still
|
||||
// running and no new window: exactly the report.
|
||||
//
|
||||
// Waiting on a handle rather than sleeping a fixed time is also the honest
|
||||
// version: it ends the instant the old process ends, however long or short that
|
||||
// is, and it is a plain kernel wait — nothing that looks like a script starting
|
||||
// another program.
|
||||
func waitForProcessExit(pid int, timeout time.Duration) bool {
|
||||
if pid <= 0 {
|
||||
return true
|
||||
}
|
||||
h, err := windows.OpenProcess(windows.SYNCHRONIZE, false, uint32(pid))
|
||||
if err != nil {
|
||||
// Already gone, or not ours to wait on. Either way there is nothing to
|
||||
// wait for — and refusing to launch over an unexpected permission error
|
||||
// would be worse than starting.
|
||||
return true
|
||||
}
|
||||
defer windows.CloseHandle(h)
|
||||
ms := uint32(timeout / time.Millisecond)
|
||||
ev, err := windows.WaitForSingleObject(h, ms)
|
||||
return err == nil && ev == uint32(windows.WAIT_OBJECT_0)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user