缩略图缓存加容量闸与删除联动清理
- Store 增 MaxBytes(默认 256MB):写入后节流扫描(10 分钟一次、同时只跑一个), 超限按 mtime 从旧到新删到 3/4 水位(滞回,避免卡在边界反复删),顺带清掉 进程被杀留下的 .tmp - 命中缓存时 Touch 更新 mtime,淘汰因此是 LRU 而非「最早生成的先死」, 热图不会被误删后反复回源拉存储 - 新增 Purge(sha):后台删除上传文件时连带清掉该图所有宽度的产物与 .failed 标记,否则已删图片会永久占着缓存
This commit is contained in:
@@ -6,7 +6,12 @@ import (
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func makePNG(t *testing.T, w, h int, transparent bool) []byte {
|
||||
@@ -92,3 +97,76 @@ func TestFlattenAlphaUnpremultiply(t *testing.T) {
|
||||
t.Fatalf("unpremultiply wrong: r=%d a=%d", flat[0], flat[3])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSweepEvictsOldest(t *testing.T) {
|
||||
s := NewStore(t.TempDir())
|
||||
s.MaxBytes = 2500 // 每个文件 1000B,留得下 2 个
|
||||
sha := func(i int) string { return strings.Repeat(string(rune('a'+i)), 64) }
|
||||
|
||||
// 三个宽度:写入顺序即 mtime 顺序(下面再显式拨时间)
|
||||
for i := 0; i < 3; i++ {
|
||||
if _, err := s.Put(sha(i), 480, bytes.Repeat([]byte("x"), 1000), "image/jpeg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// 跳过节流,让这次 Put 真的触发扫描
|
||||
s.lastSweep = time.Time{}
|
||||
oldest := time.Now().Add(-3 * time.Hour)
|
||||
for i := 0; i < 2; i++ { // 前两个假装很久没人访问
|
||||
os.Chtimes(s.jpegPath(sha(i), 480), oldest, oldest)
|
||||
}
|
||||
if _, err := s.Put(sha(3), 480, bytes.Repeat([]byte("x"), 1000), "image/jpeg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var total int64
|
||||
var kept []string
|
||||
filepath.WalkDir(s.Dir, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
fi, _ := d.Info()
|
||||
total += fi.Size()
|
||||
kept = append(kept, d.Name())
|
||||
return nil
|
||||
})
|
||||
if total > s.MaxBytes {
|
||||
t.Fatalf("sweep 后仍超限: total=%d max=%d files=%v", total, s.MaxBytes, kept)
|
||||
}
|
||||
if len(kept) >= 4 {
|
||||
t.Fatalf("一个都没淘汰,说明扫描没跑: files=%v", kept)
|
||||
}
|
||||
// 最新写入的那个必须还在
|
||||
if s.FindCached(sha(3), 480) == "" {
|
||||
t.Fatalf("刚写入的缓存被误删: %v", kept)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPurgeRemovesAllEntriesForFile(t *testing.T) {
|
||||
s := NewStore(t.TempDir())
|
||||
sha := strings.Repeat("f", 64)
|
||||
for _, w := range []int{480, 960} {
|
||||
if _, err := s.Put(sha, w, []byte("data"), "image/jpeg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
s.MarkFailed(sha)
|
||||
other := strings.Repeat("a", 64)
|
||||
if _, err := s.Put(other, 480, []byte("keep"), "image/jpeg"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s.Purge(sha)
|
||||
|
||||
for _, w := range []int{480, 960} {
|
||||
if p := s.FindCached(sha, w); p != "" {
|
||||
t.Fatalf("purge 后仍残留 %s", p)
|
||||
}
|
||||
}
|
||||
if s.FailedRecently(sha) {
|
||||
t.Fatal("失败标记没清掉")
|
||||
}
|
||||
if s.FindCached(other, 480) == "" {
|
||||
t.Fatal("误删了别的文件的缓存")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user