Albirew/nyaa-pantsu
Archivé
1
0
Bifurcation 0
Ce dépôt a été archivé le 2022-05-07. Vous pouvez voir ses fichiers ou le cloner, mais pas ouvrir de ticket ou de demandes d'ajout, ni soumettre de changements.
nyaa-pantsu/cache/cache.go

130 lignes
2,7 Kio
Go
Brut Vue normale Historique

2017-05-10 10:27:17 +02:00
package cache
import (
"container/list"
"sync"
"time"
2017-05-10 11:03:49 +02:00
"github.com/ewhal/nyaa/common"
2017-05-10 10:27:17 +02:00
"github.com/ewhal/nyaa/model"
)
var (
2017-05-10 11:03:49 +02:00
cache = make(map[common.SearchParam]*list.Element, 10)
2017-05-10 10:27:17 +02:00
ll = list.New()
totalUsed int
mu sync.Mutex
// Mutable for quicker testing
expiryTime = time.Second * 60
// Size sets the maximum size of the cache before evicting unread data in MB
Size float64 = 1 << 10
)
// Key stores the ID of either a thread or board page
type Key struct {
LastN uint8
Board string
ID uint64
}
// Single cache entry
type store struct {
2017-05-10 11:03:49 +02:00
sync.Mutex // Controls general access to the contents of the struct
2017-05-10 10:27:17 +02:00
lastFetched time.Time
2017-05-10 11:03:49 +02:00
key common.SearchParam
data []model.Torrent
count, size int
2017-05-10 10:27:17 +02:00
}
// Check the cache for and existing record. If miss, run fn to retrieve fresh
// values.
func Get(key common.SearchParam, fn func() ([]model.Torrent, int, error)) (
data []model.Torrent, count int, err error,
2017-05-10 10:27:17 +02:00
) {
s := getStore(key)
// Also keeps multiple requesters from simultaneously requesting the same
// data
s.Lock()
defer s.Unlock()
if s.isFresh() {
return s.data, s.count, nil
2017-05-10 10:27:17 +02:00
}
data, count, err = fn()
2017-05-10 10:27:17 +02:00
if err != nil {
return
2017-05-10 10:27:17 +02:00
}
s.update(data, count)
return
2017-05-10 10:27:17 +02:00
}
// Retrieve a store from the cache or create a new one
2017-05-10 11:03:49 +02:00
func getStore(k common.SearchParam) (s *store) {
2017-05-10 10:27:17 +02:00
mu.Lock()
defer mu.Unlock()
el := cache[k]
if el == nil {
s = &store{key: k}
cache[k] = ll.PushFront(s)
} else {
ll.MoveToFront(el)
s = el.Value.(*store)
}
return s
}
// Clear the cache. Only used for testing.
func Clear() {
mu.Lock()
defer mu.Unlock()
ll = list.New()
2017-05-10 11:03:49 +02:00
cache = make(map[common.SearchParam]*list.Element, 10)
2017-05-10 10:27:17 +02:00
}
// Update the total used memory counter and evict, if over limit
func updateUsedSize(delta int) {
mu.Lock()
defer mu.Unlock()
totalUsed += delta
for totalUsed > int(Size)*(1<<20) {
s := ll.Remove(ll.Back()).(*store)
delete(cache, s.key)
totalUsed -= s.size
}
}
// Return, if the data can still be considered fresh, without querying the DB
func (s *store) isFresh() bool {
if s.lastFetched.IsZero() { // New store
return false
}
return s.lastFetched.Add(expiryTime).After(time.Now())
2017-05-10 10:27:17 +02:00
}
// Stores the new values of s. Calculates and stores the new size. Passes the
// delta to the central cache to fire eviction checks.
func (s *store) update(data []model.Torrent, count int) {
2017-05-10 10:27:17 +02:00
newSize := 0
for _, d := range data {
newSize += d.Size()
}
s.data = data
s.count = count
2017-05-10 10:27:17 +02:00
delta := newSize - s.size
s.size = newSize
2017-05-10 11:03:49 +02:00
s.lastFetched = time.Now()
2017-05-10 10:27:17 +02:00
// Technically it is possible to update the size even when the store is
// already evicted, but that should never happen, unless you have a very
// small cache, very large stored datasets and a lot of traffic.
updateUsedSize(delta)
}