Skip to main content

GeneralUpdate.Core — Execution Flow Deep Dive

Target Audience: Developers new to GeneralUpdate.Core

After reading you will understand:

  • Overall architecture and layered design of the update system
  • Complete execution chain from app startup to update completion
  • Responsibilities of the Client and Upgrade processes
  • Selection logic and execution differences between Chain (diff) and Full packages
  • Middleware pipeline design and working principles
  • Automatic Full fallback mechanism when Chain fails
  • How IPC communication delivers update tasks across processes
  • Silent Mode delayed-update design intent

Table of Contents

  1. Architecture Overview
  2. Entry Point: Bootstrap's Dual Identity Design
  3. ClientStrategy: Complete Update Flow
  4. DownloadPlanBuilder: Download Planning & Chain/Full Decision
  5. Download Engine: DefaultDownloadOrchestrator
  6. Middleware Pipeline: Hash → Compress → Patch
  7. DiffPipeline: Differential Engine Internals
  8. Chain→Full Fallback Mechanism
  9. IPC Communication Protocol
  10. UpdateStrategy: Upgrade Process Execution Flow
  11. Silent Mode: Delayed Upgrade Mechanism
  12. OS Strategy Platform Differences
  13. Error Recovery Panorama
  14. Key Code Path Index

1. Architecture Overview

1.1 Three-Layer Architecture

GeneralUpdate.Core adopts a three-layer dispatch + two-engine design:

┌──────────────────────────────────────────────────────────┐
│ Layer 1: Entry Dispatch │
│ GeneralUpdateBootstrap │
│ Dispatches to role strategies based on AppType │
├──────────────────────────────────────────────────────────┤
│ Layer 2: Role Strategy │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ClientStrategy│ │UpdateStrategy│ │ OssStrategy │ │
│ │ Download+ │ │ Read IPC+ │ │ OSS Mode │ │
│ │ Dispatch │ │ Apply │ │ │ │
│ └──────┬──────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ └────────────────┼──────────────────┘ │
│ ▼ │
│ Layer 3: OS Strategy │
│ ┌──────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │WindowsStrategy│ │LinuxStrategy│ │ MacStrategy │ │
│ └──────┬───────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼───────────────┘ │
│ ▼ │
│ Middleware Pipeline (per-version) │
│ Hash → Compress → Patch │
├──────────────────────────────────────────────────────────┤
│ Two Engines │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ Download Engine │ │ Differential Engine │ │
│ │ DefaultDownload │ │ DiffPipeline │ │
│ │ Orchestrator │ │ + HDiffPatch │ │
│ │ + Retry Policy │ │ + Parallel Patch Apply│ │
│ └─────────────────────┘ └─────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

1.2 Core Design Principles

PrincipleDescription
Client Unified DownloadAll packages (Client + Upgrade + Chain + Full ZIPs) are downloaded at once by the Client process to %TEMP%/main_temp/
Upgrade Applies OnlyThe Upgrade process makes no network requests; it receives version info via encrypted IPC file and only runs the middleware pipeline
Chain FallbackWhen Chain (diff) package application fails, it automatically retries with a pre-downloaded Full package in-place — no second server request needed
Middleware PipelineEach version runs independently through the Hash → Compress → Patch pipeline — single responsibility, testable, replaceable

1.3 Two Package Types

TypePackageTypeContentApplication Method
Chain (Diff)1.patch binary diff files + added files + delete manifestExtract to temp PatchPath → combine old installed files + .patch via HDiffPatch to produce new files
Full2Complete application filesDirectly extract over install directory, skipping PatchMiddleware

1.4 Two Process Roles

ProcessAppTypeEntry StrategyResponsibilities
Main app (e.g. MyApp.exe)ClientClientStrategyServer version check, download all packages at once, self-upgrade (Upgrade package), write IPC file, launch Upgrade process, exit
Updater (e.g. Updater.exe)UpgradeUpdateStrategyRead IPC file for version info, run pipeline to upgrade main app files, write back manifest, launch main app, exit

2. Entry Point: Bootstrap's Dual Identity Design

GeneralUpdateBootstrap is the entry point for the entire update library. Its constructor attempts to read the IPC file before any other method is called.

2.1 Constructor as IPC Probe

public GeneralUpdateBootstrap()
{
InitializeFromEnvironment(); // Reads encrypted IPC file
}

What InitializeFromEnvironment() does:

