make sure the expiration is per key too
This commit is contained in:
parent
954bd1f3ca
commit
61e5554f20
2 changed files with 89 additions and 32 deletions
|
|
@ -9,8 +9,10 @@ type CachedNode struct {
|
||||||
cb func() *Element
|
cb func() *Element
|
||||||
isByKey bool
|
isByKey bool
|
||||||
byKeyCache map[any]*Entry
|
byKeyCache map[any]*Entry
|
||||||
|
byKeyExpiration map[any]time.Time
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
expiration time.Time
|
expiration time.Time
|
||||||
|
duration time.Duration
|
||||||
html string
|
html string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +54,7 @@ func CachedPerKey[K comparable](duration time.Duration, cb GetElementFuncWithKey
|
||||||
isByKey: true,
|
isByKey: true,
|
||||||
cb: nil,
|
cb: nil,
|
||||||
html: "",
|
html: "",
|
||||||
expiration: time.Now().Add(duration),
|
duration: duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return func() *Element {
|
return func() *Element {
|
||||||
|
|
@ -81,7 +83,7 @@ func CachedPerKeyT[K comparable, T any](duration time.Duration, cb GetElementFun
|
||||||
isByKey: true,
|
isByKey: true,
|
||||||
cb: nil,
|
cb: nil,
|
||||||
html: "",
|
html: "",
|
||||||
expiration: time.Now().Add(duration),
|
duration: duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return func(data T) *Element {
|
return func(data T) *Element {
|
||||||
|
|
@ -104,7 +106,7 @@ func CachedPerKeyT2[K comparable, T any, T2 any](duration time.Duration, cb GetE
|
||||||
isByKey: true,
|
isByKey: true,
|
||||||
cb: nil,
|
cb: nil,
|
||||||
html: "",
|
html: "",
|
||||||
expiration: time.Now().Add(duration),
|
duration: duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return func(data T, data2 T2) *Element {
|
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,
|
isByKey: true,
|
||||||
cb: nil,
|
cb: nil,
|
||||||
html: "",
|
html: "",
|
||||||
expiration: time.Now().Add(duration),
|
duration: duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return func(data T, data2 T2, data3 T3) *Element {
|
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,
|
isByKey: true,
|
||||||
cb: nil,
|
cb: nil,
|
||||||
html: "",
|
html: "",
|
||||||
expiration: time.Now().Add(duration),
|
duration: duration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return func(data T, data2 T2, data3 T3, data4 T4) *Element {
|
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() {
|
func (c *CachedNode) ClearCache() {
|
||||||
c.html = ""
|
c.html = ""
|
||||||
|
if c.byKeyCache != nil {
|
||||||
|
for key := range c.byKeyCache {
|
||||||
|
delete(c.byKeyCache, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CachedNode) Render(ctx *RenderContext) {
|
func (c *CachedNode) Render(ctx *RenderContext) {
|
||||||
|
|
@ -258,11 +265,16 @@ func (c *ByKeyEntry) Render(ctx *RenderContext) {
|
||||||
key := c.key
|
key := c.key
|
||||||
parentMeta := c.parent.meta.(*CachedNode)
|
parentMeta := c.parent.meta.(*CachedNode)
|
||||||
|
|
||||||
|
parentMeta.mutex.Lock()
|
||||||
|
defer parentMeta.mutex.Unlock()
|
||||||
|
|
||||||
if parentMeta.byKeyCache == nil {
|
if parentMeta.byKeyCache == nil {
|
||||||
parentMeta.byKeyCache = make(map[any]*Entry)
|
parentMeta.byKeyCache = make(map[any]*Entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := parentMeta.byKeyCache[key]
|
if parentMeta.byKeyExpiration == nil {
|
||||||
|
parentMeta.byKeyExpiration = make(map[any]time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
var setAndWrite = func() {
|
var setAndWrite = func() {
|
||||||
html := Render(c.cb())
|
html := Render(c.cb())
|
||||||
|
|
@ -273,12 +285,20 @@ func (c *ByKeyEntry) Render(ctx *RenderContext) {
|
||||||
ctx.builder.WriteString(html)
|
ctx.builder.WriteString(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exists in cache but expired
|
expEntry, ok := parentMeta.byKeyExpiration[key]
|
||||||
if entry != nil && entry.expiration.Before(time.Now()) {
|
|
||||||
delete(parentMeta.byKeyCache, 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()
|
setAndWrite()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := parentMeta.byKeyCache[key]
|
||||||
|
|
||||||
// not in cache
|
// not in cache
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,43 @@ func TestCacheByKeyT1Expired(t *testing.T) {
|
||||||
assert.Equal(t, 3, renderCount)
|
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) {
|
func BenchmarkMailToStatic(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
ctx := RenderContext{
|
ctx := RenderContext{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue