fix: restore two-process architecture in standard Client mode - #441
Merged
JusterZhu merged 6 commits intoMay 26, 2026
Merged
Conversation
ClientUpdateStrategy previously ran the update pipeline and launched the main application in-process. This broke the "mutual upgrade" capability (client cannot update the upgrade executable itself while running). Now the Client validates versions, downloads packages, sends IPC, and launches the upgrade process (AppName, default "Update.exe") — then exits. The upgrade process reads ProcessInfo via AES-encrypted file IPC, runs the pipeline, and launches the main application. This matches the documented two-process architecture and the behavior already used by SilentPollOrchestrator and OSSUpdateStrategy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Restores the intended two-process “mutual upgrade” architecture for standard ClientUpdateStrategy, where the client validates/downloads and then hands off to a separate upgrade executable to apply updates and restart the main app (so the updater can update itself safely).
Changes:
- Replaces in-process update pipeline execution +
StartApp()with launching the upgrade executable and exiting the client process. - Adds basic validation/logging around the upgrade executable path before launching.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+235
to
+240
| // Launch the upgrade process to apply updates and restart the main application. | ||
| // The upgrade process (AppName, default "Update.exe") reads ProcessInfo via IPC | ||
| // and runs the pipeline (Hash -> Compress -> Patch) before launching the main app. | ||
| var updaterPath = Path.Combine(_configInfo.InstallPath, _configInfo.AppName); | ||
| if (!File.Exists(updaterPath)) | ||
| throw new FileNotFoundException($"Upgrade application not found: {updaterPath}"); |
Comment on lines
+243
to
+245
| Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = updaterPath }); | ||
| GeneralTracer.Info("ClientUpdateStrategy: upgrade process launched, exiting."); | ||
| await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); |
| GeneralTracer.Info($"ClientUpdateStrategy: launching upgrade process {updaterPath}"); | ||
| Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = updaterPath }); | ||
| GeneralTracer.Info("ClientUpdateStrategy: upgrade process launched, exiting."); | ||
| await GracefulExit.CurrentProcessAsync().ConfigureAwait(false); |
Splits UpdateVersions by AppType and runs the pipeline in two phases: 1. Upgrade itself (AppType.Upgrade) — updates Upgrade.exe first 2. MainApp (AppType.Client) — updates the main application This enables the "mutual upgrade" pattern: Client downloads all packages and launches Upgrade, which updates itself before updating MainApp. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each process updates the OTHER executable — no file lock conflict: - MainApp runs pipeline for Upgrade packages → updates Upgrade.exe - Upgrade runs pipeline for MainApp packages → updates MainApp.exe - IPC now only carries MainApp versions (Upgrade already applied) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Core should only depend on IDirtyStrategy (apply patches), not IBinaryDiffer (which has both CleanAsync for patch generation and DirtyAsync for application). Patch generation belongs in GeneralUpdate.Differential. - Removed IBinaryDiffer.cs from Core (moved responsibility to Differential) - PatchMiddleware now calls IDirtyStrategy.ExecuteAsync(sourcePath, patchPath) - AbstractStrategy.DirtyStrategy property replaces Differ - Bootstrap.DirtyStrategy<T>() replaces Bootstrap.BinaryDiffer<T>() - Updated all tests accordingly Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Restored IBinaryDiffer to Core with DirtyAsync only (file-level algorithm) - Added BinaryDiffer<T>() injection to AbstractBootstrap - Added BinaryDiffer property to AbstractStrategy, passed via PipelineContext - Bootstrap injects both IDirtyStrategy and IBinaryDiffer into strategies - ClientUpdateStrategy/UpgradeUpdateStrategy: SetBinaryDiffer() - Fixed Differential's IBinaryDiffer: extends Core's + adds CleanAsync Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
JusterZhu
force-pushed
the
fix/two-process-architecture
branch
from
May 26, 2026 08:15
66fe1ab to
aa98283
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The standard
ClientUpdateStrategywas running the update pipeline and launching the main application in-process. This broke the "mutual upgrade" capability — the client cannot safely update the upgrade executable itself while running.This PR restores the two-process architecture: Client validates, downloads, sends IPC, and launches the upgrade process, then exits. The upgrade process runs the pipeline and launches the main app.
This matches the documented architecture and the behavior already used by
SilentPollOrchestratorandOSSUpdateStrategy.Before / After
The Upgrade Process
The upgrade executable (AppName, default "Update.exe") is a separate EXE that creates its own
GeneralUpdateBootstrap, manually setsAppType.Upgrade:The bootstrap constructor reads ProcessInfo from AES-encrypted file IPC (written by the Client), providing all parameters (versions, paths, blacklists, etc.) to
UpgradeUpdateStrategy.Files changed
Strategy/ClientUpdateStrategy.csTest plan
🤖 Generated with Claude Code