Handle evictions properly when cache is empty.

This commit is contained in:
Kyle Isom 2023-08-27 18:01:16 -07:00
parent 6fbdece4be
commit b92e16fa4d
4 changed files with 23 additions and 10 deletions

2
go.mod
View File

@ -1,3 +1,5 @@
module git.wntrmute.dev/kyle/go-mru module git.wntrmute.dev/kyle/go-mru
go 1.17 go 1.17
require github.com/benbjohnson/clock v1.3.5

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=

24
mru.go
View File

@ -30,7 +30,7 @@ type Cache struct {
// New must be used to create a new Cache. // New must be used to create a new Cache.
func New(cap int) *Cache { func New(cap int) *Cache {
return &Cache{ return &Cache{
store: map[string]*Item{}, store: map[string]*item{},
access: newTimestamps(cap), access: newTimestamps(cap),
cap: cap, cap: cap,
clock: clock.New(), clock: clock.New(),
@ -39,11 +39,11 @@ func New(cap int) *Cache {
} }
func (c *Cache) lock() { func (c *Cache) lock() {
m.mtx.lock() c.mtx.Lock()
} }
func (c *Cache) unlock() { func (c *Cache) unlock() {
m.mtx.unlock() c.mtx.Unlock()
} }
// Len returns the number of items currently in the cache. // Len returns the number of items currently in the cache.
@ -53,6 +53,10 @@ func (c *Cache) Len() int {
// evict should remove the least-recently-used cache item. // evict should remove the least-recently-used cache item.
func (c *Cache) evict() { func (c *Cache) evict() {
if c.access.Len() == 0 {
return
}
k := c.access.K(0) k := c.access.K(0)
c.evictKey(k) c.evictKey(k)
} }
@ -79,8 +83,8 @@ func (c *Cache) sanityCheck() {
// data structures are consistent. It is not normally required, and it // data structures are consistent. It is not normally required, and it
// is primarily used in testing. // is primarily used in testing.
func (c *Cache) ConsistencyCheck() error { func (c *Cache) ConsistencyCheck() error {
m.lock() c.lock()
defer m.unlock() defer c.unlock()
if err := c.access.ConsistencyCheck(); err != nil { if err := c.access.ConsistencyCheck(); err != nil {
return err return err
} }
@ -111,8 +115,8 @@ func (c *Cache) ConsistencyCheck() error {
// Store adds the value v to the cache under the k. // Store adds the value v to the cache under the k.
func (c *Cache) Store(k string, v interface{}) { func (c *Cache) Store(k string, v interface{}) {
m.lock() c.lock()
defer m.unlock() defer c.unlock()
c.sanityCheck() c.sanityCheck()
@ -124,7 +128,7 @@ func (c *Cache) Store(k string, v interface{}) {
c.evictKey(k) c.evictKey(k)
} }
itm := &Item{ itm := &item{
V: v, V: v,
access: c.clock.Now().UnixNano(), access: c.clock.Now().UnixNano(),
} }
@ -136,8 +140,8 @@ func (c *Cache) Store(k string, v interface{}) {
// Get returns the value stored in the cache. If the item isn't present, // Get returns the value stored in the cache. If the item isn't present,
// it will return false. // it will return false.
func (c *Cache) Get(k string) (interface{}, bool) { func (c *Cache) Get(k string) (interface{}, bool) {
m.lock() c.lock()
defer m.unlock() defer c.unlock()
c.sanityCheck() c.sanityCheck()

View File

@ -20,6 +20,11 @@ func TestBasicCacheEviction(t *testing.T) {
t.Fatal("cache should have size 0") t.Fatal("cache should have size 0")
} }
c.evict()
if err := c.ConsistencyCheck(); err != nil {
t.Fatal(err)
}
c.Store("raven", 1) c.Store("raven", 1)
if err := c.ConsistencyCheck(); err != nil { if err := c.ConsistencyCheck(); err != nil {
t.Fatal(err) t.Fatal(err)