2021-07-11 00:54:15 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-07-11 00:54:15 +03:00
package cache
import (
"strconv"
"sync"
"time"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2024-04-13 11:38:44 +03:00
mc "gitea.com/go-chi/cache" //nolint:depguard
2023-07-14 06:00:31 +03:00
lru "github.com/hashicorp/golang-lru/v2"
2021-07-11 00:54:15 +03:00
)
// TwoQueueCache represents a LRU 2Q cache adapter implementation
type TwoQueueCache struct {
lock sync . Mutex
2023-07-14 06:00:31 +03:00
cache * lru . TwoQueueCache [ string , any ]
2021-07-11 00:54:15 +03:00
interval int
}
// TwoQueueCacheConfig describes the configuration for TwoQueueCache
type TwoQueueCacheConfig struct {
Size int ` ini:"SIZE" json:"size" `
RecentRatio float64 ` ini:"RECENT_RATIO" json:"recent_ratio" `
GhostRatio float64 ` ini:"GHOST_RATIO" json:"ghost_ratio" `
}
// MemoryItem represents a memory cache item.
type MemoryItem struct {
2023-07-04 21:36:08 +03:00
Val any
2021-07-11 00:54:15 +03:00
Created int64
Timeout int64
}
func ( item * MemoryItem ) hasExpired ( ) bool {
return item . Timeout > 0 &&
( time . Now ( ) . Unix ( ) - item . Created ) >= item . Timeout
}
var _ mc . Cache = & TwoQueueCache { }
// Put puts value into cache with key and expire time.
2023-07-04 21:36:08 +03:00
func ( c * TwoQueueCache ) Put ( key string , val any , timeout int64 ) error {
2021-07-11 00:54:15 +03:00
item := & MemoryItem {
Val : val ,
Created : time . Now ( ) . Unix ( ) ,
Timeout : timeout ,
}
c . lock . Lock ( )
defer c . lock . Unlock ( )
c . cache . Add ( key , item )
return nil
}
// Get gets cached value by given key.
2023-07-04 21:36:08 +03:00
func ( c * TwoQueueCache ) Get ( key string ) any {
2021-07-11 00:54:15 +03:00
c . lock . Lock ( )
defer c . lock . Unlock ( )
cached , ok := c . cache . Get ( key )
if ! ok {
return nil
}
item , ok := cached . ( * MemoryItem )
if ! ok || item . hasExpired ( ) {
c . cache . Remove ( key )
return nil
}
return item . Val
}
// Delete deletes cached value by given key.
func ( c * TwoQueueCache ) Delete ( key string ) error {
c . lock . Lock ( )
defer c . lock . Unlock ( )
c . cache . Remove ( key )
return nil
}
// Incr increases cached int-type value by given key as a counter.
func ( c * TwoQueueCache ) Incr ( key string ) error {
c . lock . Lock ( )
defer c . lock . Unlock ( )
cached , ok := c . cache . Get ( key )
if ! ok {
return nil
}
item , ok := cached . ( * MemoryItem )
if ! ok || item . hasExpired ( ) {
c . cache . Remove ( key )
return nil
}
var err error
item . Val , err = mc . Incr ( item . Val )
return err
}
// Decr decreases cached int-type value by given key as a counter.
func ( c * TwoQueueCache ) Decr ( key string ) error {
c . lock . Lock ( )
defer c . lock . Unlock ( )
cached , ok := c . cache . Get ( key )
if ! ok {
return nil
}
item , ok := cached . ( * MemoryItem )
if ! ok || item . hasExpired ( ) {
c . cache . Remove ( key )
return nil
}
var err error
item . Val , err = mc . Decr ( item . Val )
return err
}
// IsExist returns true if cached value exists.
func ( c * TwoQueueCache ) IsExist ( key string ) bool {
c . lock . Lock ( )
defer c . lock . Unlock ( )
cached , ok := c . cache . Peek ( key )
if ! ok {
return false
}
item , ok := cached . ( * MemoryItem )
if ! ok || item . hasExpired ( ) {
c . cache . Remove ( key )
return false
}
return true
}
// Flush deletes all cached data.
func ( c * TwoQueueCache ) Flush ( ) error {
c . lock . Lock ( )
defer c . lock . Unlock ( )
c . cache . Purge ( )
return nil
}
2023-07-14 06:00:31 +03:00
func ( c * TwoQueueCache ) checkAndInvalidate ( key string ) {
2021-07-11 00:54:15 +03:00
c . lock . Lock ( )
defer c . lock . Unlock ( )
cached , ok := c . cache . Peek ( key )
if ! ok {
return
}
item , ok := cached . ( * MemoryItem )
if ! ok || item . hasExpired ( ) {
2023-07-14 06:00:31 +03:00
c . cache . Remove ( key )
2021-07-11 00:54:15 +03:00
}
}
func ( c * TwoQueueCache ) startGC ( ) {
if c . interval < 0 {
return
}
for _ , key := range c . cache . Keys ( ) {
c . checkAndInvalidate ( key )
}
time . AfterFunc ( time . Duration ( c . interval ) * time . Second , c . startGC )
}
// StartAndGC starts GC routine based on config string settings.
func ( c * TwoQueueCache ) StartAndGC ( opts mc . Options ) error {
var err error
size := 50000
if opts . AdapterConfig != "" {
size , err = strconv . Atoi ( opts . AdapterConfig )
}
if err != nil {
if ! json . Valid ( [ ] byte ( opts . AdapterConfig ) ) {
return err
}
cfg := & TwoQueueCacheConfig {
Size : 50000 ,
RecentRatio : lru . Default2QRecentRatio ,
GhostRatio : lru . Default2QGhostEntries ,
}
_ = json . Unmarshal ( [ ] byte ( opts . AdapterConfig ) , cfg )
2023-07-14 06:00:31 +03:00
c . cache , err = lru . New2QParams [ string , any ] ( cfg . Size , cfg . RecentRatio , cfg . GhostRatio )
2021-07-11 00:54:15 +03:00
} else {
2023-07-14 06:00:31 +03:00
c . cache , err = lru . New2Q [ string , any ] ( size )
2021-07-11 00:54:15 +03:00
}
c . interval = opts . Interval
if c . interval > 0 {
go c . startGC ( )
}
return err
}
2022-05-15 21:43:27 +03:00
// Ping tests if the cache is alive.
func ( c * TwoQueueCache ) Ping ( ) error {
return mc . GenericPing ( c )
}
2021-07-11 00:54:15 +03:00
func init ( ) {
mc . Register ( "twoqueue" , & TwoQueueCache { } )
}