No behavior changes. Covers the purely mechanical findings the canonical
v2.12.2 config raises on the cache-size/eviction work:
- nolintlint: deleted 7 dead //nolint:gosec directives (cachesize.go x2,
config.go, eviction.go x2, storage.go x2). gosec never raises G115/G703
on those lines under the pinned toolchain, exactly the defect class
that failed round 2 of this PR. The 6 live gosec suppressions are
untouched.
- funcorder: moved writeIfAbsent after Exists (storage.go) and
touchVariant/touchSourceContent after IncrementStats (cache.go).
- lll: wrapped over-length signatures, calls and messages at 88 columns.
- paralleltest: t.Parallel() on the new eviction, contentlock and
cache_max_bytes tests and their subtests. configFromYAML uses only
t.TempDir, so the config cases are parallel-safe.
- noctx: test helper DB calls now use ExecContext/QueryContext/
QueryRowContext with t.Context().
- goconst: extracted testHeaderContentType into the shared test constant
block and testVariantKeyOne into the eviction tests; reused the
existing testContentTypeJPEG and keyCacheMaxBytes constants.
- modernize: interface{} to any, atomic.Int32 for the contentlock
counters, min/max in ComputeDefaultCacheMaxBytes.
- intrange: integer range loops in the contentlock tests.
- wsl_v5: whitespace before the contentlock rendezvous statements.
- sloglint: slog.DiscardHandler in the config test logger.
- cyclop/funlen: split TestZeroMaxBytesDisablesDiskCache into four
assertion helpers and extracted the concurrent store goroutine from
TestEvictSourceBlobExcludesConcurrentStoreOfIdenticalContent. Every
assertion is preserved verbatim; only their grouping changed.
110 lines
3.4 KiB
Go
110 lines
3.4 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
// DefaultCacheMaxBytesFloor is the minimum computed default for the
|
|
// cache_max_bytes setting: 500 MiB. The floor applies only to the
|
|
// computed default (when the key is omitted from the configuration),
|
|
// never to explicitly configured values.
|
|
const DefaultCacheMaxBytesFloor int64 = 524288000
|
|
|
|
// cacheDirPerms is the permission mode for the cache directory created
|
|
// before probing free space, matching the state directory permissions.
|
|
const cacheDirPerms = 0o750
|
|
|
|
// freeSpaceFractionNumerator and freeSpaceFractionDenominator express
|
|
// the 75% share of free space used for the computed default limit as
|
|
// integer arithmetic (dividing before multiplying avoids overflow).
|
|
const (
|
|
freeSpaceFractionNumerator uint64 = 3
|
|
freeSpaceFractionDenominator uint64 = 4
|
|
)
|
|
|
|
// FreeSpaceProbeFunc reports the number of free bytes available on the
|
|
// filesystem containing path. It is a function type so tests can
|
|
// inject a fake probe instead of depending on the host disk.
|
|
type FreeSpaceProbeFunc func(path string) (uint64, error)
|
|
|
|
// defaultFreeSpaceProbe reports free filesystem bytes via statfs on
|
|
// the given path, as available to unprivileged processes.
|
|
func defaultFreeSpaceProbe(path string) (uint64, error) {
|
|
var stat syscall.Statfs_t
|
|
if err := syscall.Statfs(path, &stat); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if stat.Bsize < 0 {
|
|
return 0, fmt.Errorf("statfs reported negative block size %d for %q", stat.Bsize, path)
|
|
}
|
|
|
|
blockSize := uint64(stat.Bsize)
|
|
|
|
return stat.Bavail * blockSize, nil
|
|
}
|
|
|
|
// ComputeDefaultCacheMaxBytes returns the default cache size limit for
|
|
// the filesystem containing cacheDir: 75% of the free bytes reported
|
|
// by probe, with a floor of DefaultCacheMaxBytesFloor.
|
|
func ComputeDefaultCacheMaxBytes(
|
|
cacheDir string, probe FreeSpaceProbeFunc,
|
|
) (int64, error) {
|
|
freeBytes, err := probe(cacheDir)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config key %q: cannot determine free space for %q: %w",
|
|
"cache_max_bytes", cacheDir, err)
|
|
}
|
|
|
|
computed := freeBytes / freeSpaceFractionDenominator * freeSpaceFractionNumerator
|
|
computed = min(computed, math.MaxInt64)
|
|
|
|
limit := int64(computed)
|
|
limit = max(limit, DefaultCacheMaxBytesFloor)
|
|
|
|
return limit, nil
|
|
}
|
|
|
|
// resolveCacheMaxBytes finalizes CacheMaxBytes after state_dir
|
|
// validation: an explicitly configured value is kept as-is (no floor
|
|
// applies), while an omitted key receives the computed default based
|
|
// on free space in <state_dir>/cache/. The cache directory is created
|
|
// first so statfs measures the filesystem that will actually hold the
|
|
// cache. The effective limit is logged either way.
|
|
func (c *Config) resolveCacheMaxBytes(
|
|
log *slog.Logger, probe FreeSpaceProbeFunc,
|
|
) error {
|
|
if !c.cacheMaxBytesExplicit {
|
|
cacheDir := filepath.Join(c.StateDir, "cache")
|
|
|
|
if err := os.MkdirAll(cacheDir, cacheDirPerms); err != nil {
|
|
return fmt.Errorf("config key %q: cannot create cache directory %q: %w",
|
|
"cache_max_bytes", cacheDir, err)
|
|
}
|
|
|
|
limit, err := ComputeDefaultCacheMaxBytes(cacheDir, probe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.CacheMaxBytes = limit
|
|
|
|
log.Info("computed default cache size limit from free space",
|
|
"cache_max_bytes", limit,
|
|
"cache_dir", cacheDir,
|
|
)
|
|
}
|
|
|
|
log.Info("effective cache size limit",
|
|
"cache_max_bytes", c.CacheMaxBytes,
|
|
"cache_disabled", c.CacheMaxBytes == 0,
|
|
)
|
|
|
|
return nil
|
|
}
|