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.
49 lines
1.5 KiB
Go
49 lines
1.5 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestWaitPidArg(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
args []string
|
|
want int
|
|
}{
|
|
{"after the flag", []string{"--post-update", "--wait-pid", "4321"}, 4321},
|
|
{"joined with =", []string{"--wait-pid=4321"}, 4321},
|
|
{"absent", []string{"--post-update"}, 0},
|
|
{"flag with nothing after it", []string{"--wait-pid"}, 0},
|
|
{"not a number", []string{"--wait-pid", "later"}, 0},
|
|
} {
|
|
if got := waitPidArg(tc.args); got != tc.want {
|
|
t.Errorf("%s: got %d, wanted %d", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every relaunch has to tell the new process which one to wait for.
|
|
//
|
|
// The auto-update relaunch lost that when its PowerShell helper was removed —
|
|
// the helper had waited for the pid, and nothing took over the job — and an
|
|
// operator was left with no window after an update and the previous OpsLog
|
|
// still running. This keeps the two spawn sites honest: if a relaunch is added
|
|
// without --wait-pid, it is the same bug again.
|
|
func TestEveryRelaunchPassesItsPid(t *testing.T) {
|
|
spawn := regexp.MustCompile(`exec\.Command\(exe, "--(post-update|relaunch)"[^)]*\)`)
|
|
for _, file := range []string{"update.go", "app.go"} {
|
|
src, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", file, err)
|
|
}
|
|
for _, call := range spawn.FindAllString(string(src), -1) {
|
|
if !strings.Contains(call, "--wait-pid") {
|
|
t.Errorf("%s: %s does not tell the new instance which process to wait for", file, call)
|
|
}
|
|
}
|
|
}
|
|
}
|