Skip to content

Invalidate a manager's installed-package caches when an operation succeeds - #5586

Merged
Gabriel Dufresne (GabrielDuf) merged 1 commit into
mainfrom
fix/installed-version-stale-after-update
Oct 23, 2026
Merged

Gabriel Dufresne (GabrielDuf) merged 1 commit into
mainfrom
fix/installed-version-stale-after-update

Conversation

@GabrielDuf

@GabrielDuf Gabriel Dufresne (GabrielDuf) commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Why

After an update, the Installed Packages tab can keep showing the version that was just replaced until you reload it manually (0.5.3 after updating to 1.1 in the issue).

The refresh the issue asks for already exists. UpdatePackageOperation.HandleSuccess removes the row and re-adds whatever Manager.GetInstalledPackages() returns. That call is fed stale data: several managers answer from a TaskRecycler result taken before the operation finished.

Manager Installed-listing cache
WinGet (native COM helper) 15 s, keyed only on the source-index refresh
Scoop 15 s
Chocolatey, PowerShell, PowerShell 7, .NET Tool (BaseNuGet) joins any listing already in progress

When the list predates the update, ResolveInstalledPackageSnapshotAsync finds no row at the new version. It falls back to the highest match, which is the old version. Update all hits this reliably, because every package after the first finishes inside the first one's 15 s window.

Cargo was already fine: its operation helper clears its own cache when an operation succeeds.

What changed

  • IPackageManager / PackageManager: a per-manager InstalledPackagesGeneration counter and InvalidateInstalledPackages(). NullPackageManager and the CatalogEditorTests fake get no-op versions.

  • PackageOperation: OperationSucceeded calls InvalidateInstalledPackages() before HandleSuccess. This is the one success point for install, update and uninstall operations, whether they run through the CLI, the broker or Cargo's managed installer. The event is raised synchronously, so the counter moves before anything re-lists. Downloads and pre/post commands don't bump it.

  • The installed-listing caches now include the counter in their key:

    • WinGet COM: keyed on both the source-index counter and the new installed counter.
    • Scoop and BaseNuGet: keyed on the installed counter.

    A listing that starts after a successful operation is therefore always fresh. Cargo keeps its existing invalidation.

Verification

  • New tests:

    • NativeWinGetHelperTakesAFreshSnapshotOnceTheInstalledPackagesAreInvalidated
    • ASuccessfulOperationInvalidatesTheInstalledPackagesOfItsManager, which runs a real successful update operation.

    Both fail with the fix removed.

  • UniGetUI.PackageEngine.Tests and UniGetUI.Tests pass.

  • dotnet format whitespace src --folder and dotnet format style src/UniGetUI.Windows.slnx both exit 0 with --verify-no-changes.

  • Full rebuilds of UniGetUI.Windows.slnx before and after: no new warnings in any changed file.

  • In the app: a WinGet update refreshed the Installed tab to the new version without a manual reload.

  • TaskRecycler builds its keys from the delegate's hash code. On both CoreCLR and NativeAOT (.NET 10) that hash includes the delegate's target, so Scoop and the four NuGet managers each keep their own cache entry, as before.

Notes for reviewers

  • Cost: each successful operation makes the next installed listing re-read instead of reusing the 15 s cache. During Update all that's one listing per finished package: about 1 s for WinGet COM, one scoop list for Scoop. The NuGet managers barely change, since they already listed fresh except when joining a listing in progress. HandleSuccess runs in the background, so the queue doesn't wait on it.
  • Failed operations still don't invalidate. That was already the case and is out of scope here.
  • Not verified: the Linux-only UniGetUI.Avalonia.slnx build, and a NativeAOT publish. The change uses no reflection or runtime code generation: delegate calls and one closed generic over a value tuple.

Closes #5362

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.

🔵 Needs a closer look

NativeAOT publish validation and cross-platform build coverage remain unverified and require human sign-off.

No open issues 🎉

What changed in this PR

This PR addresses #5362 by invalidating installed-package caches after successful operations, allowing the existing Installed Packages refresh to retrieve current versions.

Changes:

  • Adds a thread-safe, per-manager cache generation counter and invalidation contract.
  • Invalidates caches before success handling and incorporates the counter into WinGet, Scoop, and NuGet-backed cache keys.
  • Adds regression tests for successful-operation invalidation and fresh WinGet snapshots.
File Description
src/​UniGetUI.Tests/​CatalogEditorTests.cs Updates the test manager for the new interface.
src/​UniGetUI.PackageEngine.Tests/​WinGetManagerTests.cs Tests snapshot refresh after invalidation.
src/​UniGetUI.PackageEngine.Tests/​PackageOperationsTests.cs Tests invalidation after a successful update.
src/​UniGetUI.PackageEngine.PackageManagerClasses/​Manager/​PackageManager.cs Implements atomic cache generation tracking.
src/​UniGetUI.PackageEngine.PackageManagerClasses/​Manager/​Classes/​NullPackageManager.cs Adds no-op invalidation support.
src/​UniGetUI.PackageEngine.Operations/​PackageOperations.cs Invalidates before success handlers run.
src/​UniGetUI.PackageEngine.Managers.WinGet/​ClientHelpers/​NativeWinGetHelper.cs Keys snapshots on source and installed generations.
src/​UniGetUI.PackageEngine.Managers.Scoop/​Scoop.cs Keys installed listings on the generation counter.
src/​UniGetUI.PackageEngine.Managers.Generic.NuGet/​BaseNuGet.cs Prevents reuse of in-progress listings from earlier generations.
src/​UniGetUI.PackageEngine.Interfaces/​IPackageManager.cs Defines the generation and invalidation contract.

Review effort: Balanced

@randy-but-a-ro randy-but-a-ro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🤖 Pull request was approved automatically: the AI review is complete and all its review threads are resolved. 🎉

Integration Details
{
	"deliveryId": "783717c0-cef2-11f1-80c0-adf720254145",
	"headSha": "15364036446bd4f4db3a6b564c2b4196d6f8bac5",
	"reviewer": "copilot-pull-request-reviewer[bot]"
}

@GabrielDuf
Gabriel Dufresne (GabrielDuf) merged commit 0def3dc into main Oct 23, 2026
6 checks passed
@GabrielDuf
Gabriel Dufresne (GabrielDuf) deleted the fix/installed-version-stale-after-update branch October 23, 2026 16:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[IMPROVEMENT] Spot-update metadata for installed packages just updated.

2 participants