Skip to content

fix(core): resolve 16 audit findings — race conditions, design flaws, glue code, and performance issues#529

Merged
JusterZhu merged 2 commits into
masterfrom
fix/core-audit-bugs-and-cleanup
Jun 20, 2026
Merged

fix(core): resolve 16 audit findings — race conditions, design flaws, glue code, and performance issues#529
JusterZhu merged 2 commits into
masterfrom
fix/core-audit-bugs-and-cleanup

Conversation

@JusterZhu

Copy link
Copy Markdown
Collaborator

Summary

Fixes 16 findings from the Core audit (issue #528). Covers race conditions, rollback logic, null-safety, design inconsistencies, repeated/duplicated code, and performance hot-spots.

Changes

Bug Fixes

  • Race condition in Process.Start (WindowsStrategy, MacStrategy): Removed the HasExited check after Process.Start() — a fast-starting app can exit before we inspect it, causing a false failure.
  • Rollback destroys valid updates (AbstractStrategy): TryRollback() is now guarded by _appliedAnyVersion — only rollback when no version has been applied yet in this batch.
  • Null version.Name in DeleteVersionZip (AbstractStrategy): Added null/whitespace guard.
  • Semaphore timeout logging (DefaultDownloadOrchestrator): Reduced timeout from 5min to 1min, added active-slots diagnostic to the warning message.
  • Concurrent DeleteDirectory (StorageManager): Each file/dir operation is now wrapped in try-catch to handle concurrent modification races gracefully.
  • Re-entry guard for ExecuteAsync (AbstractStrategy): Added Interlocked.Exchange guard to prevent corrupt state from concurrent calls.

Design Improvements

  • XML doc mismatch (UpdateRequest): Updated doc to only list the fields actually validated.
  • Dual SSL policy (VersionService): Removed independent _sharedClient and _globalSslPolicy; delegates entirely to HttpClientProvider.
  • GracefulExit self-Kill (GracefulExit): CurrentProcessAsync() no longer calls Kill() on the current process — lets the async stack unwind naturally.
  • Over-broad directory skip (BlackMatcher): Changed IndexOf to StartsWith prefix matching to avoid false-positive directory skips.
  • Global lock in Option (Option): Replaced lock + TryGetValue with ConcurrentDictionary.GetOrAdd.

Glue Code Removal

  • BlackPolicy init duplicated (Bootstrap, ClientStrategy): Extracted BlackDefaults.CreatePolicyWithDefaults() shared helper.
  • Bowl shutdown duplicated (Bootstrap, ClientStrategy): Removed the redundant CallSmallBowlHomeAsync from Bootstrap; ClientStrategy already handles it.
  • OS strategy resolution duplicated (ClientStrategy, UpdateStrategy): Extracted shared OsStrategyResolver class.
  • Hook semantics inconsistent (UpdateStrategy): SafeOnBeforeUpdateAsync now returns false on exception (abort), matching ClientStrategy behavior.

Performance

  • Repeated version parsing (DownloadPlanBuilder): Pre-parse all asset versions into a dictionary and use a local Lookup() helper.
  • Repeated hash instance (HashMiddleware): Made Sha256HashAlgorithm a static readonly field.
  • Temp directory accumulation (StorageManager): Added CleanupOldTempDirectories() called at bootstrap startup, cleaning dirs older than 24h.

Testing

  • All changes compile on netstandard2.0, net8.0, and net10.0 with 0 errors, 0 warnings.
  • No functional changes to the public API surface.

Closes #528

Copilot AI review requested due to automatic review settings June 20, 2026 12:50

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

This PR addresses the Core audit findings (#528) by hardening update execution against race conditions, removing duplicated “glue code”, aligning cross-component behaviors (hooks, SSL policy), and improving performance hot spots.

Changes:

  • Fixes several race/concurrency issues (process start TOCTOU, re-entrant execution guard, safer directory deletion) and corrects rollback semantics to avoid undoing already-applied versions.
  • Removes duplicated logic by extracting shared helpers (OS strategy resolution, blacklist defaults) and unifying SSL policy management through HttpClientProvider.
  • Improves performance via cached version parsing, reusable hash helper instance, and temp directory cleanup at startup.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/c#/GeneralUpdate.Core/Strategy/WindowsStrategy.cs Removes HasExited TOCTOU check after Process.Start.
src/c#/GeneralUpdate.Core/Strategy/MacStrategy.cs Mirrors Windows process-start fix on macOS.
src/c#/GeneralUpdate.Core/Strategy/UpdateStrategy.cs Uses shared OS resolver; aligns hook exception semantics (abort on exception).
src/c#/GeneralUpdate.Core/Strategy/OsStrategyResolver.cs New shared OS strategy/platform resolver to remove duplication.
src/c#/GeneralUpdate.Core/Strategy/ClientStrategy.cs Uses shared OS resolver and shared blacklist defaults helper.
src/c#/GeneralUpdate.Core/Strategy/AbstractStrategy.cs Adds re-entry guard and rollback guard; null-safety for zip deletion.
src/c#/GeneralUpdate.Core/Pipeline/HashMiddleware.cs Reuses a static Sha256HashAlgorithm instance.
src/c#/GeneralUpdate.Core/Network/VersionService.cs Delegates SSL policy and shared client usage to HttpClientProvider.
src/c#/GeneralUpdate.Core/GracefulExit.cs Avoids self-Kill; attempts a gentler self-close path.
src/c#/GeneralUpdate.Core/FileSystem/StorageManager.cs Adds temp cleanup and hardens recursive deletion against concurrent FS changes.
src/c#/GeneralUpdate.Core/FileSystem/BlackMatcher.cs Tightens directory skip matching (prefix-based).
src/c#/GeneralUpdate.Core/FileSystem/BlackDefaults.cs Adds CreatePolicyWithDefaults to centralize blacklist defaulting.
src/c#/GeneralUpdate.Core/Download/Orchestrators/DefaultDownloadOrchestrator.cs Improves semaphore-timeout diagnostics and reduces timeout.
src/c#/GeneralUpdate.Core/Download/DownloadPlanBuilder.cs Caches parsed versions to reduce repeated parsing.
src/c#/GeneralUpdate.Core/Configuration/UpdateRequest.cs Updates XML docs to match actual validation behavior.
src/c#/GeneralUpdate.Core/Configuration/Option.cs Replaces global lock with ConcurrentDictionary.GetOrAdd.
src/c#/GeneralUpdate.Core/Bootstrap/GeneralUpdateBootstrap.cs Removes duplicated bowl shutdown + blacklist init; adds temp cleanup at startup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +250 to +254
// Parse timestamp from "generalupdate_yyyy-MM-dd-HHmmss-fff_PID_name"
// If the PID segment matches the current process, skip it.
var parts = dirName.Split('_');
if (parts.Length >= 4 && int.TryParse(parts[3], out var pid) && pid == currentPid)
continue;
Comment on lines 344 to 349
public static void DeleteDirectory(string targetDir)
{
// Enumerate then delete with per-item exception handling.
// Between enumeration and deletion, concurrent processes may add/remove
// files — handle these races gracefully instead of crashing.
foreach (var file in Directory.GetFiles(targetDir))
Comment on lines +96 to +100
@@ -97,7 +97,7 @@ public bool IsBlacklistedFormat(string extension)
/// </remarks>
public bool ShouldSkipDirectory(string directoryName)
=> _config.Directories?.Any(d =>
directoryName.IndexOf(d, StringComparison.OrdinalIgnoreCase) >= 0) == true;
directoryName.StartsWith(d, StringComparison.OrdinalIgnoreCase)) == true;
Comment on lines +72 to +75
// GetOrAdd is lock-free on ConcurrentDictionary; the factory runs at most once per key.
// If the name was already registered with a different type T, the cast below throws,
// which is far more likely to be a programming error than a runtime concern.
var raw = _registry.GetOrAdd(name, _ => new Option<T>(name, defaultValue));
Comment thread src/c#/GeneralUpdate.Core/GracefulExit.cs
Comment on lines +86 to +92
/// <summary>Pre-parses versions for a list of assets to avoid repeated Semver.TryParse calls.</summary>
private static Dictionary<DownloadAsset, SemVersion?> PreParseVersions(IEnumerable<DownloadAsset> assets)
{
return assets.ToDictionary(
a => a,
a => ParseVersion(a.Version));
}
@JusterZhu JusterZhu force-pushed the fix/core-audit-bugs-and-cleanup branch from dd80200 to 31fa0d4 Compare June 20, 2026 12:56
@JusterZhu JusterZhu closed this Jun 20, 2026
@JusterZhu JusterZhu reopened this Jun 20, 2026
… glue code, and performance issues

This commit addresses the full audit review of GeneralUpdate.Core:

**Bug fixes:**
- Remove HasExited race condition in WindowsStrategy & MacStrategy (#3,#4)
- Only rollback when no version succeeded yet in AbstractStrategy (#5)
- Guard against null version.Name in DeleteVersionZip (#6)
- Better semaphore timeout logging in DefaultDownloadOrchestrator (#8)
- Robust concurrent-safe DeleteDirectory in StorageManager (#10)
- Add re-entry guard in AbstractStrategy.ExecuteAsync (#11)

**Design improvements:**
- Fix XML doc to match actual validation in UpdateRequest.Validate() (#12)
- Unify SSL policy: VersionService delegates to HttpClientProvider (#16,#17)
- GracefulExit self-shutdown no longer calls Kill() on the current process (#18)
- Use StartsWith instead of IndexOf in BlackMatcher.ShouldSkipDirectory (#19)
- Replace lock with ConcurrentDictionary.GetOrAdd in Option.ValueOf (#20)

**Glue code removal:**
- Extract shared BlackDefaults.CreatePolicyWithDefaults() (#21)
- Remove duplicate CallSmallBowlHomeAsync from Bootstrap (#22)
- Extract shared OsStrategyResolver class (#23)
- Make SafeOnBeforeUpdateAsync semantics consistent: exception = abort (#24)

**Performance:**
- Cache parsed SemVers in DownloadPlanBuilder to avoid repeated parsing (#26)
- Reuse Sha256HashAlgorithm as static field in HashMiddleware (#30)
- Add CleanupOldTempDirectories() to prevent temp directory accumulation (#31)

Co-authored-by: Claude <noreply@anthropic.com>
@JusterZhu JusterZhu force-pushed the fix/core-audit-bugs-and-cleanup branch from 31fa0d4 to ee30c85 Compare June 20, 2026 13:02
- Fix PID parse index in CleanupOldTempDirectories (index 2, not 3)
- Wrap all DeleteDirectory enumeration in try/catch for concurrent-safety
- Extract directory name in ShouldSkipDirectory to handle both bare names and full paths
- Update Option.cs GetOrAdd comment to note factory may run multiple times under contention
- Dispose Process handle with 'using' in GracefulExit.CurrentProcessAsync
- Guard PreParseVersions against duplicate keys from custom IDownloadSource implementations

Co-authored-by: Claude <noreply@anthropic.com>
@JusterZhu JusterZhu merged commit 5056a51 into master Jun 20, 2026
3 checks passed
@JusterZhu JusterZhu deleted the fix/core-audit-bugs-and-cleanup branch June 20, 2026 13:11
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.

Audit: GeneralUpdate.Core — race conditions, design flaws, glue code, and performance opportunities

2 participants