Skip to content

fix: restore two-process architecture in standard Client mode - #441

Merged
JusterZhu merged 6 commits into
GeneralLibrary:masterfrom
JusterZhu:fix/two-process-architecture
May 26, 2026
Merged

fix: restore two-process architecture in standard Client mode#441
JusterZhu merged 6 commits into
GeneralLibrary:masterfrom
JusterZhu:fix/two-process-architecture

Conversation

@JusterZhu

Copy link
Copy Markdown
Collaborator

Summary

The standard ClientUpdateStrategy was 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 SilentPollOrchestrator and OSSUpdateStrategy.

Before / After

Before (broken):
  Client: 验证 → 下载 → Pipeline → StartApp    ← 单进程,无法自更新

After:
  Client: 验证 → 下载 → IPC → Launch Upgrade.exe → Exit
  Upgrade:                              IPC读取 → Pipeline → StartApp → Exit

The Upgrade Process

The upgrade executable (AppName, default "Update.exe") is a separate EXE that creates its own GeneralUpdateBootstrap, manually sets AppType.Upgrade:

new GeneralUpdateBootstrap()
    .Option(UpdateOptions.AppType, AppType.Upgrade)
    .LaunchAsync();

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

File Change
Strategy/ClientUpdateStrategy.cs Replace in-process pipeline+StartApp with upgrade EXE launch + GracefulExit

Test plan

  • All 959 CoreTest tests pass
  • Build succeeds for netstandard2.0, net8.0, net10.0

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 26, 2026 06:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
JusterZhu and others added 5 commits May 26, 2026 15:10
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
JusterZhu force-pushed the fix/two-process-architecture branch from 66fe1ab to aa98283 Compare May 26, 2026 08:15
@JusterZhu
JusterZhu merged commit e035390 into GeneralLibrary:master May 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants