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

@ -6,12 +6,14 @@ import (
)
type CachedNode struct {
cb func() *Element
isByKey bool
byKeyCache map[any]*Entry
mutex sync.Mutex
expiration time.Time
html string
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
}
type Entry struct {
@ -49,10 +51,10 @@ func CachedPerKey[K comparable](duration time.Duration, cb GetElementFuncWithKey
element := &Element{
tag: CachedNodeTag,
meta: &CachedNode{
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
isByKey: true,
cb: nil,
html: "",
duration: duration,
},
}
return func() *Element {
@ -78,10 +80,10 @@ func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFun
element := &Element{
tag: CachedNodeTag,
meta: &CachedNode{
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
isByKey: true,
cb: nil,
html: "",
duration: duration,
},
}
return func(data T) *Element {
@ -101,10 +103,10 @@ func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetE
element := &Element{
tag: CachedNodeTag,
meta: &CachedNode{
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
isByKey: true,
cb: nil,
html: "",
duration: duration,
},
}
return func(data T, data2 T2) *Element {
@ -124,10 +126,10 @@ func CachedPerKeyT3[K comparable, T any, T2 any, T3 any](duration time.Duration,
element := &Element{
tag: CachedNodeTag,
meta: &CachedNode{
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
isByKey: true,
cb: nil,
html: "",
duration: duration,
},
}
return func(data T, data2 T2, data3 T3) *Element {
@ -147,10 +149,10 @@ func CachedPerKeyT4[K comparable, T any, T2 any, T3 any, T4 any](duration time.D
element := &Element{
tag: CachedNodeTag,
meta: &CachedNode{
isByKey: true,
cb: nil,
html: "",
expiration: time.Now().Add(duration),
isByKey: true,
cb: nil,
html: "",
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,13 +285,21 @@ 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)
setAndWrite()
return
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 {
setAndWrite()

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{