The eviction loop runs the reconciliation pass on every periodic tick, so this test fails until the pass runs at startup and then once a day. Model: opus-5-5
This commit is contained in:
@@ -9,7 +9,9 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -918,6 +920,84 @@ func TestPeriodicReconciliationAdoptsFileThatAppearsAfterStartup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEvictionTicksDoNotReconcile starts the eviction loop with an
|
||||||
|
// interval of 10 ms. The startup pass reconciles before it removes the
|
||||||
|
// expired negative cache rows, so once the expired row stored before the
|
||||||
|
// start is gone, the startup reconciliation has made all its reads. The
|
||||||
|
// test then waits for three periodic ticks, each seen when it removes an
|
||||||
|
// expired row stored after the previous one went. The reconciliation
|
||||||
|
// pass runs at startup and then once a day, so none of those ticks may
|
||||||
|
// read a content table for reconciliation.
|
||||||
|
func TestEvictionTicksDoNotReconcile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cache, _ := newEvictionTestCache(t, 1<<30)
|
||||||
|
|
||||||
|
var reconciliationReads atomic.Int64
|
||||||
|
|
||||||
|
cache.reconciliationReadTestHook = func(int) { reconciliationReads.Add(1) }
|
||||||
|
|
||||||
|
insertExpiredNegativeCacheRow(t, cache, "/startup.jpg")
|
||||||
|
|
||||||
|
cache.StartEviction(10 * time.Millisecond)
|
||||||
|
defer func() { _ = cache.StopEviction(t.Context()) }()
|
||||||
|
|
||||||
|
waitForNegativeCacheRowRemoved(t, cache, "/startup.jpg")
|
||||||
|
|
||||||
|
startupReads := reconciliationReads.Load()
|
||||||
|
t.Logf("the startup pass made %d reconciliation reads", startupReads)
|
||||||
|
|
||||||
|
if startupReads == 0 {
|
||||||
|
t.Fatal("the startup pass made no reconciliation read")
|
||||||
|
}
|
||||||
|
|
||||||
|
for tick := range 3 {
|
||||||
|
path := "/tick-" + strconv.Itoa(tick) + ".jpg"
|
||||||
|
|
||||||
|
insertExpiredNegativeCacheRow(t, cache, path)
|
||||||
|
waitForNegativeCacheRowRemoved(t, cache, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
tickReads := reconciliationReads.Load() - startupReads
|
||||||
|
t.Logf("three eviction ticks made %d reconciliation reads", tickReads)
|
||||||
|
|
||||||
|
if tickReads != 0 {
|
||||||
|
t.Errorf("three eviction ticks made %d reconciliation reads, want 0: "+
|
||||||
|
"the reconciliation pass ran on an eviction tick", tickReads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertExpiredNegativeCacheRow adds a negative_cache row for sourcePath
|
||||||
|
// that expired an hour ago.
|
||||||
|
func insertExpiredNegativeCacheRow(t *testing.T, cache *Cache, sourcePath string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, err := cache.db.ExecContext(t.Context(), `
|
||||||
|
INSERT INTO negative_cache
|
||||||
|
(source_host, source_path, status_code, expires_at)
|
||||||
|
VALUES (?, ?, 404, datetime('now', '-1 hour'))
|
||||||
|
`, testHostExample, sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to insert an expired negative cache row: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForNegativeCacheRowRemoved waits up to five seconds for the
|
||||||
|
// eviction loop to remove the negative_cache row for sourcePath.
|
||||||
|
func waitForNegativeCacheRowRemoved(t *testing.T, cache *Cache, sourcePath string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
|
||||||
|
for negativeCacheRowCount(t, cache, sourcePath) > 0 {
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
t.Fatalf("the expired negative cache row for %s was not removed", sourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
Reference in New Issue
Block a user