Skip to content

Refresh HTTP cache on miss for exact-version restore (#3116)#7321

Open
Saibamen wants to merge 1 commit into
NuGet:devfrom
Saibamen:fix/3116-refresh-http-cache-on-miss
Open

Refresh HTTP cache on miss for exact-version restore (#3116)#7321
Saibamen wants to merge 1 commit into
NuGet:devfrom
Saibamen:fix/3116-refresh-http-cache-on-miss

Conversation

@Saibamen
Copy link
Copy Markdown

@Saibamen Saibamen commented Apr 26, 2026

Summary

Fixes the long-standing publish-then-consume failure tracked in NuGet/Home#3116. When a restore asks for an exact package version that the cached versions list does not contain, NuGet now refreshes the HTTP cache once and retries before declaring the package unresolved. This eliminates spurious NU1102 failures without users having to discover --no-cache, RestoreNoCache, or dotnet nuget locals http-cache --clear.

The change extends the existing 2-attempt retry loop in ResolverUtility.FindLibraryEntryAsync and reuses SourceCacheContext.WithRefreshCacheTrue(), which already plumbs through to the in-memory caches in RemoteV3FindPackageByIdResource / HttpFileSystemBasedFindPackageByIdResource and forces MaxAge=0 on the on-disk HTTP cache.

Fixes NuGet/Home#3116

Behavior

Refresh fires only when all are true:

  • The match is null or LibraryType.Unresolved after the first pass.
  • The version range is exact (non-floating, min-inclusive).
  • At least one consulted provider is HTTP-backed.
  • The cache hasn't already been refreshed for this (libraryRange, framework) key in this restore.
  • NUGET_HTTP_CACHE_REFRESH_ON_MISS is not set to false / 0.

Bounded to one extra HTTP round-trip per cache key per restore, only on the failure path. Floating ranges already satisfied by the cache are intentionally left alone.

Design proposal

Companion design proposal: NuGet/Home#14872 (accepted/2026/refresh-http-cache-on-miss.md). It covers the broader rationale, alternatives considered (not caching 404s, shortening TTL, always going to origin, honouring server Cache-Control), prior art (Cargo, npm, pip), and follow-ups deferred from this PR (nuget.config knob http_cache_refresh_on_miss, MSBuild property RestoreHttpCacheRefreshOnMiss, HttpCacheRefreshOnMissCount telemetry, application to the MSBuild SDK resolver).

Test plan

  • New unit tests in FindPackageTests:
    • FindPackage_WhenExactVersionNotInCachedList_RefreshesHttpCacheAndResolves - cache miss for exact version triggers exactly one refresh and resolves.
    • FindPackage_WhenExactVersionStillNotFoundAfterRefresh_ReturnsUnresolved - version genuinely absent results in exactly two find calls, result is Unresolved.
    • FindPackage_WhenFloatingRangeIsSatisfiedFromStaleCache_DoesNotRefresh - floating ranges satisfied by cache do not trigger a refresh.
  • All 14 tests in FindPackageTests pass on net10.0.
  • NuGet.DependencyResolver.Core.csproj builds clean for both net472 and net8.0.
  • Manual repro of Refactor logic in _GetRestoreProjectStyle target to a task #3116 (publish package, immediately restore on a machine with stale cache) - to be validated by a reviewer with a private feed.

Notes / follow-ups

  • This PR ships only the env-var opt-out (NUGET_HTTP_CACHE_REFRESH_ON_MISS). The nuget.config http_cache_refresh_on_miss key and RestoreHttpCacheRefreshOnMiss MSBuild property described in the design proposal are deferred to a follow-up to keep this change small.
  • xliff localization files were touched by the build's auto-sync; translators will localize Log_RefreshingHttpCacheOnMiss in the normal localization flow.

When a restore asks for an exact package version that the cached
versions list does not contain, refresh the HTTP cache once and retry
before declaring the package unresolved. This eliminates spurious
NU1102 failures for the common publish-then-consume scenario without
requiring users to discover --no-cache or clear the HTTP cache by
hand.

The refresh reuses the existing SourceCacheContext.WithRefreshCacheTrue
plumbing, is bounded to at most one extra HTTP round-trip per cache
key per restore, only fires on the failure path, and is opt-out via
the NUGET_HTTP_CACHE_REFRESH_ON_MISS environment variable. Floating
ranges already satisfied by the cached list are left alone to avoid
amplifying traffic on common restores.

Addresses NuGet/Home#3116.

bool isStaleCacheMiss = match == null || IsCacheStaleForExactVersion(match, libraryRange);

if (isStaleCacheMiss
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't know/think this is enough.
My interpretation is that this won't change anything right now.

ResolverUtility is called on per project basis and it may even be called multiple times during a single restore operation for different package versions/framework versions.

The SourceRepositoryDependencyProvider, which is the abstraction that handles querying the sources has a caching layer on its own that's not gonna be get overwritten by just requesting a cache refresh.
That component is shared across different projects for perf purposes, so that'd need to know it needs to do something different as well.

During a single restore operation, many projects may be restored, so if a refresh brings in new data, we'd need to ensure that we're not refreshing more than once during an operation. Not even for consistency purposes, but for perf as well

For example, A -> B -> C

If C has a graph where's a missing package version somewhere.
We wouldn't wasn't the restores for all 3 projects to request and get a refresh either. It's just unnecessary http calls.

Refresh anytime a version is missed is probably too much.
Refreshing once per restore operation when a version is missed would be great, but the perfect one would be the one the cache wouldn't be refreshed if the cache was generated during the current operation already.

@dotnet-policy-service dotnet-policy-service Bot added the Status:No recent activity PRs that have not had any recent activity and will be closed if the label is not removed label May 4, 2026
@nkolev92 nkolev92 self-assigned this May 28, 2026
@dotnet-policy-service dotnet-policy-service Bot removed the Status:No recent activity PRs that have not had any recent activity and will be closed if the label is not removed label May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Community PRs created by someone not in the NuGet team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NuGet should refresh its HTTP cache if it can't find the package requested

2 participants