aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@xeiaso.net>2023-03-01 19:06:24 -0500
committerXe Iaso <me@xeiaso.net>2023-03-01 19:06:24 -0500
commitadb5d6a858e40422a323a63e74aa3d3a310587d9 (patch)
tree7d365c202ba50119ec569f8dd5a8244575990313
parenta5821b5aa075994f0e38ead158686af9069b174d (diff)
downloadx-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.go85
-rw-r--r--cmd/xedn/main.go68
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() {