diff options
| author | Xe Iaso <me@xeiaso.net> | 2023-03-01 19:06:24 -0500 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2023-03-01 19:06:24 -0500 |
| commit | adb5d6a858e40422a323a63e74aa3d3a310587d9 (patch) | |
| tree | 7d365c202ba50119ec569f8dd5a8244575990313 | |
| parent | a5821b5aa075994f0e38ead158686af9069b174d (diff) | |
| download | x-adb5d6a858e40422a323a63e74aa3d3a310587d9.tar.xz x-adb5d6a858e40422a323a63e74aa3d3a310587d9.zip | |
use singleflight.Group to prevent double fetches
Signed-off-by: Xe Iaso <me@xeiaso.net>
| -rw-r--r-- | cmd/xedn/imgoptimize.go | 85 | ||||
| -rw-r--r-- | cmd/xedn/main.go | 68 |
2 files changed, 89 insertions, 64 deletions
diff --git a/cmd/xedn/imgoptimize.go b/cmd/xedn/imgoptimize.go index ed2d465..1b1347b 100644 --- a/cmd/xedn/imgoptimize.go +++ b/cmd/xedn/imgoptimize.go @@ -17,6 +17,7 @@ import ( "github.com/chai2010/webp" "github.com/disintegration/imaging" "go.etcd.io/bbolt" + "golang.org/x/sync/singleflight" "tailscale.com/metrics" "within.website/ln" "within.website/x/internal/avif" @@ -26,6 +27,7 @@ type OptimizedImageServer struct { DB *bbolt.DB Cache *Cache PNGEnc *png.Encoder + group *singleflight.Group } var ( @@ -113,58 +115,65 @@ func (ois *OptimizedImageServer) ResizeTo(widthPixels int, character, mood, form return result.Bytes(), nil } - // /file/christine-static/stickers/aoi/yawn.png - path := fmt.Sprintf("/file/christine-static/stickers/%s/%s.png", character, mood) - data, err := ois.Cache.LoadBytesOrFetch(path) - if err != nil { - return nil, fmt.Errorf("can't fetch: %w", err) - } + data, err, _ := ois.group.Do(string(boltPath), func() (interface{}, error) { + // /file/christine-static/stickers/aoi/yawn.png + path := fmt.Sprintf("/file/christine-static/stickers/%s/%s.png", character, mood) + data, err := ois.Cache.LoadBytesOrFetch(path) + if err != nil { + return nil, fmt.Errorf("can't fetch: %w", err) + } - os.WriteFile("foo.png", data, 0666) + os.WriteFile("foo.png", data, 0666) - img, _, err := image.Decode(bytes.NewBuffer(data)) - if err != nil { - return nil, fmt.Errorf("can't decode image: %w", err) - } + img, _, err := image.Decode(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("can't decode image: %w", err) + } - dstImg := imaging.Resize(img, widthPixels, 0, imaging.Lanczos) + dstImg := imaging.Resize(img, widthPixels, 0, imaging.Lanczos) - switch format { - case "png": - if err := ois.PNGEnc.Encode(&result, dstImg); err != nil { - return nil, err - } - case "webp": - if err := webp.Encode(&result, dstImg, &webp.Options{Quality: 70}); err != nil { - return nil, err - } - case "avif": - if err := avif.Encode(&result, dstImg, &avif.Options{Quality: 48, Speed: avif.MaxSpeed}); err != nil { - return nil, err + switch format { + case "png": + if err := ois.PNGEnc.Encode(&result, dstImg); err != nil { + return nil, err + } + case "webp": + if err := webp.Encode(&result, dstImg, &webp.Options{Quality: 70}); err != nil { + return nil, err + } + case "avif": + if err := avif.Encode(&result, dstImg, &avif.Options{Quality: 48, Speed: avif.MaxSpeed}); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("I don't know how to render to %s yet, sorry", format) } - default: - return nil, fmt.Errorf("I don't know how to render to %s yet, sorry", format) - } - OISFileConversions.Add(format, 1) + OISFileConversions.Add(format, 1) - err = ois.DB.Update(func(tx *bbolt.Tx) error { - bkt, err := tx.CreateBucketIfNotExists([]byte("sticker_cache")) - if err != nil { - return err - } + err = ois.DB.Update(func(tx *bbolt.Tx) error { + bkt, err := tx.CreateBucketIfNotExists([]byte("sticker_cache")) + if err != nil { + return err + } - if err := bkt.Put(boltPath, result.Bytes()); err != nil { - return err + if err := bkt.Put(boltPath, result.Bytes()); err != nil { + return err + } + + return nil + }) + if err != nil { + return nil, fmt.Errorf("can't write to database: %w", err) } - return nil + return result.Bytes(), nil }) if err != nil { - return nil, fmt.Errorf("can't write to database: %w", err) + return nil, err } - return result.Bytes(), nil + return data.([]byte), nil } func (ois *OptimizedImageServer) ListFiles(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/xedn/main.go b/cmd/xedn/main.go index 7044060..7591610 100644 --- a/cmd/xedn/main.go +++ b/cmd/xedn/main.go @@ -24,6 +24,7 @@ import ( "github.com/rs/cors" "github.com/sebest/xff" "go.etcd.io/bbolt" + "golang.org/x/sync/singleflight" "tailscale.com/metrics" "tailscale.com/tsnet" "tailscale.com/tsweb" @@ -47,6 +48,7 @@ type Cache struct { ActualHost string Client *http.Client DB *bbolt.DB + cacheGroup *singleflight.Group } func Hash(data string) string { @@ -211,20 +213,27 @@ func (dc *Cache) LoadBytesOrFetch(path string) ([]byte, error) { err := dc.Load(path, buf) if err != nil { if err == ErrNotCached { - resp, err := dc.Client.Get(fmt.Sprintf("https://%s%s", dc.ActualHost, path)) - if err != nil { - cacheErrors.Add(1) - return nil, err - } + _, err, _ := dc.cacheGroup.Do(path, func() (interface{}, error) { + resp, err := dc.Client.Get(fmt.Sprintf("https://%s%s", dc.ActualHost, path)) + if err != nil { + cacheErrors.Add(1) + return nil, err + } - if resp.StatusCode != http.StatusOK { - cacheErrors.Add(1) - return nil, web.NewError(http.StatusOK, resp) - } + if resp.StatusCode != http.StatusOK { + cacheErrors.Add(1) + return nil, web.NewError(http.StatusOK, resp) + } + + err = dc.Save(path, resp) + if err != nil { + cacheErrors.Add(1) + return nil, err + } - err = dc.Save(path, resp) + return nil, nil + }) if err != nil { - cacheErrors.Add(1) return nil, err } @@ -241,26 +250,31 @@ func (dc *Cache) GetFile(w http.ResponseWriter, r *http.Request) error { err := dc.Load(dir, w) if err != nil { if err == ErrNotCached { - r.URL.Host = dc.ActualHost - r.URL.Scheme = "https" - resp, err := dc.Client.Get(r.URL.String()) - if err != nil { - cacheErrors.Add(1) - return err - } + _, err, _ := dc.cacheGroup.Do(r.URL.Path, func() (interface{}, error) { + r.URL.Host = dc.ActualHost + r.URL.Scheme = "https" + resp, err := dc.Client.Get(r.URL.String()) + if err != nil { + cacheErrors.Add(1) + return nil, err + } - if resp.StatusCode != http.StatusOK { - cacheErrors.Add(1) - return web.NewError(http.StatusOK, resp) - } + if resp.StatusCode != http.StatusOK { + cacheErrors.Add(1) + return nil, web.NewError(http.StatusOK, resp) + } - err = dc.Save(dir, resp) + err = dc.Save(dir, resp) + if err != nil { + cacheErrors.Add(1) + return nil, err + } + cacheLoads.Add(1) + return nil, nil + }) if err != nil { - cacheErrors.Add(1) return err } - - cacheLoads.Add(1) } else { cacheErrors.Add(1) return err @@ -350,6 +364,7 @@ func main() { ActualHost: *b2Backend, Client: &http.Client{}, DB: db, + cacheGroup: &singleflight.Group{}, } go dc.CronPurgeDead() @@ -358,6 +373,7 @@ func main() { DB: db, Cache: dc, PNGEnc: &png.Encoder{CompressionLevel: png.BestCompression}, + group: &singleflight.Group{}, } go func() { |
