Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ const (
ActiveExpiration
)

// ExpireAfterType enumerates the expire after types.
type ExpireAfterType int

const (
// ExpireAfterWrite specifies that each entry should be automatically removed from the cache
// once a fixed duration has elapsed after the entry's creation, or the most recent replacement of its value.
ExpireAfterWrite ExpireAfterType = iota

// ExpireAfterAccess specifies that each entry should be automatically removed from the cache
// once a fixed duration has elapsed after the entry's creation, the most recent replacement of its value,
// or its last access.
ExpireAfterAccess
)

// Config configures the cache.
type Config struct {
// Maximum number of items in the cache
Expand All @@ -73,6 +87,8 @@ type Config struct {
MinAge time.Duration
// Type of key expiration: Passive or Active
ExpirationType ExpirationType
// Type of the expiration time: AfterWrite or AfterAccess
ExpireAfterType ExpireAfterType
// For active expiration, how often to iterate over the keyspace. Defaults
// to the MaxAge
ExpirationInterval time.Duration
Expand All @@ -96,6 +112,7 @@ type Cache struct {
minAge time.Duration
maxAge time.Duration
expirationType ExpirationType
expireAfterType ExpireAfterType
expirationInterval time.Duration
onEviction func(key, value interface{})
onExpiration func(key, value interface{})
Expand Down Expand Up @@ -144,13 +161,20 @@ func New(config Config) *Cache {
interval = config.MaxAge
}

expireAfterType := config.ExpireAfterType
if expireAfterType <= 0 {
// default to ExpireAfterWrite
expireAfterType = ExpireAfterWrite
}

seed := rand.NewSource(time.Now().UnixNano())

cache := &Cache{
capacity: config.Capacity,
maxAge: config.MaxAge,
minAge: minAge,
expirationType: config.ExpirationType,
expireAfterType: expireAfterType,
expirationInterval: interval,
onEviction: config.OnEviction,
onExpiration: config.OnExpiration,
Expand Down Expand Up @@ -209,6 +233,9 @@ func (cache *Cache) Get(key interface{}) (interface{}, bool) {

if element, ok := cache.items[key]; ok {
entry := element.Value.(*cacheEntry)
if cache.expireAfterType == ExpireAfterAccess {
entry.timestamp = cache.getTimestamp()
}
if cache.maxAge == 0 || time.Since(entry.timestamp) <= cache.maxAge {
cache.evictionList.MoveToFront(element)
cache.hits++
Expand Down
28 changes: 28 additions & 0 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,34 @@ func TestExpiration(t *testing.T) {
assert.False(t, eviction)
}

func TestExpirationAfterAccess(t *testing.T) {
var k, v interface{}
var eviction bool

cache := New(Config{
Capacity: 1,
MaxAge: time.Millisecond,
ExpireAfterType: ExpireAfterAccess,
OnExpiration: func(key, value interface{}) {
k = key
v = value
},
OnEviction: func(key, value interface{}) {
eviction = true
},
})

cache.Set("foo", 1)
<-time.After(time.Millisecond * 2)

val, ok := cache.Get("foo")
assert.True(t, ok)
assert.NotNil(t, val)
assert.Nil(t, k)
assert.Nil(t, v)
assert.False(t, eviction)
}

type MockRandGenerator struct {
startAt int64
incr int64
Expand Down