diff --git a/front/cache.go b/front/cache.go index 8a3a38a3..2159724f 100644 --- a/front/cache.go +++ b/front/cache.go @@ -21,8 +21,8 @@ import ( "context" "github.com/dimkr/tootik/cfg" "github.com/dimkr/tootik/front/text" + "golang.org/x/sync/semaphore" "slices" - "sync" "time" ) @@ -40,7 +40,7 @@ func (w chanWriter) Write(p []byte) (int, error) { return len(p), nil } -func callAndCache(r *Request, w text.Writer, args []string, f func(text.Writer, *Request, ...string), key string, now time.Time, cache *sync.Map) { +func callAndCache(r *Request, w text.Writer, args []string, f func(text.Writer, *Request, ...string), key string, now time.Time, cache *cacheEntry) { c := make(chan []byte) ctx, cancel := context.WithCancel(context.Background()) @@ -91,28 +91,41 @@ func callAndCache(r *Request, w text.Writer, args []string, f func(text.Writer, w2.Textf("(Cached response generated on %s)", now.Format(time.UnixDate)) w2.Flush() - cache.Store(key, cacheEntry{buf.Bytes(), now}) + cache.Value = buf.Bytes() + cache.Created = now } -func withCache(f func(text.Writer, *Request, ...string), d time.Duration, cache *sync.Map, cfg *cfg.Config) func(text.Writer, *Request, ...string) { +func withCache(f func(text.Writer, *Request, ...string), d time.Duration, cfg *cfg.Config) func(text.Writer, *Request, ...string) { + cache := &cacheEntry{} + lock := semaphore.NewWeighted(1) + return func(w text.Writer, r *Request, args ...string) { key := r.URL.String() now := time.Now() - entry, cached := cache.Load(key) - if !cached { + if err := lock.Acquire(r.Context, 1); err != nil { + r.Log.Warn("Failed to acquire cache lock", "key", key) + w.Error() + return + } + + if cache.Value == nil { r.Log.Info("Generating first response", "key", key) callAndCache(r, w, args, f, key, now, cache) + lock.Release(1) return } - if entry.(cacheEntry).Created.After(now.Add(-d)) { + if cache.Created.After(now.Add(-d)) { + value := cache.Value + lock.Release(1) r.Log.Info("Sending cached response", "key", key) - w.Write(entry.(cacheEntry).Value) + w.Write(value) return } r.Log.Info("Generating new response", "key", key) callAndCache(r, w, args, f, key, now, cache) + lock.Release(1) } } diff --git a/front/handler.go b/front/handler.go index 44722176..8fa10457 100644 --- a/front/handler.go +++ b/front/handler.go @@ -25,7 +25,6 @@ import ( "github.com/dimkr/tootik/front/static" "github.com/dimkr/tootik/front/text" "regexp" - "sync" "time" ) @@ -57,7 +56,6 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve Resolver: resolver, DB: db, } - var cache sync.Map h.handlers[regexp.MustCompile(`^/$`)] = withUserMenu(h.home) @@ -72,8 +70,8 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve h.handlers[regexp.MustCompile(`^/users/mentions$`)] = withUserMenu(h.mentions) - h.handlers[regexp.MustCompile(`^/local$`)] = withCache(withUserMenu(h.local), time.Minute*15, &cache, cfg) - h.handlers[regexp.MustCompile(`^/users/local$`)] = withCache(withUserMenu(h.local), time.Minute*15, &cache, cfg) + h.handlers[regexp.MustCompile(`^/local$`)] = withCache(withUserMenu(h.local), time.Minute*15, cfg) + h.handlers[regexp.MustCompile(`^/users/local$`)] = withCache(withUserMenu(h.local), time.Minute*15, cfg) h.handlers[regexp.MustCompile(`^/outbox/(\S+)$`)] = withUserMenu(h.userOutbox) h.handlers[regexp.MustCompile(`^/users/outbox/(\S+)$`)] = withUserMenu(h.userOutbox) @@ -120,11 +118,11 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve h.handlers[regexp.MustCompile(`^/communities$`)] = withUserMenu(h.communities) h.handlers[regexp.MustCompile(`^/users/communities$`)] = withUserMenu(h.communities) - h.handlers[regexp.MustCompile(`^/hashtag/([a-zA-Z0-9]+)$`)] = withCache(withUserMenu(h.hashtag), time.Minute*5, &cache, cfg) - h.handlers[regexp.MustCompile(`^/users/hashtag/([a-zA-Z0-9]+)$`)] = withCache(withUserMenu(h.hashtag), time.Minute*5, &cache, cfg) + h.handlers[regexp.MustCompile(`^/hashtag/([a-zA-Z0-9]+)$`)] = withCache(withUserMenu(h.hashtag), time.Minute*5, cfg) + h.handlers[regexp.MustCompile(`^/users/hashtag/([a-zA-Z0-9]+)$`)] = withCache(withUserMenu(h.hashtag), time.Minute*5, cfg) - h.handlers[regexp.MustCompile(`^/hashtags$`)] = withCache(withUserMenu(h.hashtags), time.Minute*30, &cache, cfg) - h.handlers[regexp.MustCompile(`^/users/hashtags$`)] = withCache(withUserMenu(h.hashtags), time.Minute*30, &cache, cfg) + h.handlers[regexp.MustCompile(`^/hashtags$`)] = withCache(withUserMenu(h.hashtags), time.Minute*30, cfg) + h.handlers[regexp.MustCompile(`^/users/hashtags$`)] = withCache(withUserMenu(h.hashtags), time.Minute*30, cfg) h.handlers[regexp.MustCompile(`^/search$`)] = withUserMenu(search) h.handlers[regexp.MustCompile(`^/users/search$`)] = withUserMenu(search) @@ -132,8 +130,8 @@ func NewHandler(domain string, closed bool, cfg *cfg.Config, resolver ap.Resolve h.handlers[regexp.MustCompile(`^/fts$`)] = withUserMenu(h.fts) h.handlers[regexp.MustCompile(`^/users/fts$`)] = withUserMenu(h.fts) - h.handlers[regexp.MustCompile(`^/status$`)] = withCache(withUserMenu(h.status), time.Minute*5, &cache, cfg) - h.handlers[regexp.MustCompile(`^/users/status$`)] = withCache(withUserMenu(h.status), time.Minute*5, &cache, cfg) + h.handlers[regexp.MustCompile(`^/status$`)] = withCache(withUserMenu(h.status), time.Minute*5, cfg) + h.handlers[regexp.MustCompile(`^/users/status$`)] = withCache(withUserMenu(h.status), time.Minute*5, cfg) h.handlers[regexp.MustCompile(`^/oops`)] = withUserMenu(oops) h.handlers[regexp.MustCompile(`^/users/oops`)] = withUserMenu(oops)