mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-03 12:00:51 +00:00
bea6fcaa84
The `gitea.com/go-chi/cache` package only exists for Gitea, so it moves into `modules/cache`. - `ITEM_TTL = -1` disables caching as documented - Sub-second TTLs round up instead of never expiring - The Redis adapter no longer grows a `MacaronCache` hash - Use two-queue cache for in-memory cache Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
127 lines
3.1 KiB
Go
127 lines
3.1 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cache
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gitea.dev/modules/json"
|
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
|
)
|
|
|
|
const twoQueueDefaultSize = 50000
|
|
|
|
type twoQueueCache struct {
|
|
cache *lru.TwoQueueCache[string, memoryItem] // wrap the existing thread-safe 2Q (two-queue) cache directly
|
|
}
|
|
|
|
type twoQueueCacheConfig struct {
|
|
Size int `json:"size"`
|
|
RecentRatio float64 `json:"recent_ratio"`
|
|
GhostRatio float64 `json:"ghost_ratio"`
|
|
}
|
|
|
|
func newTwoQueueCache(conn string, gcInterval time.Duration) (backend, error) {
|
|
lruCache, err := newTwoQueueLRU(conn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cache := &twoQueueCache{cache: lruCache}
|
|
startGC(gcInterval, cache.deleteExpired)
|
|
return cache, nil
|
|
}
|
|
|
|
func newTwoQueueLRU(conn string) (*lru.TwoQueueCache[string, memoryItem], error) {
|
|
if conn == "" {
|
|
return lru.New2Q[string, memoryItem](twoQueueDefaultSize)
|
|
}
|
|
if size, err := strconv.Atoi(conn); err == nil {
|
|
return lru.New2Q[string, memoryItem](size)
|
|
}
|
|
if !json.Valid([]byte(conn)) {
|
|
return nil, fmt.Errorf("invalid two-queue cache HOST %q, expected a size or a JSON config", conn)
|
|
}
|
|
config := twoQueueCacheConfig{
|
|
Size: twoQueueDefaultSize,
|
|
RecentRatio: lru.Default2QRecentRatio,
|
|
GhostRatio: lru.Default2QGhostEntries,
|
|
}
|
|
_ = json.Unmarshal([]byte(conn), &config)
|
|
return lru.New2QParams[string, memoryItem](config.Size, config.RecentRatio, config.GhostRatio)
|
|
}
|
|
|
|
func (c *twoQueueCache) Get(key string) (string, bool) {
|
|
item, ok := c.cache.Get(key)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
if item.expired(timeNow()) {
|
|
c.cache.Remove(key)
|
|
return "", false
|
|
}
|
|
return item.value, true
|
|
}
|
|
|
|
func (c *twoQueueCache) Put(key, value string, ttl int64) error {
|
|
item := newMemoryItem(value, ttl)
|
|
c.cache.Add(key, item)
|
|
return nil
|
|
}
|
|
|
|
func (c *twoQueueCache) Delete(key string) error {
|
|
c.cache.Remove(key)
|
|
return nil
|
|
}
|
|
|
|
func (c *twoQueueCache) IsExist(key string) bool {
|
|
item, ok := c.cache.Peek(key)
|
|
return ok && !item.expired(timeNow())
|
|
}
|
|
|
|
func (c *twoQueueCache) Ping() error {
|
|
return nil
|
|
}
|
|
|
|
func (c *twoQueueCache) deleteExpired() {
|
|
now := timeNow()
|
|
for _, key := range c.cache.Keys() {
|
|
if item, ok := c.cache.Peek(key); ok && item.expired(now) {
|
|
// here might be a slight data-race: the item might have been removed or updated by another goroutine between the Peek and Remove calls,
|
|
// but it's acceptable since the cache is not guaranteed to be 100% accurate and any item can be evicted at any time
|
|
c.cache.Remove(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
type memoryItem struct {
|
|
value string
|
|
expiresAt time.Time
|
|
}
|
|
|
|
func newMemoryItem(value string, ttl int64) memoryItem {
|
|
item := memoryItem{value: value}
|
|
if ttl > 0 {
|
|
item.expiresAt = timeNow().Add(time.Duration(ttl) * time.Second)
|
|
}
|
|
return item
|
|
}
|
|
|
|
func (item memoryItem) expired(now time.Time) bool {
|
|
return !item.expiresAt.IsZero() && !now.Before(item.expiresAt)
|
|
}
|
|
|
|
func startGC(interval time.Duration, deleteExpired func()) {
|
|
if interval <= 0 {
|
|
return
|
|
}
|
|
go func() {
|
|
for range time.Tick(interval) {
|
|
deleteExpired()
|
|
}
|
|
}()
|
|
}
|