fix(storage): treat dedupe-candidate cache miss as no candidates, not an error (#4122)

GetAllDedupeReposCandidates propagated zerr.ErrCacheMiss from the cache's
GetAllBlobs verbatim. But a cache miss is the normal case for a not-yet-cached
blob — the first push of a new blob, or a cross-repo mount check during a push
— and semantically means "no dedupe/mount candidates", not a failure.

Propagating it caused canMount (used by the CheckBlob and CreateBlobUpload
handlers) to surface the error, which the route handlers log as an
"unexpected error". With remote (e.g. S3) storage a cache is always present
(dedupe:false does not disable it), so this logs an error-level line for every
fresh blob digest on every push — significant log spam during bulk pushes and
cross-repo mounts, with no functional impact (the push still succeeds via a
normal upload).

Handle ErrCacheMiss the same way as the existing nil-cache branch above:
return no candidates and no error.

Signed-off-by: Janko Thyson <janko@kaosmaps.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janko Thyson
2026-06-11 09:24:52 +02:00
committed by GitHub
parent 66e9cfb01f
commit a675ac96b9
2 changed files with 22 additions and 0 deletions
+10
View File
@@ -1388,6 +1388,16 @@ func (is *ImageStore) GetAllDedupeReposCandidates(digest godigest.Digest) ([]str
blobsPaths, err := is.cache.GetAllBlobs(digest)
if err != nil {
// A cache miss means the digest is not present in the cache yet, so there
// are simply no dedupe/mount candidates for it — that is the normal case
// for a not-yet-cached blob, not a failure. Treat it like the nil-cache
// case above and return no candidates rather than propagating the error
// (which callers log as an "unexpected error", spamming the logs on every
// fresh blob during pushes/cross-repo mount checks).
if errors.Is(err, zerr.ErrCacheMiss) {
return nil, nil //nolint:nilnil
}
return nil, err
}
+12
View File
@@ -644,6 +644,18 @@ func TestGetAllDedupeReposCandidates(t *testing.T) {
slices.Sort(repos)
So(repoNames, ShouldResemble, repos)
})
Convey("A digest with no cached blob returns no candidates and no error", t, func(c C) {
// A cache miss is the normal case for a not-yet-cached blob (e.g. the
// first push of a new blob, or a cross-repo mount check during a push).
// It must surface as "no dedupe/mount candidates", NOT as an error the
// caller logs as "unexpected error".
uncachedDigest := godigest.FromBytes([]byte("blob that was never pushed to any repo"))
repos, err := imgStore.GetAllDedupeReposCandidates(uncachedDigest)
So(err, ShouldBeNil)
So(repos, ShouldBeEmpty)
})
})
}
}