void InitializeFromEnvironment()
{
var provider = new EncryptedFileProcessContractProvider();
var contract = provider.Receive(); // Read %TEMP%/GeneralUpdate/ipc/process_info.enc

if (contract == null) return; // No IPC file → this is not an Upgrade process

// IPC file found → this is an Upgrade process
// Fill internal config with IPC info
_configInfo.UpdateAppName = contract.AppName;
_configInfo.InstallPath = contract.InstallPath;
_configInfo.UpdateVersions = contract.UpdateVersions;
// ... other fields
}

2.2 LaunchAsync Dispatch

                    ┌─────────────────────────┐
│ Constructor │
│ InitializeFromEnv() │
└────────────┬────────────┘

┌────────────┴────────────┐
│ Read IPC file │
└────────────┬────────────┘

┌────────────┴────────────┐
│ Has IPC? │
└────────────┬────────────┘
│ │
No Yes
│ │
┌─────▼──┐ ┌──────▼─────┐
│No IPC │ │Has IPC │
│data │ │data filled │
└────┬───┘ └──────┬─────┘
│ │
┌────▼────┐ ┌─────▼────┐
│SetConfig│ │ LaunchAsy│
│or │ │ nc(AppTyp│
│SetSource│ │ e.Upgrade)│
│fill cfg │ │ → Update │
└────┬────┘ │ Strategy │
│ └──────────┘
┌────▼────┐
│ LaunchAs│
│ync(AppTy│
│pe.Client│
│)→Client │
│Strategy │
└─────────┘

2.3 Key Insight

The same GeneralUpdateBootstrap type follows completely different execution paths in the two processes:

Client process:
new GeneralUpdateBootstrap()
→ InitializeFromEnvironment() → No IPC file → _configInfo is empty
.SetConfig(request) → _configInfo set from user code
.LaunchAsync(AppType.Client) → ClientStrategy

Upgrade process:
new GeneralUpdateBootstrap()
→ InitializeFromEnvironment() → IPC file found → _configInfo pre-filled
.LaunchAsync(AppType.Upgrade) → UpdateStrategy (uses config filled in constructor)

This means the Upgrade process needs no command-line arguments or configuration files. All information is passed via the IPC file.


3. ClientStrategy: Complete Update Flow

ClientStrategy is the most complex role strategy, responsible for the entire chain from version check to launching the upgrade process.

3.1 Complete Flow Diagram

3.2 Step Details

Step 1: Clean Up Conflicting Processes (CallSmallBowlHomeAsync)

// ClientStrategy.cs:985-1002
// Before starting the update, kill any running upgrade processes (Bowl)
// Prevent them from holding file locks that could block backup or replacement
private async Task CallSmallBowlHomeAsync(string processName)
{
var processes = Process.GetProcessesByName(processName);
foreach (var process in processes)
await GracefulExit.ShutdownAsync(process);
}

Step 2: Version Check

// Build download source (default HTTP)
var downloadSource = new HttpDownloadSource(
_configInfo.UpdateUrl, // Server URL
_configInfo.ClientVersion, // Current main app version
_configInfo.UpgradeClientVersion, // Current upgrade program version
_configInfo.AppSecretKey, // App secret key
GetPlatform(), // Platform type
// ... other parameters
);

// Request server for available updates
var sourceResult = await downloadSource.ListAsync();

