make sure the expiration is per key too

This commit is contained in:
maddalax 2024-09-28 11:56:29 -05:00
parent 954bd1f3ca
commit 61e5554f20
2 changed files with 89 additions and 32 deletions

View file

@ -9,8 +9,10 @@ type CachedNode struct {
cb func() *Element
isByKey bool
byKeyCache map[any]*Entry
byKeyExpiration map[any]time.Time
mutex sync.Mutex
expiration time.Time
duration time.Duration
html string
}
@ -52,7 +54,7 @@ func CachedPerKey[K comparable](duration time.Duration, cb GetElementFuncWithKey
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
duration: duration,
},
}
return func() *Element {
@ -81,7 +83,7 @@ func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFun
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
duration: duration,
},
}
return func(data T) *Element {
@ -104,7 +106,7 @@ func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetE
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
duration: duration,
},
}
return func(data T, data2 T2) *Element {
@ -127,7 +129,7 @@ func CachedPerKeyT3[K comparable, T any, T2 any, T3 any](duration time.Duration,
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
duration: duration,
},
}
return func(data T, data2 T2, data3 T3) *Element {
@ -150,7 +152,7 @@ func CachedPerKeyT4[K comparable, T any, T2 any, T3 any, T4 any](duration time.D
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
duration: duration,
},
}
return func(data T, data2 T2, data3 T3, data4 T4) *Element {
@ -233,6 +235,11 @@ func CachedT4[T any, T2 any, T3 any, T4 any](duration time.Duration, cb GetEleme
func (c *CachedNode) ClearCache() {
c.html = ""
if c.byKeyCache != nil {
for key := range c.byKeyCache {
delete(c.byKeyCache, key)
}
}
}
func (c *CachedNode) Render(ctx *RenderContext) {
@ -258,11 +265,16 @@ func (c *ByKeyEntry) Render(ctx *RenderContext) {
key := c.key
parentMeta := c.parent.meta.(*CachedNode)
parentMeta.mutex.Lock()
defer parentMeta.mutex.Unlock()
if parentMeta.byKeyCache == nil {
parentMeta.byKeyCache = make(map[any]*Entry)
}
entry := parentMeta.byKeyCache[key]
if parentMeta.byKeyExpiration == nil {
parentMeta.byKeyExpiration = make(map[any]time.Time)
}
var setAndWrite = func() {
html := Render(c.cb())
@ -273,12 +285,20 @@ func (c *ByKeyEntry) Render(ctx *RenderContext) {
ctx.builder.WriteString(html)
}
// exists in cache but expired
if entry != nil && entry.expiration.Before(time.Now()) {
delete(parentMeta.byKeyCache, key)
expEntry, ok := parentMeta.byKeyExpiration[key]
if !ok {
parentMeta.byKeyExpiration[key] = time.Now().Add(parentMeta.duration)
} else {
// key is expired
if expEntry.Before(time.Now()) {
parentMeta.byKeyExpiration[key] = time.Now().Add(parentMeta.duration)
setAndWrite()
return
}
}
entry := parentMeta.byKeyCache[key]
// not in cache
if entry == nil {

View file

@ -367,6 +367,43 @@ func TestCacheByKeyT1Expired(t *testing.T) {
assert.Equal(t, 3, renderCount)
}
func TestCacheByKeyT1Expired_2(t *testing.T) {
t.Parallel()
renderCount := 0
cachedItem := CachedPerKeyT(time.Millisecond*5, func(key string) (any, GetElementFunc) {
return key, func() *Element {
renderCount++
return Pf(key)
}
})
assert.Equal(t, "<p >one</p>", Render(cachedItem("one")))
time.Sleep(time.Millisecond * 3)
assert.Equal(t, "<p >two</p>", Render(cachedItem("two")))
assert.Equal(t, "<p >two</p>", Render(cachedItem("two")))
assert.Equal(t, "<p >two</p>", Render(cachedItem("two")))
time.Sleep(time.Millisecond * 3)
assert.Equal(t, "<p >one</p>", Render(cachedItem("one")))
assert.Equal(t, "<p >two</p>", Render(cachedItem("two")))
assert.Equal(t, 3, renderCount)
}
func BenchmarkCacheByKey(b *testing.B) {
b.ReportAllocs()
page := CachedPerKeyT(time.Hour, func(userId string) (any, GetElementFunc) {
return userId, func() *Element {
return MailTo(userId)
}
})
for i := 0; i < 5000; i++ {
userId := uuid.NewString()
Render(page(userId))
}
Render(page(uuid.NewString()))
}
func BenchmarkMailToStatic(b *testing.B) {
b.ReportAllocs()
ctx := RenderContext{