Use an RWMutex instead of a Mutex, making Get a read operation only (this slightly changes the eviction behavior: lookup doesn't completely expunge an expired item, but the janitor still will at the next cleanup.) Also, use the same RWMutex in Load and Save (thanks, Alan Shreve)

This commit is contained in:
Patrick Mylund Nielsen 2013-06-30 20:11:57 -04:00
parent 1fc39f1402
commit 5388b25b3b
1 changed files with 16 additions and 17 deletions

View File

@ -76,7 +76,7 @@ type Cache struct {
}
type cache struct {
sync.Mutex
sync.RWMutex
defaultExpiration time.Duration
items map[string]*item
janitor *janitor
@ -122,8 +122,8 @@ func (c *cache) Add(k string, x interface{}, d time.Duration) error {
return nil
}
// Set a new value for the cache key only if it already exists. Returns an
// error if it does not.
// Set a new value for the cache key only if it already exists, and the existing
// item hasn't expired. Returns an error otherwise.
func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
c.Lock()
_, found := c.get(k)
@ -139,19 +139,15 @@ func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
// Get an item from the cache. Returns the item or nil, and a bool indicating
// whether the key was found.
func (c *cache) Get(k string) (interface{}, bool) {
c.Lock()
c.RLock()
x, found := c.get(k)
c.Unlock()
c.RUnlock()
return x, found
}
func (c *cache) get(k string) (interface{}, bool) {
item, found := c.items[k]
if !found {
return nil, false
}
if item.Expired() {
c.delete(k)
if !found || item.Expired() {
return nil, false
}
return item.Object, true
@ -874,12 +870,13 @@ func (c *cache) DeleteExpired() {
// Write the cache's items (using Gob) to an io.Writer.
func (c *cache) Save(w io.Writer) (err error) {
enc := gob.NewEncoder(w)
defer func() {
if x := recover(); x != nil {
err = fmt.Errorf("Error registering item types with Gob library")
}
}()
c.RLock()
defer c.RUnlock()
for _, v := range c.items {
gob.Register(v.Object)
}
@ -903,15 +900,17 @@ func (c *cache) SaveFile(fname string) error {
}
// Add (Gob-serialized) cache items from an io.Reader, excluding any items with
// keys that already exist in the current cache.
// keys that already exist (and haven't expired) in the current cache.
func (c *cache) Load(r io.Reader) error {
dec := gob.NewDecoder(r)
items := map[string]*item{}
err := dec.Decode(&items)
if err == nil {
c.Lock()
defer c.Unlock()
for k, v := range items {
_, found := c.items[k]
if !found {
ov, found := c.items[k]
if !found || ov.Expired() {
c.items[k] = v
}
}
@ -937,9 +936,9 @@ func (c *cache) LoadFile(fname string) error {
// Returns the number of items in the cache. This may include items that have
// expired, but have not yet been cleaned up.
func (c *cache) ItemCount() int {
c.Lock()
c.RLock()
n := len(c.items)
c.Unlock()
c.RUnlock()
return n
}
@ -995,7 +994,7 @@ func newCache(de time.Duration) *cache {
// interval. If the expiration duration is less than 1, the items in the cache
// never expire (by default), and must be deleted manually. If the cleanup
// interval is less than one, expired items are not deleted from the cache
// before their next lookup or before calling DeleteExpired.
// before calling DeleteExpired.
func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
c := newCache(defaultExpiration)
// This trick ensures that the janitor goroutine (which--granted it