From 28295f59122e70102ec5e228535dd2d29b6c857b Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 9 Aug 2026 10:37:02 +0100 Subject: [PATCH] Enhance Windows installer with parent process ID handling for improved update reliability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced a new variable to capture the parent process ID, allowing the installer to wait for the exact updater process instead of relying on executable names. - Updated the `restartFarmControlAfterUpdate` function to handle the new parent PID, ensuring a more reliable shutdown of the Farm Control application during updates. - Modified the `launchWindowsInstaller` function to pass the current process ID as an argument, enhancing the installer’s ability to manage application restarts effectively. --- packaging/windows/installer.nsh | 60 ++++++++++++++++++++++++++------- src/desktop/winappupdate.js | 9 ++++- 2 files changed, 56 insertions(+), 13 deletions(-) diff --git a/packaging/windows/installer.nsh b/packaging/windows/installer.nsh index 576eb06..898bb6b 100644 --- a/packaging/windows/installer.nsh +++ b/packaging/windows/installer.nsh @@ -6,6 +6,7 @@ Var ProgressLogFile Var IsInAppUpdate Var RestartAfterInstall Var FinalInstDir +Var UpdateParentPid ; Generate an include at compile time that defines APP_COPY_TOTAL_BYTES and ; copyApplicationFilesWithProgress. The generated macro contains one File @@ -23,6 +24,7 @@ Var FinalInstDir StrCpy $IsInAppUpdate "0" StrCpy $RestartAfterInstall "0" StrCpy $FinalInstDir "" + StrCpy $UpdateParentPid "0" ${GetParameters} $R9 @@ -38,6 +40,13 @@ Var FinalInstDir StrCpy $RestartAfterInstall "1" ${EndIf} + ClearErrors + ${GetOptions} $R9 "/PARENTPID=" $R8 + ${IfNot} ${Errors} + ; Convert to an integer before it is used in Win32 calls or taskkill. + IntOp $UpdateParentPid $R8 + 0 + ${EndIf} + ; Prefer env var so paths with spaces are reliable; /LOG= remains supported. ReadEnvStr $ProgressLogFile "FARMCONTROL_INSTALL_LOG" ${If} $ProgressLogFile == "" @@ -330,22 +339,49 @@ Function restartFarmControlAfterUpdate !insertmacro progressSuccess !insertmacro progressStatus "Waiting for Farm Control to close..." - Sleep 2000 - restart_wait_loop: - ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq FarmControl.exe" 2>nul | find /I "FarmControl.exe"' $R0 - ${If} $R0 == 0 - Sleep 1500 - Goto restart_wait_loop - ${EndIf} - ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq launcher.exe" 2>nul | find /I "launcher.exe"' $R0 - ${If} $R0 == 0 - Sleep 1500 - Goto restart_wait_loop + ${If} $UpdateParentPid > 0 + ; Wait for the exact updater process rather than an executable name. CEF + ; builds can keep bun/renderer processes alive after the launcher exits. + System::Call 'kernel32::OpenProcess(i 0x00100000, i 0, i $UpdateParentPid) p .r0' + ${If} $0 != 0 + ; Allow two minutes for a graceful CEF shutdown. + System::Call 'kernel32::WaitForSingleObject(p r0, i 120000) i .r1' + System::Call 'kernel32::CloseHandle(p r0)' + + ${If} $1 == 258 + !insertmacro progressStatus "Farm Control is taking too long to close; forcing shutdown..." + DetailPrint "Farm Control process $UpdateParentPid did not exit; terminating its process tree..." + ExecWait 'taskkill /F /T /PID $UpdateParentPid' $R0 + ${EndIf} ${EndIf} + ${Else} + ; Compatibility fallback for manually launched older installers. + StrCpy $R7 0 + restart_wait_loop: + ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq FarmControl.exe" 2>nul | find /I "FarmControl.exe"' $R0 + ${If} $R0 != 0 + ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq launcher.exe" 2>nul | find /I "launcher.exe"' $R0 + ${EndIf} + ${If} $R0 != 0 + Goto restart_wait_done + ${EndIf} + + IntOp $R7 $R7 + 1 + ${If} $R7 < 80 + Sleep 1500 + Goto restart_wait_loop + ${EndIf} + + !insertmacro progressStatus "Farm Control is taking too long to close; forcing shutdown..." + !insertmacro quitFarmControl + ${EndIf} + + restart_wait_done: + ; Give CEF descendants a moment to release DLLs before renaming the tree. + Sleep 3000 ${If} $IsInAppUpdate == "1" - Sleep 1000 !insertmacro swapUpdateStaging ${EndIf} diff --git a/src/desktop/winappupdate.js b/src/desktop/winappupdate.js index 6cd1c7d..a5b3462 100644 --- a/src/desktop/winappupdate.js +++ b/src/desktop/winappupdate.js @@ -262,9 +262,16 @@ export const launchWindowsInstaller = async ( // /S = silent (https://nsis.sourceforge.io/Reference/SilentInstall) // /UPDATE = in-app update (stage into Farm Control.new; swap after exit) // /RESTARTFC = installer waits for this process to exit, swaps folders, relaunches + // /PARENTPID = exact process to wait for (CEF may outlive the launcher) // /LOG= + FARMCONTROL_INSTALL_LOG = progress log // (installer:% / PHASE / STATUS / COPY_TOTAL / COPY_FILE) - const installerArgs = ['/S', '/UPDATE', '/RESTARTFC', `/LOG=${logPath}`] + const installerArgs = [ + '/S', + '/UPDATE', + '/RESTARTFC', + `/PARENTPID=${process.pid}`, + `/LOG=${logPath}` + ] const installerProcess = spawn(resolvedPath, installerArgs, { detached: true,