The server returns List<DownloadAsset>, each Asset containing:

  • Name: Package name, also the ZIP filename (e.g. 1.0.1)
  • Version: Version number
  • Url: Download URL
  • SHA256: Hash for verification
  • Size: File size
  • PackageType: 1=Chain, 2=Full
  • AppType: 1=Client, 2=Upgrade
  • IsFreeze: Whether frozen (frozen packages don't participate in updates)
  • IsForcibly: Whether forced update
  • MinClientVersion: Minimum compatible version
  • FallbackFullName/Url/Hash/Version: Corresponding fallback full package info

Step 3: Scenario Determination

var scenario = (_configInfo.IsMainUpdate, _configInfo.IsUpgradeUpdate) switch
{
(false, false) => UpdateScenario.None, // No update needed
(false, true) => UpdateScenario.UpgradeOnly, // Only upgrade program needs update
(true, false) => UpdateScenario.MainOnly, // Only main app needs update
(true, true) => UpdateScenario.Both, // Both need updating
};

Step 4: Backup

// Backup install directory to .backups/
StorageManager.Backup(_configInfo.InstallPath, _configInfo.BackupDirectory, blacklist);
// Clean old backups, keep only the most recent 3
StorageManager.CleanBackup(_configInfo.InstallPath, keepVersions: 3);

Default blacklist excludes: .backups, .git, .svn, bin, obj, node_modules, etc.

Step 5: Download All Packages at Once

// ClientStrategy.cs:589-597
// Merge chain packages and fallback packages, de-dup, download at once
var allAssets = plan.Assets.Concat(plan.FallbackFulls)
.GroupBy(a => a.Url)
.Select(g => g.First())
.ToList();
var mergedPlan = new DownloadPlan(allAssets, plan.IsForcibly);
var downloadReport = await ExecuteDownloadAsync(mergedPlan);

if (downloadReport.FailedCount > 0)
throw new InvalidOperationException("Download failed!");

Key point: Chain packages and their Fallback Full packages are downloaded simultaneously — there is no two-phase retry of "download chain first, then download full on failure." This design provides better UX: on download failure, the entire batch can be retried immediately instead of one-by-one.

Step 6: Scenario Dispatch

Execution paths for the three scenarios:

┌─────────────────────────────────────────────────────────────────┐
│ UpgradeOnly │
│ │
│ ApplyUpgradePackagesAsync() │
│ ↓ │
│ _osStrategy.Create(_configInfo) │
│ ↓ │
│ _osStrategy.ExecuteAsync() │
│ ↓ │
│ OS strategy runs Hash → Compress → Patch for each upgrade version│
│ ↓ │
│ AllPackagesSucceeded → WriteBackUpgradeVersion() │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ MainOnly │
│ │
│ SendProcessIpc() │
│ ↓ │
│ Write AES-encrypted IPC file to %TEMP%/GeneralUpdate/ipc/process_info.enc│
│ ↓ │
│ LaunchUpgradeProcessAsync() │
│ ↓ │
│ Launch Upgrade process → current Client process continues or exits │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ Both │
│ │
│ ApplyUpgradePackagesAsync() ─── self-upgrade first │
│ ↓ abort on failure │
│ SendProcessIpc() ─── then write IPC │
│ ↓ │
│ LaunchUpgradeProcessAsync() ─── finally launch Upgrade process │
└─────────────────────────────────────────────────────────────────┘

4. DownloadPlanBuilder: Download Planning & Chain/Full Decision

DownloadPlanBuilder is a static utility class responsible for determining which packages to download from the server's Asset list.

4.1 Complete Decision Flow

4.2 Package Switch Threshold Logic

Core code for the 80% threshold:

// DownloadPlanBuilder.cs:174-196
if (chainCandidates.Count > 0 && fullCandidates.Count > 0)
{
// Take the largest-version Full package as reference
var bestFull = fullCandidates.OrderByDescending(v => v).First();

// Calculate total size of Chain packages for the same AppType
long chainTotal = chainCandidates
.Where(a => a.AppType == bestFull.AppType)
.Sum(a => a.Size);

// If Chain total >= 80% of Full
if (chainTotal >= (long)(bestFull.Size * 0.8))
{
// Use Full package directly — less to download, more reliable
return new DownloadPlan(new[] { bestFull }, isForcibly);
}
}

Why this threshold?

ScenarioChain sizeFull sizeRatioDecisionReason
Only a few lines changed1 MB50 MB2%ChainDiff saves 98% bandwidth
Large file changes42 MB50 MB84%FullDiff saves only 16%, not worth the complexity and risk
Many new files35 MB50 MB70%ChainSaves 30%, worthwhile

4.3 Fallback Package Matching Rules

When total Chain size < 80% of Full size, use Chain + FallbackFull mode:

// DownloadPlanBuilder.cs:204-237
var chainWithFallback = chainCandidates.Select(chain =>
{
// Find the smallest Full package with same AppType and version >= chain version
var match = fullCandidates
.Where(f => f.AppType == chain.AppType)
.OrderBy(f => f.Version) // Smallest version first
.FirstOrDefault(f => f.Version >= chain.Version);

if (match != null)
{
// Attach FallbackFull metadata to this Chain package
return chain with
{
FallbackFullName = match.Name,
FallbackFullUrl = match.Url,
FallbackFullHash = match.SHA256,
FallbackFullVersion = match.Version
};
}
return chain; // No matching Full package — no fallback capability
});

5. Download Engine: DefaultDownloadOrchestrator

5.1 Architecture

ClientStrategy


IDownloadOrchestrator (interface)

├── DefaultDownloadOrchestrator (default implementation)
│ │
│ ├── IDownloadPolicy → Retry policy (backoff algorithm)
│ ├── IDownloadExecutor → Actual HTTP download
│ └── IDownloadPipeline → Post-download processing (SHA256 verification)

└── Custom Orchestrator (replaceable)

5.2 Execution Logic

5.3 Key Design Points

Resume Download: Uses HTTP Range headers — if a download is interrupted, only the missing portion is re-downloaded.

Retry Policy:

  • Default max 3 retries
  • Interval = RetryInterval * 2^(N-1) (exponential backoff)
  • Default initial interval 1 second

Concurrency Control:

  • DiffMode.Serial → forces serial execution
  • Normal mode → Clamp(MaxConcurrency, 1, Environment.ProcessorCount * 2)

6. Middleware Pipeline: Hash → Compress → Patch

The middleware pipeline is the core processing pattern of GeneralUpdate.Core. Each version runs through the complete pipeline independently.

6.1 Pipeline Construction

The pipeline is built by the OS strategy:

// WindowsStrategy.cs
protected override PipelineBuilder BuildPipeline(PipelineContext context)
{
var needsPatch = context.Get<bool>("PatchEnabled")
&& context.Get<int>("PackageType") != (int)PackageType.Full;

return new PipelineBuilder(context)
.UseMiddleware<HashMiddleware>() // 1. Integrity verification
.UseMiddleware<CompressMiddleware>() // 2. Extraction
.UseMiddlewareIf<PatchMiddleware>( // 3. Diff application (Chain packages only)
needsPatch: needsPatch);
}

6.2 PipelineContext: Data Contract Between Middleware

PipelineContext is a ConcurrentDictionary<string, object>. All middleware exchange data through it:

KeyWriterConsumerDescription
ZipFilePathAbstractStrategyHashMiddleware, CompressMiddlewareFull ZIP path TempPath/{name}.zip
HashAbstractStrategyHashMiddlewareExpected SHA256
FormatAbstractStrategyCompressMiddlewareCompression format (currently only Zip)
EncodingAbstractStrategyCompressMiddlewareFile encoding
SourcePathAbstractStrategyCompressMiddleware, PatchMiddlewareInstall directory
PatchPathAbstractStrategyCompressMiddleware, PatchMiddlewareChain package temp extract directory
PatchEnabledAbstractStrategyOS strategy (during BuildPipeline)Whether diff is enabled
PackageTypeAbstractStrategyOS strategy, CompressMiddleware1=Chain, 2=Full
DiffPipelineAbstractStrategyPatchMiddlewareParallel diff engine

6.3 Chain vs Full Execution Differences

6.4 Middleware Implementation Details

HashMiddleware

public async Task InvokeAsync(PipelineContext context)
{
var zipPath = context.Get<string>("ZipFilePath");
var expectedHash = context.Get<string>("Hash");

var actualHash = ComputeSHA256(zipPath); // Compute ZIP file SHA256

if (!string.Equals(actualHash, expectedHash, StringComparison.OrdinalIgnoreCase))
throw new CryptographicException("File hash mismatch!");
}

CompressMiddleware

public async Task InvokeAsync(PipelineContext context)
{
var packageType = context.Get<int>("PackageType");
var zipPath = context.Get<string>("ZipFilePath");
var sourcePath = context.Get<string>("SourcePath");
var patchPath = context.Get<string>("PatchPath");
var patchEnabled = context.Get<bool>("PatchEnabled");
var format = context.Get<Format>("Format");
var encoding = context.Get<Encoding>("Encoding");

if (packageType == (int)PackageType.Full)
{
// Full package → extract directly to install directory
CompressProvider.Decompress(zipPath, sourcePath, format, encoding);
}
else if (patchEnabled)
{
// Chain package + patch enabled → extract to PatchPath
CompressProvider.Decompress(zipPath, patchPath, format, encoding);
}
else
{
// Chain package + patch disabled → fall back to full extract
CompressProvider.Decompress(zipPath, sourcePath, format, encoding);
}
}

PatchMiddleware

public async Task InvokeAsync(PipelineContext context)
{
var sourcePath = context.Get<string>("SourcePath");
var patchPath = context.Get<string>("PatchPath");
var diffPipeline = context.Get<DiffPipeline>("DiffPipeline");

// Invoke diff engine to apply patches
await diffPipeline.DirtyAsync(sourcePath, patchPath,
progress: new DiffProgressReporter(this),
cancellationToken: CancellationToken.None);
}

6.5 Pipeline Main Loop

The OS strategy (AbstractStrategy) iterates through all versions and runs the pipeline:

// AbstractStrategy.cs:149-287
public async Task ExecuteAsync()
{
foreach (var version in _configinfo.UpdateVersions)
{
var context = CreatePipelineContext(version, patchPath);
var pipelineBuilder = BuildPipeline(context);
await pipelineBuilder.Build(); // Run middleware pipeline
DeleteVersionZip(version); // Delete processed ZIP
}
}

Note: A failure in one version does not affect processing of other versions. However, the AllPackagesSucceeded flag tells the caller whether any version failed.


7. DiffPipeline: Differential Engine Internals

DiffPipeline is GeneralUpdate's differential engine, providing two operation modes:

  • CleanAsync: Server-side — compares old and new version directories, generates .patch files
  • DirtyAsync: Client-side — applies .patch files to an installed directory

7.1 Main Flow

7.2 Atomic Replace Strategy (ApplyPatch)

private async Task ApplyPatch(string appFilePath, string patchFilePath, CancellationToken ct)
{
// 1. Generate random temp filename
var tempPath = Path.Combine(
Path.GetDirectoryName(appFilePath)!,
$"{Path.GetRandomFileName()}_{Path.GetFileName(appFilePath)}");

// 2. Apply HDiffPatch → write to temp file
await _binaryDiffer.DirtyAsync(appFilePath, tempPath, patchFilePath, ct);

// 3. Atomic replace
if (File.Exists(tempPath))
{
File.SetAttributes(appFilePath, FileAttributes.Normal); // Handle read-only files
File.Delete(appFilePath); // Delete original
File.Move(tempPath, appFilePath); // Temp → final location
}
}

Why atomic replace?

❌ Direct overwrite:
Process crashes mid-write → file partially corrupted → app won't start

✅ Temp file strategy:
1. Write completely to temp file (if crash, original file is intact)
2. Delete original file
3. Move temp file to final location
If crash between steps 2 and 3 → file may be missing
But this is safe for future updates — next update will re-download

7.3 Delete File Identification Strategy

Deletion is matched by file content SHA256 hash, not by filename:

// Read the list of deleted file hashes from generalupdate.delete.json
// Scan the current install directory, compute SHA256 for each file
// If match → delete

var deleteHashes = new HashSet<string>(/* read from JSON */);
foreach (var file in appFiles)
{
var fileHash = hashAlgorithm.ComputeHash(file.FullName);
if (deleteHashes.Contains(fileHash))
File.Delete(file.FullName);
}

Benefit: Even if a file is renamed, it won't be deleted as long as its content hasn't changed.

7.4 Parallel Control

using var semaphore = new SemaphoreSlim(_options.MaxDegreeOfParallelism); // Default=2

var tasks = matchedPairs.Select(pair => Task.Run(async () =>
{
await semaphore.WaitAsync(ct);
try
{
await ApplyPatch(...);
}
catch (Exception ex) when (!_options.StopOnFirstError)
{
// StopOnFirstError=false (default) → individual file failure doesn't block everything
// Report error through Progress, continue processing other files
}
finally
{
semaphore.Release();
}
}));

await Task.WhenAll(tasks);

8. Chain→Full Fallback Mechanism

This is GeneralUpdate.Core's most advanced fault-tolerance mechanism, implemented in AbstractStrategy.ExecuteAsync().

8.1 Complete Flow

8.2 Version Tracking to Skip Subsequent Chain Packages

// AbstractStrategy.cs:168-185
SemVersion? fallbackEffectiveVersion = null;

foreach (var version in _configinfo.UpdateVersions)
{
// If a Full fallback was previously applied
// Skip all Chain packages <= that version
if (fallbackEffectiveVersion != null
&& version.PackageType == (int)PackageType.Chain
&& versionSv <= fallbackEffectiveVersion)
{
continue; // Already covered, skip
}

try
{
await pipelineBuilder.Build(); // Chain attempt
}
catch when (version.PackageType == Chain && FallbackFullName != null)
{
// Chain failed → retry with Full
await fallbackBuilder.Build();
fallbackEffectiveVersion = ffv; // Record fallback version
}
}

8.3 Edge Case Examples

Scenario: 3 Chain packages, all fall back to the same Full package

Version list: 1.0.1(chain), 1.0.2(chain), 1.0.3(chain)
Full package: 1.0.3-full (FallbackFull for all chain packages)

Execution:
1. 1.0.1 chain → failed → fallback to 1.0.3-full → fallbackEffectiveVersion = 1.0.3
2. 1.0.2 chain → skip (1.0.2 <= 1.0.3)
3. 1.0.3 chain → skip (1.0.3 <= 1.0.3)

Scenario: Some Chain packages fail, each has a different Full package

Version list: 1.0.1(chain, fallback=1.0.1-full), 1.0.2(chain, fallback=1.0.2-full)
Execution:
1. 1.0.1 chain → success → continue
2. 1.0.2 chain → failed → fallback to 1.0.2-full → fallbackEffectiveVersion = 1.0.2

Scenario: Chain packages succeed, no fallback needed

Version list: 1.0.1(chain), 1.0.2(chain)
Execution:
1. 1.0.1 chain → success
2. 1.0.2 chain → success
→ fallbackEffectiveVersion stays null, nothing to skip

9. IPC Communication Protocol

IPC (Inter-Process Communication) is the data transfer mechanism between the Client process and the Upgrade process.

9.1 Communication Flow

9.2 Encryption Details

// IpcEncryption.cs
// Uses AES-256-CBC encryption
// Key: SHA256("GeneralUpdate.ProcessContract.IPC.v1") → 32 bytes
// IV: Fixed 16 bytes (starts with 0x47, rest 0x00)
// File path: %TEMP%/GeneralUpdate/ipc/process_info.enc

public static void EncryptToFile(byte[] plainBytes, string filePath, byte[] key, byte[] iv)
{
using var aes = Aes.Create();
aes.Key = key;
aes.IV = iv;
using var encryptor = aes.CreateEncryptor();
var cipher = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

// FileShare.Read allows the receiver to start reading before writing completes
using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
fs.Write(cipher, 0, cipher.Length);
}

9.3 ProcessContract Field Mapping

ProcessContractSourceMeaningConsumer
UpdateAppName_configInfo.MainAppNameName of the program to launch after updateOS strategy's StartAppAsync
InstallPath_configInfo.InstallPathInstall directoryPipeline's SourcePath
CurrentVersion_configInfo.ClientVersionCurrent main app versionLogging/reporting
LastVersion_configInfo.LastVersionTarget versionLogging/reporting
UpdateVersionsclientVersions listList of packages to applyPipeline input
TempPathmain_temp directoryDirectory containing ZIP filesPipeline's ZipFilePath
UpdatePath_configInfo.UpdatePathUpgrade program directoryPath resolution
Encoding_configInfo.EncodingFile encodingCompressMiddleware
CompressFormat_configInfo.FormatCompression formatCompressMiddleware
DownloadTimeOut_configInfo.DownloadTimeOutDownload timeoutOrchestrator (reserved)
BackupDirectory_configInfo.BackupDirectory.backups directoryLogging/reporting
ReportUrl_configInfo.ReportUrlStatus report URLUpdateStrategy
LaunchClientAfterUpdate_configInfo.LaunchClientAfterUpdateWhether to auto-launch main app after updateUpdateStrategy
ReportType1 or 21=active polling 2=push notificationReport type differentiation

10. UpdateStrategy: Upgrade Process Execution Flow

UpdateStrategy is the strategy for the Upgrade process. Its biggest difference from ClientStrategy: it makes zero network requests.

10.1 Complete Flow

10.2 Why Doesn't the Upgrade Process Make Network Requests?

// UpdateStrategy.cs core logic
public async Task ExecuteAsync()
{
// No IDownloadSource.ListAsync() call
// No IDownloadOrchestrator.ExecuteAsync() call
// All data is already in _configInfo, from the IPC file

// Directly run the pipeline
_osStrategy.Create(_configInfo);
await _osStrategy.ExecuteAsync();

// Check result
if ((_osStrategy as AbstractStrategy)?.AllPackagesSucceeded == true)
{
// Write back manifest
ManifestInfo.TryUpdateVersion(installPath, clientVersion: latestVersion);
// Launch main app
await _osStrategy.StartAppAsync();
}
// If failed → don't write manifest, don't launch main app
// Next Client start will re-detect the update
}

Design Intent: The Upgrade process is a "short-lived, one-shot process."

  • It doesn't need network capability (smaller binary size)
  • It doesn't need to know the server address (security: smaller attack surface)
  • It only needs to read local files and perform file operations

10.3 Preventing Update Loops

AllPackagesSucceeded is an important safety gate:

Client process                       Upgrade process
│ │
├─ Version check: 1.0.0 → 1.0.1 │
├─ Download 1.0.1.zip │
├─ IPC → launch Upgrade ──────→ │
│ ├─ Apply 1.0.1 → ✅ success
│ ├─ Write manifest: 1.0.1
│ └─ Launch main app 1.0.1
│ │
└── Old process exits ←─────────────────┘

New version main app starts
Version is already latest

If Upgrade process fails:

Upgrade process
├─ Apply 1.0.1 → ❌ failed
├─ AllPackagesSucceeded = false
├─ Don't write manifest (version still 1.0.0)
└─ Don't launch main app

Next Client start:
├─ Version check: 1.0.0 → 1.0.1 (still needs update)
└─ Try again

11. Silent Mode: Delayed Upgrade Mechanism

Silent Mode is a variant of the standard update flow — the only difference is the timing of launching the Upgrade process changes from "immediately" to "on process exit."

11.1 Standard Mode vs Silent Mode

Standard mode:
[Download → IPC → Launch Upgrade → Exit] → Upgrade runs → Launch main app

Silent mode:
[Download → IPC → User continues working → Process exits → Launch Upgrade] → Upgrade runs → Launch main app

Silent mode follows the same flow, just split into two phases:

PollLoopAsync (background thread):
ClientStrategy.ExecuteAsync() → HasPreparedClientUpdate = true → IPC written → loop exits
(User is completely unaware, continues using the app normally)

AppDomain.ProcessExit (on process exit):
SilentPollOrchestrator.OnProcessExit → LaunchUpgradeProcessSync()
→ Upgrade process starts → applies update → version updated on next launch

11.2 Complete Flow Diagram

11.3 TryLaunchUpgrade — Fallback Method

// SilentPollOrchestrator.cs:187-210
// If AppDomain.ProcessExit is unreliable (e.g., Ctrl+C)
// Can manually call this method to launch the upgrade process
public bool TryLaunchUpgrade()
{
if (_prepared != 1 || Interlocked.Exchange(ref _updaterStarted, 1) == 1)
return false;

_strategy.LaunchUpgradeProcessSync();
return true;
}

12. OS Strategy Platform Differences

AspectWindowsLinuxmacOS
Pipeline BuildingHash→Compress→Patch(IfNeeded)SameSame
Launch Main AppLaunch app + optional Bowl daemon → GracefulExit.CurrentProcessAsync()Launch app only → GracefulExitLaunch app only, additional File.Exists check
Path Case SensitivityCase-insensitiveCase-sensitiveCase-insensitive (APFS default)
Bowl Support✅ Supports crash daemon

Bowl is a Windows-only crash daemon process. When the main app exits unexpectedly, Bowl can detect this and re-launch it. During the update flow, Bowl is killed first by CallSmallBowlHomeAsync() in ClientStrategy to avoid file locks.


13. Error Recovery Panorama

Error ScenarioCatch LocationHandlingConsequence
Download failureClientStrategy.DownloadAndApplyAsync()Check FailedCount > 0, throw exceptionBubbles up to ExecuteAsync() catch → triggers error hook + reports failure
Chain pipeline fails (has FallbackFull)AbstractStrategy.ExecuteAsync() catch whenRebuild PipelineContext, set PackageType=Full, re-run Hash→CompressUpdate succeeds eventually, fallbackEffectiveVersion records fallback version
Chain pipeline fails (no FallbackFull)AbstractStrategy.ExecuteAsync() catchAllPackagesSucceeded=false, triggers HandleExecuteException, TryRollback() if nothing succeeded yetThat version fails, continue next version
Fallback Full also failsAbstractStrategy.ExecuteAsync() catch (inner try)AllPackagesSucceeded=false, continue next versionThat version fails
Upgrade package fails (Both scenario)ClientStrategy.cs Both branchAbort IPC sending + Upgrade process launchPrevents Upgrade process from getting stale TempPath
Upgrade process pipeline failsUpdateStrategy.ExecuteAsync()AllPackagesSucceeded=false, skip manifest write-back, skip main app launchNext Client start re-detects the update
Rollback failsAbstractStrategy.TryRollback()Log only, doesn't blockInstall directory may be in inconsistent state
ZIP hash mismatchHashMiddlewareCryptographicExceptionVersion pipeline fails immediately, triggers Chain→Full fallback or version failure
File lockedIpcEncryption.DecryptFromFile()Catch IOException, return nullIPC file not ready, Upgrade process waits or exits

Rollback Logic

// AbstractStrategy.cs:504-532
private void TryRollback()
{
// Only called when "no version has succeeded in this batch yet"
// If a version has already been successfully applied and overwritten files,
// rolling back would undo valid work, causing cross-version downgrade

if (!_appliedAnyVersion)
{
// Try to restore from .backups/
StorageManager.Restore(backupDir, _configInfo.InstallPath);
}
}

14. Key Code Path Index

StepFileKey Line
Entry dispatchBootstrap/GeneralUpdateBootstrap.csLaunchAsync() @L125
Upgrade path IPC readingBootstrap/GeneralUpdateBootstrap.csInitializeFromEnvironment() @L357
Client role full workflowStrategy/ClientStrategy.csExecuteStandardWorkflowAsync() @L396
Scenario determinationStrategy/ClientStrategy.csScenario switch @L452-458
Batch downloadStrategy/ClientStrategy.csDownloadAndApplyAsync() @L589-597
Self-upgrade Upgrade packageStrategy/ClientStrategy.csApplyUpgradePackagesAsync() @L723-737
IPC write fileStrategy/ClientStrategy.csSendProcessIpc() @L751-765
Launch Upgrade processStrategy/ClientStrategy.csLaunchUpgradeProcessAsync() @L777-789
Download plan buildingDownload/DownloadPlanBuilder.csBuild() @L111
Chain vs Full threshold decisionDownload/DownloadPlanBuilder.cs@L174-196
Fallback package matchingDownload/DownloadPlanBuilder.cs@L200-238
Default download orchestrationDownload/Orchestrators/DefaultDownloadOrchestrator.csExecuteAsync()
Parallel pipeline loop + Chain→Full fallbackStrategy/AbstractStrategy.csExecuteAsync() @L149-287
Pipeline context assemblyStrategy/AbstractStrategy.csCreatePipelineContext() @L322-346
Windows pipeline buildingStrategy/WindowsStrategy.csBuildPipeline()
Hash verification middlewarePipeline/HashMiddleware.csInvokeAsync()
Extract middleware (chain/full branching)Pipeline/CompressMiddleware.csInvokeAsync()
Patch application middlewarePipeline/PatchMiddleware.csInvokeAsync()
Server-side diff generationPipeline/DiffPipeline.csCleanAsync()
Client-side diff applicationPipeline/DiffPipeline.csDirtyAsync()
Atomic replacePipeline/DiffPipeline.csApplyPatch()
Delete file handlingPipeline/DiffPipeline.csHandleDeleteList()
New file copyingPipeline/DiffPipeline.csCopyUnknownFiles()
Encrypt write fileIpc/IpcEncryption.csEncryptToFile()
Decrypt read fileIpc/IpcEncryption.csDecryptFromFile()
IPC ProviderIpc/IProcessInfoProvider.csEncryptedFileProcessContractProvider
ProcessContract definitionConfiguration/ProcessContract.csAll fields
Configuration mappingConfiguration/ConfigurationMapper.csMapToProcessContract()
Silent mode pollingSilent/SilentPollOrchestrator.csPollLoopAsync() @L116
Silent mode exit launchSilent/SilentPollOrchestrator.csOnProcessExit() @L156
Upgrade roleStrategy/UpdateStrategy.csExecuteAsync()
Pipeline context data contractPipeline/PipelineContext.csConcurrentDictionary
OS strategy resolutionBootstrap/OsStrategyResolver.csResolve() / GetPlatform()
Event managerEvent/EventManager.csDispatch() / AddListener()
Diff progress bridgePipeline/DiffProgressReporter.csReport()

This document covers the complete execution flow of GeneralUpdate.Core. If you find any inaccuracies or areas that need supplementing, please submit an Issue or PR.