perf: cache generated image thumbnails

This commit is contained in:
OpenClaw Agent
2026-09-22 05:51:06 +08:00
parent f799104fb2
commit e6806de5f6
4 changed files with 225 additions and 27 deletions
+72
View File
@@ -0,0 +1,72 @@
package api
import (
"errors"
"sync"
"sync/atomic"
"testing"
)
func TestThumbnailCacheDeduplicatesConcurrentBuilds(t *testing.T) {
c := newThumbnailCache()
key := thumbnailCacheKey{Path: "photo.jpg", Size: 10, ModTime: 20}
var builds atomic.Int32
const workers = 24
var wg sync.WaitGroup
results := make([][]byte, workers)
errs := make([]error, workers)
wg.Add(workers)
for i := 0; i < workers; i++ {
go func(i int) {
defer wg.Done()
results[i], errs[i] = c.Do(key, func() ([]byte, error) {
builds.Add(1)
return []byte("thumbnail"), nil
})
}(i)
}
wg.Wait()
if builds.Load() != 1 {
t.Fatalf("thumbnail builder ran %d times, want 1", builds.Load())
}
for i := range results {
if errs[i] != nil || string(results[i]) != "thumbnail" {
t.Fatalf("worker %d got result %q, err=%v", i, results[i], errs[i])
}
}
}
func TestThumbnailCacheDoesNotCacheErrors(t *testing.T) {
c := newThumbnailCache()
key := thumbnailCacheKey{Path: "broken.jpg"}
wantErr := errors.New("decode failed")
var builds atomic.Int32
for i := 0; i < 2; i++ {
_, err := c.Do(key, func() ([]byte, error) {
builds.Add(1)
return nil, wantErr
})
if !errors.Is(err, wantErr) {
t.Fatalf("got err %v, want %v", err, wantErr)
}
}
if builds.Load() != 2 {
t.Fatalf("error result was cached; builder ran %d times", builds.Load())
}
}
func TestThumbnailCacheEvictsByBound(t *testing.T) {
c := newThumbnailCache()
c.maxItems = 2
for i := 0; i < 3; i++ {
key := thumbnailCacheKey{Path: string(rune('a' + i))}
if _, err := c.Do(key, func() ([]byte, error) { return []byte("x"), nil }); err != nil {
t.Fatal(err)
}
}
c.mu.Lock()
defer c.mu.Unlock()
if len(c.items) != 2 || c.order.Len() != 2 {
t.Fatalf("cache size=%d/list=%d, want 2/2", len(c.items), c.order.Len())
}
}