Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbe45af544 | ||
|
|
b9d32985f6 | ||
|
|
92e48a7a91 | ||
|
|
d930e352b0 | ||
|
|
6830bdc5de |
@@ -38,6 +38,11 @@ P2: security: referer blacklist
|
|||||||
goroutine exits, stops waiting and returns its error; the handlers' stop hook
|
goroutine exits, stops waiting and returns its error; the handlers' stop hook
|
||||||
passes fx's stop context, so an eviction still running when fx's stop
|
passes fx's stop context, so an eviction still running when fx's stop
|
||||||
deadline ends fails the stop and makes the exit code 1.
|
deadline ends fails the stop and makes the exit code 1.
|
||||||
|
- 2026-10-04 dead code in `internal/imgcache` is gone (closes #73): `Purge`,
|
||||||
|
which only returned an error and which nothing called, is no longer part of
|
||||||
|
the `ImageCache` interface or `Service`; the `SignatureValidator`,
|
||||||
|
`Allowlist` and `Storage` interfaces, which nothing implemented or used, are
|
||||||
|
deleted. Nothing else changes.
|
||||||
- 2026-10-04 upstream host semaphores and variant `.meta` files no longer
|
- 2026-10-04 upstream host semaphores and variant `.meta` files no longer
|
||||||
outlive their use (closes #87): the fetcher counts the fetches holding or
|
outlive their use (closes #87): the fetcher counts the fetches holding or
|
||||||
waiting for a slot of each upstream host's semaphore and removes the host's
|
waiting for a slot of each upstream host's semaphore and removes the host's
|
||||||
|
|||||||
@@ -1077,6 +1077,30 @@ func TestReconciliationWalksStopOnceCancelled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestReconciliationPassLogsNoWarningOnceCancelled checks that a
|
||||||
|
// reconciliation pass run with an already cancelled context logs no
|
||||||
|
// warning, so a periodic tick the loop takes after a stop adds no
|
||||||
|
// warning to the one from the pass the stop interrupted.
|
||||||
|
func TestReconciliationPassLogsNoWarningOnceCancelled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
||||||
|
|
||||||
|
var logBuf bytes.Buffer
|
||||||
|
|
||||||
|
cache.log = slog.New(slog.NewJSONHandler(&logBuf, nil))
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
cache.runReconciliationPass(ctx)
|
||||||
|
t.Logf("log output: %s", logBuf.String())
|
||||||
|
|
||||||
|
if strings.Contains(logBuf.String(), `"level":"WARN"`) {
|
||||||
|
t.Errorf("runReconciliationPass logged a warning with a cancelled context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestEvictSourceBlobExcludesConcurrentStoreOfIdenticalContent exercises
|
// TestEvictSourceBlobExcludesConcurrentStoreOfIdenticalContent exercises
|
||||||
// the exact TOCTOU window between evictSourceBlob's row-deletion
|
// the exact TOCTOU window between evictSourceBlob's row-deletion
|
||||||
// transaction commit and its content file unlink: a concurrent
|
// transaction commit and its content file unlink: a concurrent
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -154,9 +153,6 @@ type ImageCache interface {
|
|||||||
// Warm pre-fetches and caches an image without returning it
|
// Warm pre-fetches and caches an image without returning it
|
||||||
Warm(ctx context.Context, req *ImageRequest) error
|
Warm(ctx context.Context, req *ImageRequest) error
|
||||||
|
|
||||||
// Purge removes a cached image
|
|
||||||
Purge(ctx context.Context, req *ImageRequest) error
|
|
||||||
|
|
||||||
// Stats returns cache statistics
|
// Stats returns cache statistics
|
||||||
Stats(ctx context.Context) (*CacheStats, error)
|
Stats(ctx context.Context) (*CacheStats, error)
|
||||||
}
|
}
|
||||||
@@ -176,29 +172,3 @@ type CacheStats struct {
|
|||||||
// HitRate is HitCount / (HitCount + MissCount)
|
// HitRate is HitCount / (HitCount + MissCount)
|
||||||
HitRate float64
|
HitRate float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignatureValidator validates request signatures
|
|
||||||
type SignatureValidator interface {
|
|
||||||
// Validate checks if the signature is valid for the request
|
|
||||||
Validate(req *ImageRequest) error
|
|
||||||
// Generate creates a signature for a request
|
|
||||||
Generate(req *ImageRequest) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allowlist checks if a URL is allowlisted (no signature required)
|
|
||||||
type Allowlist interface {
|
|
||||||
// IsAllowlisted returns true if the URL doesn't require a signature
|
|
||||||
IsAllowlisted(u *url.URL) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Storage handles persistent storage of cached content
|
|
||||||
type Storage interface {
|
|
||||||
// Store saves content and returns its hash
|
|
||||||
Store(ctx context.Context, content io.Reader) (hash string, err error)
|
|
||||||
// Load retrieves content by hash
|
|
||||||
Load(ctx context.Context, hash string) (io.ReadCloser, error)
|
|
||||||
// Delete removes content by hash
|
|
||||||
Delete(ctx context.Context, hash string) error
|
|
||||||
// Exists checks if content exists
|
|
||||||
Exists(ctx context.Context, hash string) (bool, error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,11 +56,10 @@ type ServiceConfig struct {
|
|||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static errors for service construction and unimplemented operations.
|
// Static errors for service construction.
|
||||||
var (
|
var (
|
||||||
errCacheRequired = errors.New("cache is required")
|
errCacheRequired = errors.New("cache is required")
|
||||||
errSigningKeyRequired = errors.New("signing key is required")
|
errSigningKeyRequired = errors.New("signing key is required")
|
||||||
errPurgeNotImplemented = errors.New("purge not implemented")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService creates a new image service.
|
// NewService creates a new image service.
|
||||||
@@ -189,11 +188,6 @@ func (s *Service) Warm(ctx context.Context, req *ImageRequest) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge removes a cached image. Purging is not implemented yet.
|
|
||||||
func (s *Service) Purge(_ context.Context, _ *ImageRequest) error {
|
|
||||||
return errPurgeNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats returns cache statistics.
|
// Stats returns cache statistics.
|
||||||
func (s *Service) Stats(ctx context.Context) (*CacheStats, error) {
|
func (s *Service) Stats(ctx context.Context) (*CacheStats, error) {
|
||||||
return s.cache.Stats(ctx)
|
return s.cache.Stats(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user