Split internal/imgcache into the image proxy and the cache (closes #317)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The new package internal/imageproxy holds Proxy, which was imgcache.Service, with its Config, ImageResponse, CacheStatus and errors. internal/imgcache holds the cache only, and imgcache.CacheConfig is imgcache.Config. The cache reads and writes each kind it keeps with one pair of methods: Variant and PutVariant, Source and PutSource, Failure and PutFailure. Source replaces LookupSource followed by GetSourceContent. imgcache.go and module.go are gone. The proxy's tests moved with it. They reach the cache through its database, its state directory and its metrics registry, and run its reconciliation pass with a second cache, as pixad does at startup. Model: opus-5-5
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -15,8 +16,7 @@ import (
|
||||
|
||||
// pendingAccessTimesWait is how long a test waits for the database's
|
||||
// connection, for an access time to reach the database, for a write to start
|
||||
// waiting for the connection, for a request to return past its deadline, or
|
||||
// for StopPendingAccessTimeWrites to return.
|
||||
// waiting for the connection, or for StopPendingAccessTimeWrites to return.
|
||||
const pendingAccessTimesWait = 5 * time.Second
|
||||
|
||||
// otherWritesWait is how long a test gives other writes, once one write waits
|
||||
@@ -56,52 +56,6 @@ func waitForConnectionWaits(t *testing.T, db *sql.DB, waits int64) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestService_Get_ReturnsByItsDeadlineWhileTheDatabaseIsBusy holds the
|
||||
// database's one connection while a request whose fetch is held reaches its
|
||||
// deadline. The request must return with the deadline's error while the
|
||||
// connection is still held, as it waits for no database write past its
|
||||
// deadline.
|
||||
func TestService_Get_ReturnsByItsDeadlineWhileTheDatabaseIsBusy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
svc, fixtures, fetcher := setupHeldFetchService(t)
|
||||
|
||||
svc.cache.StartPendingAccessTimeWrites()
|
||||
defer func() { _ = svc.cache.StopPendingAccessTimeWrites(t.Context()) }()
|
||||
|
||||
// Room for the request to reach its fetch on a busy host
|
||||
const timeout = 2 * time.Second
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), timeout)
|
||||
defer cancel()
|
||||
|
||||
deadline, _ := ctx.Deadline()
|
||||
|
||||
results := startGet(ctx, svc, photoVariant(fixtures, 85, imagerequest.FitCover))
|
||||
|
||||
// The request has made its database reads by the time it fetches.
|
||||
select {
|
||||
case <-fetcher.started:
|
||||
case <-time.After(timeout):
|
||||
t.Fatal("request did not reach its fetch by its deadline")
|
||||
}
|
||||
|
||||
releaseDatabase := holdDatabase(t, svc.cache)
|
||||
defer releaseDatabase()
|
||||
|
||||
select {
|
||||
case got := <-results:
|
||||
t.Logf("Get() returned %v after its deadline, error = %v",
|
||||
time.Since(deadline), got.err)
|
||||
|
||||
if !errors.Is(got.err, context.DeadlineExceeded) {
|
||||
t.Errorf("Get() error = %v, want %v", got.err, context.DeadlineExceeded)
|
||||
}
|
||||
case <-time.After(time.Until(deadline) + pendingAccessTimesWait):
|
||||
t.Fatal("request did not return while the database was busy")
|
||||
}
|
||||
}
|
||||
|
||||
// TestStopPendingAccessTimeWrites_LetsTheWriteUnderWayFinish holds the
|
||||
// database's one connection while the goroutine that writes the pending
|
||||
// access times waits for it, hands over another access time, then stops that
|
||||
@@ -242,11 +196,11 @@ func TestStopPendingAccessTimeWrites_ReturnsWhenItsContextEnds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLookupSource_HandsAccessTimeToTheBackgroundWriter finds a cached source
|
||||
// whose access time is an hour old. LookupSource must not write the access
|
||||
// TestSource_HandsAccessTimeToTheBackgroundWriter finds a cached source
|
||||
// whose access time is an hour old. Source must not write the access
|
||||
// time itself but hand it over to be written, and StopPendingAccessTimeWrites
|
||||
// must then write it.
|
||||
func TestLookupSource_HandsAccessTimeToTheBackgroundWriter(t *testing.T) {
|
||||
func TestSource_HandsAccessTimeToTheBackgroundWriter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cache, _ := newEvictionTestCache(t, 1<<30)
|
||||
@@ -261,15 +215,21 @@ func TestLookupSource_HandsAccessTimeToTheBackgroundWriter(t *testing.T) {
|
||||
`
|
||||
anHourAgoInDatabase := anHourAgo.UTC().Format(sqliteTimestampFormat)
|
||||
|
||||
found, err := cache.LookupSource(t.Context(), &imagerequest.Request{
|
||||
Source: imagerequest.Source{Host: "example.com", Path: "/photo.jpg"},
|
||||
})
|
||||
if err != nil || found != key {
|
||||
t.Fatalf("LookupSource() = %q, %v; want %q", found, err, key)
|
||||
reader, _, err := cache.Source(t.Context(),
|
||||
imagerequest.Source{Host: "example.com", Path: "/photo.jpg"})
|
||||
if err != nil {
|
||||
t.Fatalf("Source() error = %v; want the source of %q", err, key)
|
||||
}
|
||||
|
||||
found, err := io.ReadAll(reader)
|
||||
_ = reader.Close()
|
||||
|
||||
if err != nil || string(found) != "source bytes" {
|
||||
t.Fatalf("Source() reads %q, %v; want the source of %q", found, err, key)
|
||||
}
|
||||
|
||||
if countRows(t, cache, touchedQuery, string(key), anHourAgoInDatabase) != 0 {
|
||||
t.Error("LookupSource wrote the source's access time itself")
|
||||
t.Error("Source wrote the source's access time itself")
|
||||
}
|
||||
|
||||
if waiting := len(cache.pendingSourceAccessTimes); waiting != 1 {
|
||||
|
||||
Reference in New Issue
Block a user