fix: don't cache transient cover art failures as permanent misses

The CAA proxy was caching all fetch failures (including 503s and
timeouts) as empty files, treating them as permanent 'no art' misses.
During Internet Archive outages, this meant every album got cached
as having no cover art, and the cache persisted after IA recovered.

Now only 404 responses (no cover art exists) are cached as permanent
misses. 503, timeouts, and other transient errors are not cached,
so the next request retries the fetch.

Also cleared 33 incorrectly cached 0-byte miss files from a
concurrent IA outage.
This commit is contained in:
2026-03-25 14:14:30 -04:00
parent 49a26c6163
commit d5f34f242f
+18 -10
View File
@@ -77,11 +77,13 @@ func (p *CoverArtProxy) GetThumbnail(releaseGroupMBID string) string {
// Fetch from CAA.
url := CoverArtGroupURL(releaseGroupMBID)
data, err := p.fetch(url)
data, cacheable, err := p.fetch(url)
if err != nil || len(data) == 0 {
// Cache the miss as an empty file so we don't retry.
p.writeCache(releaseGroupMBID, nil)
// Only cache permanent misses (404), not transient errors.
if cacheable {
p.writeCache(releaseGroupMBID, nil)
}
return ""
}
@@ -91,37 +93,43 @@ func (p *CoverArtProxy) GetThumbnail(releaseGroupMBID string) string {
return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(data)
}
func (p *CoverArtProxy) fetch(url string) ([]byte, error) {
func (p *CoverArtProxy) fetch(url string) ([]byte, bool, error) {
// Rate-limit CAA requests.
ctx := context.Background()
if err := p.limiter.Wait(ctx); err != nil {
return nil, err
return nil, false, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
return nil, false, err
}
req.Header.Set("User-Agent", lbUserAgent)
resp, err := p.client.Do(req)
if err != nil {
return nil, err
return nil, false, err
}
defer func() { _ = resp.Body.Close() }()
// 404 = no cover art exists — permanent, safe to cache as miss.
if resp.StatusCode == http.StatusNotFound {
return nil, true, nil
}
// Other non-200 = transient error — don't cache.
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %d", ErrCoverArt, resp.StatusCode)
return nil, false, fmt.Errorf("%w: %d", ErrCoverArt, resp.StatusCode)
}
data, err := io.ReadAll(io.LimitReader(resp.Body, thumbnailMaxSize))
if err != nil {
return nil, err
return nil, false, err
}
return data, nil
return data, true, nil
}
func (p *CoverArtProxy) cachePath(mbid string) string {