158 lines
3.7 KiB
Go
158 lines
3.7 KiB
Go
/*
|
|
* Copyright (C) 2020-2022, IrineSistiana
|
|
*
|
|
* This file is part of mosdns.
|
|
*
|
|
* mosdns is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* mosdns is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package cache
|
|
|
|
import (
|
|
"github.com/IrineSistiana/mosdns/v5/pkg/concurrent_lru"
|
|
"github.com/IrineSistiana/mosdns/v5/pkg/concurrent_map"
|
|
"github.com/IrineSistiana/mosdns/v5/pkg/utils"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultCleanerInterval = time.Second * 10
|
|
)
|
|
|
|
type Key interface {
|
|
concurrent_lru.Hashable
|
|
}
|
|
|
|
type Value interface {
|
|
any
|
|
}
|
|
|
|
// Cache is a simple map cache that stores values in memory.
|
|
// It is safe for concurrent use.
|
|
type Cache[K Key, V Value] struct {
|
|
opts Opts
|
|
|
|
closed atomic.Bool
|
|
closeNotify chan struct{}
|
|
m *concurrent_map.Map[K, *elem[V]]
|
|
}
|
|
|
|
type Opts struct {
|
|
Size int
|
|
CleanerInterval time.Duration
|
|
}
|
|
|
|
func (opts *Opts) init() {
|
|
utils.SetDefaultNum(&opts.Size, 1024)
|
|
utils.SetDefaultNum(&opts.CleanerInterval, defaultCleanerInterval)
|
|
}
|
|
|
|
type elem[V Value] struct {
|
|
v V
|
|
expirationTime time.Time
|
|
}
|
|
|
|
// New initializes a Cache.
|
|
// The minimum size is 1024.
|
|
// cleanerInterval specifies the interval that Cache scans
|
|
// and discards expired values. If cleanerInterval <= 0, a default
|
|
// interval will be used.
|
|
func New[K Key, V Value](opts Opts) *Cache[K, V] {
|
|
opts.init()
|
|
c := &Cache[K, V]{
|
|
closeNotify: make(chan struct{}),
|
|
m: concurrent_map.NewMapCache[K, *elem[V]](opts.Size),
|
|
}
|
|
go c.gcLoop(opts.CleanerInterval)
|
|
return c
|
|
}
|
|
|
|
// Close closes the inner cleaner of this cache.
|
|
func (c *Cache[K, V]) Close() error {
|
|
if ok := c.closed.CompareAndSwap(false, true); ok {
|
|
close(c.closeNotify)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Cache[K, V]) Get(key K) (v V, expirationTime time.Time, ok bool) {
|
|
if e, hasEntry := c.m.Get(key); hasEntry {
|
|
if e.expirationTime.Before(time.Now()) {
|
|
c.m.Del(key)
|
|
return
|
|
}
|
|
return e.v, e.expirationTime, true
|
|
}
|
|
return
|
|
}
|
|
|
|
// Range calls f through all entries. If f returns an error, the same error will be returned
|
|
// by Range.
|
|
func (c *Cache[K, V]) Range(f func(key K, v V, expirationTime time.Time) error) error {
|
|
cf := func(key K, v *elem[V]) (newV *elem[V], setV bool, delV bool, err error) {
|
|
return nil, false, false, f(key, v.v, v.expirationTime)
|
|
}
|
|
return c.m.RangeDo(cf)
|
|
}
|
|
|
|
// Store stores this kv in cache. If expirationTime is before time.Now(),
|
|
// Store is an noop.
|
|
func (c *Cache[K, V]) Store(key K, v V, expirationTime time.Time) {
|
|
now := time.Now()
|
|
if now.After(expirationTime) {
|
|
return
|
|
}
|
|
|
|
e := &elem[V]{
|
|
v: v,
|
|
expirationTime: expirationTime,
|
|
}
|
|
c.m.Set(key, e)
|
|
return
|
|
}
|
|
|
|
func (c *Cache[K, V]) gcLoop(interval time.Duration) {
|
|
if interval <= 0 {
|
|
interval = defaultCleanerInterval
|
|
}
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-c.closeNotify:
|
|
return
|
|
case now := <-ticker.C:
|
|
c.gc(now)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache[K, V]) gc(now time.Time) {
|
|
f := func(key K, v *elem[V]) (newV *elem[V], setV, delV bool, err error) {
|
|
return nil, false, now.After(v.expirationTime), nil
|
|
}
|
|
_ = c.m.RangeDo(f)
|
|
}
|
|
|
|
// Len returns the current size of this cache.
|
|
func (c *Cache[K, V]) Len() int {
|
|
return c.m.Len()
|
|
}
|
|
|
|
// Flush removes all stored entries from this cache.
|
|
func (c *Cache[K, V]) Flush() {
|
|
c.m.Flush()
|
|
}
|