gitea/modules/cache/ephemeral.go
KN4CK3R a2651c14ce Add cache for common package queries (#22491)
This adds a cache for common package queries in `GetPackageDescriptor`.
Code which needs to process a list of packages benefits from this
change. This skips 350 queries in the package integration tests for
example.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2025-04-13 09:40:36 +00:00

91 lines
2.1 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cache
import (
"context"
"sync"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
// EphemeralCache is a cache that can be used to store data in a request level context
// This is useful for caching data that is expensive to calculate and is likely to be
// used multiple times in a request.
type EphemeralCache struct {
data map[any]map[any]any
lock sync.RWMutex
created time.Time
checkLifeTime time.Duration
}
var timeNow = time.Now
func NewEphemeralCache(checkLifeTime ...time.Duration) *EphemeralCache {
return &EphemeralCache{
data: make(map[any]map[any]any),
created: timeNow(),
checkLifeTime: util.OptionalArg(checkLifeTime, 0),
}
}
func (cc *EphemeralCache) checkExceededLifeTime(tp, key any) bool {
if cc.checkLifeTime > 0 && timeNow().Sub(cc.created) > cc.checkLifeTime {
log.Warn("EphemeralCache is expired, is highly likely to be abused for long-life tasks: %v, %v", tp, key)
return true
}
return false
}
func (cc *EphemeralCache) Get(tp, key any) (any, bool) {
if cc.checkExceededLifeTime(tp, key) {
return nil, false
}
cc.lock.RLock()
defer cc.lock.RUnlock()
ret, ok := cc.data[tp][key]
return ret, ok
}
func (cc *EphemeralCache) Put(tp, key, value any) {
if cc.checkExceededLifeTime(tp, key) {
return
}
cc.lock.Lock()
defer cc.lock.Unlock()
d := cc.data[tp]
if d == nil {
d = make(map[any]any)
cc.data[tp] = d
}
d[key] = value
}
func (cc *EphemeralCache) Delete(tp, key any) {
if cc.checkExceededLifeTime(tp, key) {
return
}
cc.lock.Lock()
defer cc.lock.Unlock()
delete(cc.data[tp], key)
}
func GetWithEphemeralCache[T, K any](ctx context.Context, c *EphemeralCache, groupKey string, targetKey K, f func(context.Context, K) (T, error)) (T, error) {
v, has := c.Get(groupKey, targetKey)
if vv, ok := v.(T); has && ok {
return vv, nil
}
t, err := f(ctx, targetKey)
if err != nil {
return t, err
}
c.Put(groupKey, targetKey, t)
return t, nil
}