mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
autoresearch iter 4: stop one disconnected client from failing a shared image flight
EnsureCompressedImageCache passed the arriving caller's request context into the singleflight body, which runs once for every concurrent requester of that cache key. If the first client disconnected, gin canceled the context, the shared generation aborted, and every follower received that failure and fell back to the uncompressed original. Detach cancellation with context.WithoutCancel so trace values still propagate but the shared work outlives any single requester.
This commit is contained in:
@@ -227,8 +227,11 @@ func EnsureCompressedImageCache(
|
||||
return nil, false, fmt.Errorf("read compressed image cache: %w", err)
|
||||
}
|
||||
|
||||
// The flight body serves every concurrent requester for this key, so it must
|
||||
// not die when whichever caller happened to arrive first disconnects.
|
||||
flightCtx := context.WithoutCancel(ctx)
|
||||
result, err, _ := compressedImageFlight.Do(cacheKey, func() (any, error) {
|
||||
return generateCompressedImageCache(ctx, upload, quality, cacheKey)
|
||||
return generateCompressedImageCache(flightCtx, upload, quality, cacheKey)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
@@ -55,7 +56,10 @@ func (s *localTestStorageService) Put(_ context.Context, key string, body io.Rea
|
||||
return contracts.StoragePutResult{Key: key, Bucket: "local"}, err
|
||||
}
|
||||
|
||||
func (s *localTestStorageService) Get(_ context.Context, key string) (*contracts.StorageObject, error) {
|
||||
func (s *localTestStorageService) Get(ctx context.Context, key string) (*contracts.StorageObject, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
path := filepath.Join(s.root, key)
|
||||
@@ -357,6 +361,50 @@ func TestServeFileByIDImageCompression(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// The singleflight body generates once on behalf of every concurrent requester
|
||||
// sharing a cache key, so the caller that happens to arrive first must not be
|
||||
// able to fail the others by disconnecting.
|
||||
func TestEnsureCompressedImageCacheSurvivesCallerCancellation(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
shared.SetStorageService(&localTestStorageService{root: tempDir})
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
||||
img.Set(0, 0, color.RGBA{R: 0, G: 255, B: 0, A: 255})
|
||||
var pngBuf bytes.Buffer
|
||||
if err := png.Encode(&pngBuf, img); err != nil {
|
||||
t.Fatalf("failed to encode test png: %v", err)
|
||||
}
|
||||
const filePath = "cancel_probe.png"
|
||||
if err := os.WriteFile(filepath.Join(tempDir, filePath), pngBuf.Bytes(), 0o600); err != nil {
|
||||
t.Fatalf("failed to write test png: %v", err)
|
||||
}
|
||||
|
||||
upload := &models.Upload{
|
||||
ID: 990001,
|
||||
FilePath: filePath,
|
||||
FileSize: int64(pngBuf.Len()),
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
// Unique per run so the persistent disk cache can never serve this key.
|
||||
Hash: fmt.Sprintf("cancel-probe-%d", time.Now().UnixNano()),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
webpBytes, cached, err := EnsureCompressedImageCache(ctx, upload, "medium")
|
||||
if err != nil {
|
||||
t.Fatalf("compressed image generation failed with a canceled caller context: %v", err)
|
||||
}
|
||||
if cached {
|
||||
t.Errorf("expected a freshly generated image, got a cache hit")
|
||||
}
|
||||
if len(webpBytes) == 0 {
|
||||
t.Errorf("expected non-empty webp bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeImageQuality(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user