From 73a30b2033723f9399acec45b8b7f266195debe9 Mon Sep 17 00:00:00 2001 From: Patrick Mylund Nielsen Date: Mon, 22 Dec 2014 01:12:10 -0500 Subject: [PATCH] Add NewFrom, and deprecate Save, SaveFile, Load, and LoadFile --- cache.go | 70 ++++++++++++++++++++++++++++++++++++++++----------- cache_test.go | 28 +++++++++++++++++++++ 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/cache.go b/cache.go index 1e4b7f7..845d945 100644 --- a/cache.go +++ b/cache.go @@ -821,6 +821,9 @@ func (c *cache) DeleteExpired() { } // Write the cache's items (using Gob) to an io.Writer. +// +// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the +// documentation for NewFrom.) func (c *cache) Save(w io.Writer) (err error) { enc := gob.NewEncoder(w) defer func() { @@ -839,6 +842,9 @@ func (c *cache) Save(w io.Writer) (err error) { // Save the cache's items to the given filename, creating the file if it // doesn't exist, and overwriting it if it does. +// +// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the +// documentation for NewFrom.) func (c *cache) SaveFile(fname string) error { fp, err := os.Create(fname) if err != nil { @@ -854,6 +860,9 @@ func (c *cache) SaveFile(fname string) error { // Add (Gob-serialized) cache items from an io.Reader, excluding any items with // keys that already exist (and haven't expired) in the current cache. +// +// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the +// documentation for NewFrom.) func (c *cache) Load(r io.Reader) error { dec := gob.NewDecoder(r) items := map[string]*Item{} @@ -873,6 +882,9 @@ func (c *cache) Load(r io.Reader) error { // Load and add cache items from the given filename, excluding any items with // keys that already exist in the current cache. +// +// NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the +// documentation for NewFrom.) func (c *cache) LoadFile(fname string) error { fp, err := os.Open(fname) if err != nil { @@ -943,33 +955,63 @@ func runJanitor(c *cache, ci time.Duration) { go j.Run(c) } -func newCache(de time.Duration) *cache { +func newCache(de time.Duration, m map[string]*Item) *cache { if de == 0 { de = -1 } c := &cache{ defaultExpiration: de, - items: map[string]*Item{}, + items: m, } return c } +func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]*Item) *Cache { + c := newCache(de, m) + // This trick ensures that the janitor goroutine (which--granted it + // was enabled--is running DeleteExpired on c forever) does not keep + // the returned C object from being garbage collected. When it is + // garbage collected, the finalizer stops the janitor goroutine, after + // which c can be collected. + C := &Cache{c} + if ci > 0 { + runJanitor(c, ci) + runtime.SetFinalizer(C, stopJanitor) + } + return C +} + // Return a new cache with a given default expiration duration and cleanup // 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 calling DeleteExpired. func New(defaultExpiration, cleanupInterval time.Duration) *Cache { - c := newCache(defaultExpiration) - // This trick ensures that the janitor goroutine (which--granted it - // was enabled--is running DeleteExpired on c forever) does not keep - // the returned C object from being garbage collected. When it is - // garbage collected, the finalizer stops the janitor goroutine, after - // which c can be collected. - C := &Cache{c} - if cleanupInterval > 0 { - runJanitor(c, cleanupInterval) - runtime.SetFinalizer(C, stopJanitor) - } - return C + items := make(map[string]*Item) + return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) +} + +// Return a new cache with a given default expiration duration and cleanup +// 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 calling DeleteExpired. +// +// NewFrom also accepts an items map which will serve as the underlying map +// for the cache. This is useful for deserializing a cache (serialized using +// e.g. gob.Encode on c.Items()), or setting a starting size by passing in e.g. +// make(map[string]*Item, 500) to avoid repeat initial resizing of a map that's +// expected to reach a certain minimum size. +// +// Only the cache's methods synchronize access to this map, so it is not +// recommended to keep any references to the map around after creating a cache. +// If need be, the map can be accessed at a later point using c.Items() (with +// the same caveats.) +// +// Note regarding serialization: When using e.g. gob, make sure to gob.Register +// the individual types stored in the cache before encoding a map retrieved with +// c.Items(), and to register those same types before decoding a blob containing +// an items map. +func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]*Item) *Cache { + return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) } diff --git a/cache_test.go b/cache_test.go index 09980a6..3e5d939 100644 --- a/cache_test.go +++ b/cache_test.go @@ -106,6 +106,34 @@ func TestCacheTimes(t *testing.T) { } } +func TestNewFrom(t *testing.T) { + m := map[string]*Item{ + "a": &Item{ + Object: 1, + Expiration: nil, + }, + "b": &Item{ + Object: 2, + Expiration: nil, + }, + } + tc := NewFrom(0, 0, m) + a, found := tc.Get("a") + if !found { + t.Fatal("Did not find a") + } + if a.(int) != 1 { + t.Fatal("a is not 1") + } + b, found := tc.Get("b") + if !found { + t.Fatal("Did not find b") + } + if b.(int) != 2 { + t.Fatal("b is not 2") + } +} + func TestStorePointerToStruct(t *testing.T) { tc := New(0, 0) tc.Set("foo", &TestStruct{Num: 1}, 0)