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 { type cache struct {
sync.Mutex sync.RWMutex
defaultExpiration time.Duration defaultExpiration time.Duration
items map[string]*item items map[string]*item
janitor *janitor janitor *janitor
@ -122,8 +122,8 @@ func (c *cache) Add(k string, x interface{}, d time.Duration) error {
return nil return nil
} }
// Set a new value for the cache key only if it already exists. Returns an // Set a new value for the cache key only if it already exists, and the existing
// error if it does not. // item hasn't expired. Returns an error otherwise.
func (c *cache) Replace(k string, x interface{}, d time.Duration) error { func (c *cache) Replace(k string, x interface{}, d time.Duration) error {
c.Lock() c.Lock()
_, found := c.get(k) _, 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 // Get an item from the cache. Returns the item or nil, and a bool indicating
// whether the key was found. // whether the key was found.
func (c *cache) Get(k string) (interface{}, bool) { func (c *cache) Get(k string) (interface{}, bool) {
c.Lock() c.RLock()
x, found := c.get(k) x, found := c.get(k)
c.Unlock() c.RUnlock()
return x, found return x, found
} }
func (c *cache) get(k string) (interface{}, bool) { func (c *cache) get(k string) (interface{}, bool) {
item, found := c.items[k] item, found := c.items[k]
if !found { if !found || item.Expired() {
return nil, false
}
if item.Expired() {
c.delete(k)
return nil, false return nil, false
} }
return item.Object, true return item.Object, true
@ -874,12 +870,13 @@ func (c *cache) DeleteExpired() {
// Write the cache's items (using Gob) to an io.Writer. // Write the cache's items (using Gob) to an io.Writer.
func (c *cache) Save(w io.Writer) (err error) { func (c *cache) Save(w io.Writer) (err error) {
enc := gob.NewEncoder(w) enc := gob.NewEncoder(w)
defer func() { defer func() {
if x := recover(); x != nil { if x := recover(); x != nil {
err = fmt.Errorf("Error registering item types with Gob library") err = fmt.Errorf("Error registering item types with Gob library")
} }
}() }()
c.RLock()
defer c.RUnlock()
for _, v := range c.items { for _, v := range c.items {
gob.Register(v.Object) 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 // 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 { func (c *cache) Load(r io.Reader) error {
dec := gob.NewDecoder(r) dec := gob.NewDecoder(r)
items := map[string]*item{} items := map[string]*item{}
err := dec.Decode(&items) err := dec.Decode(&items)
if err == nil { if err == nil {
c.Lock()
defer c.Unlock()
for k, v := range items { for k, v := range items {
_, found := c.items[k] ov, found := c.items[k]
if !found { if !found || ov.Expired() {
c.items[k] = v 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 // Returns the number of items in the cache. This may include items that have
// expired, but have not yet been cleaned up. // expired, but have not yet been cleaned up.
func (c *cache) ItemCount() int { func (c *cache) ItemCount() int {
c.Lock() c.RLock()
n := len(c.items) n := len(c.items)
c.Unlock() c.RUnlock()
return n 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 // 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 // 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 // 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 { func New(defaultExpiration, cleanupInterval time.Duration) *Cache {
c := newCache(defaultExpiration) c := newCache(defaultExpiration)
// This trick ensures that the janitor goroutine (which--granted it // This trick ensures that the janitor goroutine (which--granted it