Operators are still losing the relaunch, so I went and read the code
from before 0430aab — the commit that removed the helper — instead of
theorising again. It was:
Wait-Process -Id <pid>; Start-Sleep -Milliseconds 400;
Start-Process -FilePath <exe> -ArgumentList '--post-update'
Two things in there that the direct launch never had:
1. Start-Process made the new OpsLog a child of PowerShell, which then
exited — so it was detached. The direct launch makes it a child of
the instance that is dying, in the same process group and console.
DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP puts it back on its own,
so nothing aimed at the old process can reach the new one.
2. It slept 400 ms AFTER the old process was gone, before starting
anything. A process's handles are released by the kernel as it dies,
so the mutex is free the moment the wait returns — but the things
around it are not on that clock: the WebView2 user-data lock, the log
file, an antivirus that woke up when the exe was replaced. This is
not a theory about which of those it was; it is the pause being put
back where it was.
Not restored: the PowerShell itself. Defender removed 0.27.14 from a
station as Trojan:Script/Wacatac.H!ml, and "Script/" was that helper —
an unsigned binary replacing itself and spawning a windowless script to
start another executable is, byte for byte, a dropper. Bringing it back
trades this fault for one that deletes the program.
Also: the relaunch now logs the child's pid, which the new instance's
startup.log already records on the other side. Without the pair there is
no telling "the new instance never started" from "it started and gave up
waiting", and those have different causes.
50 lines
2.3 KiB
Go
50 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
)
|
|
|
|
// relaunchCmd builds the command that starts OpsLog again — after an update, or
|
|
// after a database switch.
|
|
//
|
|
// It exists to hold one fact in one place: a relaunch of OPSLOG ITSELF must not
|
|
// suppress the new process's window.
|
|
//
|
|
// The auto-update relaunch used to go through hideConsole, which sets
|
|
// SysProcAttr{HideWindow: true}. On Windows that puts SW_HIDE into the
|
|
// STARTUPINFO handed to CreateProcess, and Windows applies it to the first
|
|
// top-level window the new process shows. So the updated OpsLog started
|
|
// perfectly, took the single-instance mutex, connected the rig — and never
|
|
// became visible. Reported by two operators on 0.27.23 as "it goes to reload
|
|
// and just fails to load": a process in the task manager, no window, killing it
|
|
// and starting it by hand working every time.
|
|
//
|
|
// It arrived with the removal of the PowerShell helper. PowerShell's
|
|
// Start-Process launched the exe with a normal show, and the direct
|
|
// exec.Command that replaced it borrowed hideConsole from the console tools
|
|
// beside it — where hiding a console window is exactly right, and where every
|
|
// other caller still belongs. Two self-relaunches then differed by that one
|
|
// line, and only the hidden one was ever reported broken.
|
|
//
|
|
// So: nothing that touches the WINDOW. RestartApp never called hideConsole and
|
|
// the database-switch relaunch never showed the fault, which is what proved it.
|
|
// There is no console to suppress either way — OpsLog is linked for the Windows
|
|
// GUI subsystem. What SysProcAttr does carry now is detachment, below, which is
|
|
// about process parentage and not about the window.
|
|
// Two things the PowerShell helper did that the direct launch did not, both
|
|
// restored here after operators kept reporting the relaunch failing:
|
|
//
|
|
// 1. It launched through Start-Process, so the new OpsLog was a child of
|
|
// PowerShell — which then exited. The direct launch makes it a child of
|
|
// the OpsLog that is dying, inside the same process group and console.
|
|
// detachProcess puts it back on its own.
|
|
// 2. It slept 400 ms after the old process was gone, before starting
|
|
// anything. See postExitSettle in main.go.
|
|
func relaunchCmd(exe string, args ...string) *exec.Cmd {
|
|
cmd := exec.Command(exe, args...)
|
|
cmd.Dir = filepath.Dir(exe)
|
|
detachProcess(cmd)
|
|
return cmd
|
|
}
|