Initial QSfera import
This commit is contained in:
+193
@@ -0,0 +1,193 @@
|
||||
package ttlcache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoTTL indicates that an item should never expire.
|
||||
NoTTL time.Duration = -1
|
||||
|
||||
// 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
|
||||
|
||||
// DefaultTTL indicates that the default TTL value of the cache
|
||||
// instance should be used.
|
||||
DefaultTTL time.Duration = 0
|
||||
)
|
||||
|
||||
// CostItem holds the key and the value of the Item object for
|
||||
// Item cost calculation purposes.
|
||||
type CostItem[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
// 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
|
||||
calculateCost CostFunc[K, V]
|
||||
cost uint64
|
||||
}
|
||||
|
||||
// NewItem creates a new cache item.
|
||||
//
|
||||
// Deprecated: Use NewItemWithOpts instead. This function will be removed
|
||||
// in a future release.
|
||||
func NewItem[K comparable, V any](key K, value V, ttl time.Duration, enableVersionTracking bool) *Item[K, V] {
|
||||
return NewItemWithOpts(key, value, ttl, WithItemVersion[K, V](enableVersionTracking))
|
||||
}
|
||||
|
||||
// NewItemWithOpts creates a new cache item and applies the provided item
|
||||
// options.
|
||||
func NewItemWithOpts[K comparable, V any](key K, value V, ttl time.Duration, opts ...ItemOption[K, V]) *Item[K, V] {
|
||||
item := &Item[K, V]{
|
||||
key: key,
|
||||
value: value,
|
||||
ttl: ttl,
|
||||
version: -1,
|
||||
calculateCost: func(item CostItem[K, V]) uint64 { return 0 },
|
||||
}
|
||||
|
||||
applyItemOptions(item, opts...)
|
||||
item.touch()
|
||||
item.cost = item.calculateCost(CostItem[K, V]{
|
||||
Key: key,
|
||||
Value: value,
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
// update version if enabled
|
||||
if item.version > -1 {
|
||||
item.version++
|
||||
}
|
||||
|
||||
// no need to update ttl or expiry in this case
|
||||
if ttl != PreviousOrDefaultTTL {
|
||||
item.ttl = ttl
|
||||
// reset expiration timestamp because the new TTL may be
|
||||
// 0 or below
|
||||
item.expiresAt = time.Time{}
|
||||
item.touchUnsafe()
|
||||
}
|
||||
|
||||
// calculating the costs
|
||||
item.cost = item.calculateCost(CostItem[K, V]{
|
||||
Key: item.key,
|
||||
Value: item.value,
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Cost returns the cost of the item.
|
||||
func (item *Item[K, V]) Cost() uint64 {
|
||||
item.mu.RLock()
|
||||
defer item.mu.RUnlock()
|
||||
|
||||
return item.cost
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user