Keep application/octet-stream out of memory for variants (closes #70)
check / check (push) Successful in 3m30s

VariantStorage.LoadWithMeta now returns an empty content type when the
.meta file is missing or unreadable, and GetVariant serves
application/octet-stream in that case without keeping it in memory.
Memory now only ever holds a content type read from a .meta file or
passed to StoreVariant, so a read that ran before a store wrote the
.meta file can no longer leave the wrong type in memory, however it
interleaves with the store, other reads and eviction.

Model: opus-5-5
This commit is contained in:
2026-09-29 08:28:26 +00:00
parent 8620a46c90
commit de6f5e34ea
4 changed files with 25 additions and 15 deletions
+7 -6
View File
@@ -34,12 +34,13 @@ exhaustion
`Cache.metaCache` holds the content types of up to 10,000 variants in an LRU `Cache.metaCache` holds the content types of up to 10,000 variants in an LRU
(`github.com/hashicorp/golang-lru/v2`), filled by `StoreVariant` and by (`github.com/hashicorp/golang-lru/v2`), filled by `StoreVariant` and by
`GetVariant` after it reads a `.meta` file, where a type `StoreVariant` added `GetVariant` after it reads a `.meta` file, where a type `StoreVariant` added
meanwhile is kept over the one read; for a variant it holds, `GetVariant` meanwhile is kept over the one read, and never with the
skips the `.meta` read, still opening the variant file and taking the size `application/octet-stream` served for a variant without one; for a variant it
from it; eviction removes the entry before deleting the files, and holds, `GetVariant` skips the `.meta` read, still opening the variant file and
`GetVariant` removes it when the file will not open; the cap is a constant, taking the size from it; eviction removes the entry before deleting the files,
not a setting; the unused `variantMeta` type is gone; `README.md` describes and `GetVariant` removes it when the file will not open; the cap is a
it. constant, not a setting; the unused `variantMeta` type is gone; `README.md`
describes it.
- 2026-09-29 Dockerfiles install through `script/bootstrap` (closes #95): the - 2026-09-29 Dockerfiles install through `script/bootstrap` (closes #95): the
`Dockerfile` lint and build stages and `Dockerfile.lint` copy `script/`, `Dockerfile` lint and build stages and `Dockerfile.lint` copy `script/`,
`go.mod` and `go.sum`, then run `script/bootstrap` in place of their own `go.mod` and `go.sum`, then run `script/bootstrap` in place of their own
+12 -4
View File
@@ -184,7 +184,9 @@ func (c *Cache) Lookup(ctx context.Context, req *ImageRequest) (*LookupResult, e
// GetVariant returns a reader, size, and content type for a cached // GetVariant returns a reader, size, and content type for a cached
// variant. The content type comes from metaCache, or else from the // variant. The content type comes from metaCache, or else from the
// variant's .meta file and is then kept in metaCache. // variant's .meta file and is then kept in metaCache. A variant with
// no .meta file is served as application/octet-stream, which is not
// kept.
func (c *Cache) GetVariant(cacheKey VariantKey) (io.ReadCloser, int64, string, error) { func (c *Cache) GetVariant(cacheKey VariantKey) (io.ReadCloser, int64, string, error) {
if c.disabled { if c.disabled {
return nil, 0, "", ErrNotFound return nil, 0, "", ErrNotFound
@@ -521,9 +523,11 @@ func (c *Cache) IncrementTransformCount(ctx context.Context) {
// loadVariantWithMeta is GetVariant for a variant metaCache does not // loadVariantWithMeta is GetVariant for a variant metaCache does not
// hold: it reads the content type from the variant's .meta file and // hold: it reads the content type from the variant's .meta file and
// keeps it in metaCache, unless a StoreVariant has put one there // keeps it in metaCache, unless a StoreVariant has put one there
// meanwhile. The stored one wins, since this read may have found the // meanwhile, as the store's is newer. A read that finds no .meta file,
// variant file before the store wrote the .meta file, and so got // as one can between a store's writing of the variant file and of its
// application/octet-stream. // .meta file, serves application/octet-stream and keeps nothing, so
// metaCache only ever holds a type read from a .meta file or passed to
// StoreVariant.
func (c *Cache) loadVariantWithMeta( func (c *Cache) loadVariantWithMeta(
cacheKey VariantKey, cacheKey VariantKey,
) (io.ReadCloser, int64, string, error) { ) (io.ReadCloser, int64, string, error) {
@@ -532,6 +536,10 @@ func (c *Cache) loadVariantWithMeta(
return nil, 0, "", err return nil, 0, "", err
} }
if contentType == "" {
return reader, size, fallbackContentType, nil
}
c.metaCache.ContainsOrAdd(cacheKey, contentType) c.metaCache.ContainsOrAdd(cacheKey, contentType)
return reader, size, contentType, nil return reader, size, contentType, nil
+2 -2
View File
@@ -39,8 +39,8 @@ const tempFilePrefix = ".tmp-"
// to each variant file. // to each variant file.
const variantMetaSuffix = ".meta" const variantMetaSuffix = ".meta"
// fallbackContentType is recorded when a reconciled variant file has // fallbackContentType is the content type given to a variant file that
// no readable .meta sidecar. // has no readable .meta sidecar, when it is served or reconciled.
const fallbackContentType = "application/octet-stream" const fallbackContentType = "application/octet-stream"
// UsageBytes returns the total number of bytes of cache content // UsageBytes returns the total number of bytes of cache content
+4 -3
View File
@@ -531,7 +531,8 @@ func (s *VariantStorage) LoadWithSize(key VariantKey) (io.ReadCloser, int64, err
} }
// LoadWithMeta returns a reader, size, and content type for the content at // LoadWithMeta returns a reader, size, and content type for the content at
// the given key. // the given key. The content type is read from the .meta file, and is
// empty when that file is missing or unreadable.
func (s *VariantStorage) LoadWithMeta( func (s *VariantStorage) LoadWithMeta(
key VariantKey, key VariantKey,
) (io.ReadCloser, int64, string, error) { ) (io.ReadCloser, int64, string, error) {
@@ -540,8 +541,8 @@ func (s *VariantStorage) LoadWithMeta(
return nil, 0, "", err return nil, 0, "", err
} }
// Load metadata for content type var contentType string
contentType := "application/octet-stream" // fallback
metaPath := s.keyToPath(key) + ".meta" metaPath := s.keyToPath(key) + ".meta"
metaData, err := os.ReadFile(metaPath) //nolint:gosec // path derived from cache key metaData, err := os.ReadFile(metaPath) //nolint:gosec // path derived from cache key