Permalink
Browse files

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)
  • Loading branch information...
1 parent 1fc39f1 commit 5388b25b3b3d7ab9e68b08a9b06a346e6b070140 @patrickmn committed Jul 1, 2013
Showing with 16 additions and 17 deletions.
  1. +16 −17 cache.go
View
@@ -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

0 comments on commit 5388b25

Please sign in to comment.