2023-10-04 12:00:12 +00:00
|
|
|
package ttlcache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// NoTTL indicates that an item should never expire.
|
|
|
|
NoTTL time.Duration = -1
|
|
|
|
|
2024-04-17 12:19:03 +00:00
|
|
|
// PreviousOrDefaultTTL indicates that existing TTL of item should be used
|
|
|
|
// default TTL will be used as fallback if item doesn't exist
|
|
|
|
PreviousOrDefaultTTL time.Duration = -2
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
// DefaultTTL indicates that the default TTL value of the cache
|
|
|
|
// instance should be used.
|
|
|
|
DefaultTTL time.Duration = 0
|
|
|
|
)
|
|
|
|
|
|
|
|
// Item holds all the information that is associated with a single
|
|
|
|
// cache value.
|
|
|
|
type Item[K comparable, V any] struct {
|
|
|
|
// the mutex needs to be locked only when:
|
|
|
|
// - data fields are being read inside accessor methods
|
|
|
|
// - data fields are being updated
|
|
|
|
// when data fields are being read in one of the cache's
|
|
|
|
// methods, we can be sure that these fields are not modified
|
|
|
|
// concurrently since the item list is locked by its own mutex as
|
|
|
|
// well, so locking this mutex would be redundant.
|
|
|
|
// In other words, this mutex is only useful when these fields
|
|
|
|
// are being read from the outside (e.g. in event functions).
|
|
|
|
mu sync.RWMutex
|
|
|
|
key K
|
|
|
|
value V
|
|
|
|
ttl time.Duration
|
|
|
|
expiresAt time.Time
|
|
|
|
queueIndex int
|
|
|
|
version int64
|
|
|
|
}
|
|
|
|
|
|
|
|
// newItem creates a new cache item.
|
|
|
|
func newItem[K comparable, V any](key K, value V, ttl time.Duration, enableVersionTracking bool) *Item[K, V] {
|
|
|
|
item := &Item[K, V]{
|
|
|
|
key: key,
|
|
|
|
value: value,
|
|
|
|
ttl: ttl,
|
|
|
|
}
|
|
|
|
|
|
|
|
if !enableVersionTracking {
|
|
|
|
item.version = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
item.touch()
|
|
|
|
|
|
|
|
return item
|
|
|
|
}
|
|
|
|
|
|
|
|
// update modifies the item's value, TTL, and version.
|
|
|
|
func (item *Item[K, V]) update(value V, ttl time.Duration) {
|
|
|
|
item.mu.Lock()
|
|
|
|
defer item.mu.Unlock()
|
|
|
|
|
|
|
|
item.value = value
|
2024-04-17 12:19:03 +00:00
|
|
|
|
|
|
|
// update version if enabled
|
|
|
|
if item.version > -1 {
|
|
|
|
item.version++
|
|
|
|
}
|
|
|
|
|
|
|
|
// no need to update ttl or expiry in this case
|
|
|
|
if ttl == PreviousOrDefaultTTL {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
item.ttl = ttl
|
|
|
|
|
|
|
|
// reset expiration timestamp because the new TTL may be
|
|
|
|
// 0 or below
|
|
|
|
item.expiresAt = time.Time{}
|
|
|
|
item.touchUnsafe()
|
|
|
|
}
|
|
|
|
|
|
|
|
// touch updates the item's expiration timestamp.
|
|
|
|
func (item *Item[K, V]) touch() {
|
|
|
|
item.mu.Lock()
|
|
|
|
defer item.mu.Unlock()
|
|
|
|
|
|
|
|
item.touchUnsafe()
|
|
|
|
}
|
|
|
|
|
|
|
|
// touchUnsafe updates the item's expiration timestamp without
|
|
|
|
// locking the mutex.
|
|
|
|
func (item *Item[K, V]) touchUnsafe() {
|
|
|
|
if item.ttl <= 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
item.expiresAt = time.Now().Add(item.ttl)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsExpired returns a bool value that indicates whether the item
|
|
|
|
// is expired.
|
|
|
|
func (item *Item[K, V]) IsExpired() bool {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.isExpiredUnsafe()
|
|
|
|
}
|
|
|
|
|
|
|
|
// isExpiredUnsafe returns a bool value that indicates whether the
|
|
|
|
// the item is expired without locking the mutex
|
|
|
|
func (item *Item[K, V]) isExpiredUnsafe() bool {
|
|
|
|
if item.ttl <= 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return item.expiresAt.Before(time.Now())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Key returns the key of the item.
|
|
|
|
func (item *Item[K, V]) Key() K {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.key
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns the value of the item.
|
|
|
|
func (item *Item[K, V]) Value() V {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.value
|
|
|
|
}
|
|
|
|
|
|
|
|
// TTL returns the TTL value of the item.
|
|
|
|
func (item *Item[K, V]) TTL() time.Duration {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.ttl
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExpiresAt returns the expiration timestamp of the item.
|
|
|
|
func (item *Item[K, V]) ExpiresAt() time.Time {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.expiresAt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Version returns the version of the item. It shows the total number of
|
|
|
|
// changes made to the item.
|
|
|
|
// If version tracking is disabled, the return value is always -1.
|
|
|
|
func (item *Item[K, V]) Version() int64 {
|
|
|
|
item.mu.RLock()
|
|
|
|
defer item.mu.RUnlock()
|
|
|
|
|
|
|
|
return item.version
|
|
|
|
}
|