Skip to content

Commit

Permalink
lock cache during update
Browse files Browse the repository at this point in the history
  • Loading branch information
dimkr committed Oct 14, 2024
1 parent cffff5f commit 1a6f9b8
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 18 deletions.
29 changes: 21 additions & 8 deletions front/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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())
Expand Down Expand Up @@ -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)
}
}
18 changes: 8 additions & 10 deletions front/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"github.com/dimkr/tootik/front/static"
"github.com/dimkr/tootik/front/text"
"regexp"
"sync"
"time"
)

Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -120,20 +118,20 @@ 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)

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)
Expand Down

0 comments on commit 1a6f9b8

Please sign in to comment.