Stop cache eviction in progress at shutdown (closes #102) #173
@@ -6,8 +6,10 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -866,12 +868,17 @@ func TestPeriodicReconciliationAdoptsFileThatAppearsAfterStartup(t *testing.T) {
|
|||||||
// TestStopEvictionInterruptsPassInProgress holds the test database's
|
// TestStopEvictionInterruptsPassInProgress holds the test database's
|
||||||
// only connection, so the startup reconciliation pass waits for it, and
|
// only connection, so the startup reconciliation pass waits for it, and
|
||||||
// checks that StopEviction stops that pass instead of waiting for the
|
// checks that StopEviction stops that pass instead of waiting for the
|
||||||
// connection to come free.
|
// connection to come free, and that the stop logs one warning: the
|
||||||
|
// interrupted reconciliation's, with no eviction pass started after it.
|
||||||
func TestStopEvictionInterruptsPassInProgress(t *testing.T) {
|
func TestStopEvictionInterruptsPassInProgress(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cache, _ := newEvictionTestCache(t, 1<<30)
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
||||||
|
|
||||||
|
var logBuf bytes.Buffer
|
||||||
|
|
||||||
|
cache.log = slog.New(slog.NewJSONHandler(&logBuf, nil))
|
||||||
|
|
||||||
conn, err := cache.db.Conn(t.Context())
|
conn, err := cache.db.Conn(t.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to take the database connection: %v", err)
|
t.Fatalf("failed to take the database connection: %v", err)
|
||||||
@@ -902,6 +909,72 @@ func TestStopEvictionInterruptsPassInProgress(t *testing.T) {
|
|||||||
t.Fatalf("StopEviction() error = %v, want nil: the pass waiting for "+
|
t.Fatalf("StopEviction() error = %v, want nil: the pass waiting for "+
|
||||||
"the database did not stop", err)
|
"the database did not stop", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Logf("log output: %s", logBuf.String())
|
||||||
|
|
||||||
|
warnings := strings.Count(logBuf.String(), `"level":"WARN"`)
|
||||||
|
if warnings != 1 {
|
||||||
|
t.Errorf("the stop logged %d warnings, want 1", warnings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEvictToLimitStopsAtNextCandidateOnceCancelled cancels the context
|
||||||
|
// while the oldest of three source blobs is being evicted, and checks that
|
||||||
|
// EvictToLimit then returns context.Canceled without evicting the other
|
||||||
|
// two or logging a warning for either of them.
|
||||||
|
func TestEvictToLimitStopsAtNextCandidateOnceCancelled(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cache, _ := newEvictionTestCache(t, 1)
|
||||||
|
|
||||||
|
var logBuf bytes.Buffer
|
||||||
|
|
||||||
|
cache.log = slog.New(slog.NewJSONHandler(&logBuf, nil))
|
||||||
|
|
||||||
|
hashes := []ContentHash{
|
||||||
|
storeEvictionTestSource(t, cache, "cancel.example.com", "/a.jpg",
|
||||||
|
bytes.Repeat([]byte{0x61}, 1000)),
|
||||||
|
storeEvictionTestSource(t, cache, "cancel.example.com", "/b.jpg",
|
||||||
|
bytes.Repeat([]byte{0x62}, 1000)),
|
||||||
|
storeEvictionTestSource(t, cache, "cancel.example.com", "/c.jpg",
|
||||||
|
bytes.Repeat([]byte{0x63}, 1000)),
|
||||||
|
}
|
||||||
|
|
||||||
|
base := time.Now().Add(-time.Hour)
|
||||||
|
|
||||||
|
for i, hash := range hashes {
|
||||||
|
setSourceLastAccessed(t, cache, hash, base.Add(time.Duration(i)*time.Minute))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(t.Context())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cache.evictSourceBlobTestHook = func(ContentHash) { cancel() }
|
||||||
|
|
||||||
|
err := cache.EvictToLimit(ctx)
|
||||||
|
t.Logf("EvictToLimit() error = %v", err)
|
||||||
|
t.Logf("log output: %s", logBuf.String())
|
||||||
|
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
t.Errorf("EvictToLimit() error = %v, want context.Canceled", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cache.srcContent.Exists(hashes[0]) {
|
||||||
|
t.Errorf("source blob %s, evicted when the context was cancelled, "+
|
||||||
|
"is still on disk", hashes[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hash := range hashes[1:] {
|
||||||
|
if !cache.srcContent.Exists(hash) {
|
||||||
|
t.Errorf("source blob %s was evicted after the context was cancelled", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(logBuf.String(), `"level":"WARN"`) {
|
||||||
|
t.Errorf("EvictToLimit logged a warning after the context was cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNoDanglingReferences(t, cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStopEvictionReturnsWhenItsContextEnds pauses an eviction pass where
|
// TestStopEvictionReturnsWhenItsContextEnds pauses an eviction pass where
|
||||||
|
|||||||
Reference in New Issue
Block a user