fix(update): auto-restart after Windows binary swap - #6
Conversation
Schedule platform restart immediately after successful update/rollback so Windows does not stay on the old in-memory process. Prefer native-control restart-app and force-kill stuck PIDs before relaunch.
📝 WalkthroughWalkthroughChangesService restart behavior
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant AdminClient
participant SystemHandler
participant sysutil.RestartServiceAsync
participant native-control.ps1
AdminClient->>SystemHandler: perform update or rollback
SystemHandler-->>AdminClient: return restart-initiated response
SystemHandler->>sysutil.RestartServiceAsync: invoke after delay
sysutil.RestartServiceAsync->>native-control.ps1: restart service
native-control.ps1-->>sysutil.RestartServiceAsync: restart result
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/internal/handler/admin/system_handler.go`:
- Around line 123-131: Keep system-operation exclusion active until the
scheduled restart handoff begins: update the release/lock flow around the update
restart callback so the 500ms delay does not allow another operation to acquire
the lock, and apply the same restart-pending handling in the rollback restart
callback. Modify both affected sites in
backend/internal/handler/admin/system_handler.go (lines 123-131 and 202-206),
preserving normal release behavior for operations that do not trigger a restart.
In `@backend/internal/pkg/sysutil/restart_windows.go`:
- Around line 81-85: Update the relaunch command construction in the Windows
restart flow to wrap native-control restart-app execution in PowerShell
try/catch. Ensure the start fallback runs both when restart-app throws a
terminating error and when it completes with a non-zero $LASTEXITCODE, while
preserving the final failure propagation when the fallback also fails.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 51a75ba8-17dd-4785-aa33-b530c81411f7
📒 Files selected for processing (4)
backend/internal/handler/admin/system_handler.gobackend/internal/handler/admin/system_handler_test.gobackend/internal/pkg/sysutil/restart_windows.gobackend/internal/pkg/sysutil/restart_windows_test.go
| // Schedule platform restart after the response is sent. | ||
| // On Windows the process that just swapped the binary still runs the | ||
| // old image; waiting for a second /restart click can leave the old | ||
| // process alive forever (pre-fix builds no-op, or the user never | ||
| // clicks). Linux relies on systemd Restart=always after exit. | ||
| go func() { | ||
| time.Sleep(500 * time.Millisecond) | ||
| sysutil.RestartServiceAsync() | ||
| }() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Keep system-operation exclusion active through the restart handoff.
The deferred release(..., succeeded=true) runs as each callback returns, while the restart waits another 500ms. A second update or rollback can therefore acquire the lock and begin before the first operation’s scheduled restart terminates it, risking an interrupted binary operation. Represent “restart pending” in the lock/status flow, or otherwise keep competing system operations excluded until handoff.
backend/internal/handler/admin/system_handler.go#L123-L131: prevent a new system operation from starting before the update-triggered restart begins.backend/internal/handler/admin/system_handler.go#L202-L206: apply the same restart-pending exclusion to rollback.
📍 Affects 1 file
backend/internal/handler/admin/system_handler.go#L123-L131(this comment)backend/internal/handler/admin/system_handler.go#L202-L206
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/internal/handler/admin/system_handler.go` around lines 123 - 131,
Keep system-operation exclusion active until the scheduled restart handoff
begins: update the release/lock flow around the update restart callback so the
500ms delay does not allow another operation to acquire the lock, and apply the
same restart-pending handling in the rollback restart callback. Modify both
affected sites in backend/internal/handler/admin/system_handler.go (lines
123-131 and 202-206), preserving normal release behavior for operations that do
not trigger a restart.
| relaunch = fmt.Sprintf( | ||
| `& %s start; if ($LASTEXITCODE -ne 0) { throw "native-control start failed: $LASTEXITCODE" }`, | ||
| `& %s restart-app; if ($LASTEXITCODE -ne 0) { & %s start }; `+ | ||
| `if ($LASTEXITCODE -ne 0) { throw "native-control restart/start failed: $LASTEXITCODE" }`, | ||
| psQuote(nativeControl), | ||
| psQuote(nativeControl), |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect native-control action dispatch and its failure signaling; do not execute it.
fd -a '^native-control\.ps1$' . -0 |
xargs -0 -r rg -n -C3 'restart-app|Unknown|throw|exit|ErrorActionPreference'Repository: 706412584/sub2api
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)native-control\.ps1$|restart_windows\.go$|restart.*windows' || true
echo "== locate native-control =="
fd -a 'native-control|restart_windows' . -0 | sed 's#^\./##' | tr '\0' '\n'
echo "== outline restart_windows if present =="
f="$(fd 'restart_windows\.go' . | head -n 1)"
if [ -n "$f" ]; then
echo "FILE $f"
wc -l "$f"
ast-grep outline "$f" || true
sed -n '60,130p' "$f"
fi
echo "== search restart-app/native-control references =="
rg -n -C3 'restart-app|native-control|Restart-App|restartApp|Start-App|start-app|Start-Service|ErrorActionPreference' . --glob '!**/.git/**' || trueRepository: 706412584/sub2api
Length of output: 8223
🌐 Web query:
PowerShell $ErrorActionPreference Stop command exit code LASTEXITCODE throw behavior
💡 Result:
In PowerShell, $ErrorActionPreference = 'Stop' controls how non-terminating errors are handled, but it does not directly monitor or react to the $LASTEXITCODE of external commands [1][2]. Mechanism and Behavior: 1. $ErrorActionPreference = 'Stop' escalates non-terminating errors (cmdlet-based) into terminating errors [1][3][4]. 2. This preference does not affect external program exit codes by default [2][5]. Even if set to 'Stop', a failing external command will simply set $LASTEXITCODE to a non-zero value without throwing an exception or aborting the script [2]. 3. To trigger an error in response to a non-zero $LASTEXITCODE, you must manually check the variable and handle it, typically by using 'throw' [2][5]. 4. An exception to this is in PowerShell 7.4+, where you can set $PSNativeCommandUseErrorActionPreference = $true [2]. When enabled, non-zero exit codes from external programs generate a PowerShell error, which is then governed by your $ErrorActionPreference [2]. If $ErrorActionPreference is set to 'Stop' in this scenario, the script will abort upon an external command failure [2]. Throw Behavior: - The 'throw' keyword generates a script-terminating error by default, unwinding the call stack unless caught by a try/catch or trap statement [6]. - $ErrorActionPreference can suppress 'throw' if set to 'SilentlyContinue' or 'Ignore', though the error record is still captured in the $Error variable [6][7]. - If you encounter an external command failure, manually calling 'throw' is the standard way to bridge the gap between $LASTEXITCODE and the PowerShell try/catch error handling mechanism [2][5]. Summary of Interaction: - $ErrorActionPreference = 'Stop' applies to native PowerShell cmdlet errors [4][5]. - To handle external exit codes, check $LASTEXITCODE manually or use $PSNativeCommandUseErrorActionPreference = $true in PowerShell 7.4+ to integrate them into the standard error stream [2].
Citations:
- 1: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling?view=powershell-7.6
- 2: https://stackoverflow.com/questions/57468522/powershell-and-process-exit-codes
- 3: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_commonparameters?view=powershell-7.4
- 4: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.4
- 5: https://stackoverflow.com/questions/54443922/erroraction-stop-not-working-in-try-catch
- 6: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_throw?view=powershell-7.6
- 7: https://stackoverflow.com/questions/49204918/difference-between-throw-and-pscmdlet-throwterminatingerror
🌐 Web query:
PowerShell 7.4 PSNativeCommandUseErrorActionPreference true error code exceptions
💡 Result:
In PowerShell 7.4, the preference variable $PSNativeCommandUseErrorActionPreference allows you to manage how native commands with non-zero exit codes are handled [1][2]. When set to $true, a non-zero exit code triggers the creation of a NativeCommandExitException, which is treated as a PowerShell error [3][4]. Key behaviors include: 1. Error Type: When $PSNativeCommandUseErrorActionPreference is $true, a non-zero exit code results in a non-terminating error by default [3][5]. 2. ErrorActionPreference Integration: This error respects the current $ErrorActionPreference [3][2]. If you set $ErrorActionPreference to Stop, the non-terminating error is promoted to a script-terminating error [3][5]. This allows you to catch the exception using standard try/catch blocks [3][6]. 3. Exception Details: The error is specifically represented by a NativeCommandExitException [3][4]. This exception contains details such as the path of the native command, the non-zero exit code returned, and the process ID [4]. 4. Scope Control: Because it is a preference variable, you can localize its effect by setting it within a script block [1][2]. This is particularly useful for native commands like robocopy that may return non-zero exit codes for informational purposes rather than errors [1]. Prior to PowerShell 7.4, this functionality was available as an experimental feature (PSNativeCommandErrorActionPreference) [7][6]. In 7.4, it became a mainstream, stable feature [7][8]. Example of handling native command errors with try/catch: $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference =
Citations:
- 1: https://github.com/MicrosoftDocs/PowerShell-Docs/blob/main/reference/7.4/Microsoft.PowerShell.Core/About/about_Preference_Variables.md
- 2: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.4
- 3: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling?view=powershell-7.6
- 4: https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/engine/NativeCommandProcessor.cs
- 5: Native error handling:
$PSNativeCommandUseErrorActionPreference = $trueunexpectedly causes non-terminating, not statement-terminating errors PowerShell/PowerShell#18368 - 6: https://devblogs.microsoft.com/powershell/powershell-7-4-general-availability/
- 7: https://learn.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-74?view=powershell-7.4
- 8: Native Command Error Handling PowerShell/PowerShell-RFC#277
🌐 Web query:
PowerShell $ErrorActionPreference Stop cmdlet error LastExitCode undefined command behavior
💡 Result:
In PowerShell, the behavior of an undefined command (a command not found by the shell) is determined by the shell's parsing process before it ever interacts with the $ErrorActionPreference variable or generates an exit code [1][2]. When you attempt to run an undefined command: 1. Command Parsing: PowerShell's engine attempts to resolve the command name. If it cannot find the command as a cmdlet, function, script, or executable, it throws a non-terminating error (or a statement-terminating error depending on context/host) indicating the command is not recognized. Because this occurs at the parsing or binding stage, $ErrorActionPreference = 'Stop' generally does not intercept or handle "command not found" errors in the way it handles errors emitted by executing cmdlets [1][2]. 2. $LASTEXITCODE Behavior: $LASTEXITCODE is specifically designed to store the exit code of the last Win32 executable that was executed [3][4]. If an attempt to run a command fails because the command itself is undefined (and thus no external process was ever successfully launched),
Citations:
- 1: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling?view=powershell-7.6
- 2: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.4
- 3: https://stackoverflow.com/questions/57468522/powershell-and-process-exit-codes
- 4: https://stackoverflow.com/questions/10666035/difference-between-and-lastexitcode-in-powershell
- 5: https://stackoverflow.com/questions/32348794/how-to-get-status-of-invoke-expression-successful-or-failed
- 6: https://devblogs.microsoft.com/powershell/errorlevel-equivalent/
- 7: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.6
🌐 Web query:
PowerShell try catch command not found $ErrorActionPreference Stop statement-terminating error
💡 Result:
In PowerShell, a CommandNotFoundException is classified as a statement-terminating error [1]. By default, statement-terminating errors stop the current statement from executing but do not necessarily terminate the entire script [2]. To ensure a CommandNotFoundException is caught by a try/catch block, you must escalate it to a script-terminating error [3][4]. 1. Using $ErrorActionPreference = 'Stop': When you set
Citations:
- 1: https://stackoverflow.com/questions/69276210/powershell-commandnotfoundexception-is-terminating
- 2: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.6
- 3: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling?view=powershell-7.6
- 4: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-exceptions?view=powershell-7.6
- 5: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.5
- 6: https://stackoverflow.com/questions/77056310/why-are-erroraction-stop-erroractionpreference-stop-sometimes-ineffectiv
- 7: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.4
🌐 Web query:
PowerShell command not found non-zero lastexitcode with $ErrorActionPreference Stop actual exit code
💡 Result:
When a command (external program) is not found in PowerShell, it typically triggers a CommandNotFoundException [1]. Whether this behavior interacts with $ErrorActionPreference and affects the final exit code depends on how you run the command and the PowerShell version [2][3][4]. To ensure robust error handling and proper exit code management, follow these guidelines: 1. Understanding CommandNotFound: When PowerShell fails to find an executable, it throws a non-terminating error by default [1]. If $ErrorActionPreference is set to 'Stop', this error is promoted to a script-terminating error [5][6]. 2. Using $PSNativeCommandUseErrorActionPreference (PowerShell 7.4+): For external programs, $ErrorActionPreference alone does not natively control exit code behavior in older versions [3][4]. In PowerShell 7.4 and later, you can enable the $PSNativeCommandUseErrorActionPreference feature [2][4]: - Set $PSNativeCommandUseErrorActionPreference = $true. - Set $ErrorActionPreference = 'Stop'. When enabled, any non-zero exit code from an external program generates a NativeCommandExitException [2][7]. Because this is a PowerShell error, it respects $ErrorActionPreference, effectively allowing you to catch the failure in a try/catch block [2][8][4]. 3. Manual Exit Code Handling (Recommended for Compatibility): If you cannot rely on the $PSNativeCommandUseErrorActionPreference feature (e.g., in Windows PowerShell or for broader script compatibility), you must manually check the exit code [3][4]: try { # Attempt to run the command $commandOutput = my-command if ($LASTEXITCODE -ne 0) { throw "Command failed with exit code
Citations:
- 1: powershell.exe -Command returns 0 on unhandled exception PowerShell/PowerShell#1764
- 2: https://github.com/MicrosoftDocs/PowerShell-Docs/blob/main/reference/7.6/Microsoft.PowerShell.Core/About/about_Error_Handling.md
- 3: https://stackoverflow.com/questions/48864988/powershell-with-git-command-error-handling-automatically-abort-on-non-zero-exi
- 4: https://stackoverflow.com/questions/57468522/powershell-and-process-exit-codes
- 5: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_error_handling?view=powershell-7.6
- 6: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.5
- 7: Native error handling:
$PSNativeCommandUseErrorActionPreference = $trueunexpectedly causes non-terminating, not statement-terminating errors PowerShell/PowerShell#18368 - 8: https://matthorgan.xyz/blog/powershell-handling-native-applications/
- 9: https://stackoverflow.com/questions/54443922/erroraction-stop-not-working-in-try-catch
- 10: https://stackoverflow.com/questions/10666101/lastexitcode-0-but-false-in-powershell-redirecting-stderr-to-stdout-gives
- 11: Exit code from pwsh -Command script.ps1 not correct PowerShell/PowerShell#25565
Wrap native-control relaunches in try/catch before relying on fallback logic.
With $ErrorActionPreference = 'Stop', a terminatable PowerShell failure during restart-app can abort the remaining script before the $LASTEXITCODE checks run. Catch these failures and run the start fallback for both caught errors and non-zero exit codes so the old Windows process exit doesn’t leave the service stopped.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/internal/pkg/sysutil/restart_windows.go` around lines 81 - 85, Update
the relaunch command construction in the Windows restart flow to wrap
native-control restart-app execution in PowerShell try/catch. Ensure the start
fallback runs both when restart-app throws a terminating error and when it
completes with a non-zero $LASTEXITCODE, while preserving the final failure
propagation when the fallback also fails.
Summary
sysutil.RestartServiceAsync()so the newly swapped binary is loaded without relying on a second manual restart click.native-control.ps1 restart-app(app-only), falls back tostart, and force-kills a stuck old PID before relaunch.Why
0.1.167 → 0.1.168 already wrote the new exe, but the running process was still the old image;
/system/restartran old code and no-op'd (Service restart via exit only works on Linux...). This removes the chicken-and-egg for future updates once this build is installed.Test plan
go test -tags=unit ./internal/pkg/sysutil/go test -tags=unit ./internal/handler/admin/ -run SystemHandlernative-control stop/startSummary by CodeRabbit
New Features
Bug Fixes