From 96eafc2dcdc5386986b5de50fde4ec02a388ae38 Mon Sep 17 00:00:00 2001 From: Peng Gao Date: Thu, 11 Aug 2016 16:42:28 +0800 Subject: [PATCH] Add generator for code Signed-off-by: Peng Gao --- CONTRIBUTORS | 3 +- cache.go | 2 +- cachemap/main.go | 361 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 cachemap/main.go diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 8a4da4e..123cbd7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,4 +1,4 @@ -This is a list of people who have contributed code to go-cache. They, or their +This is a list of people who have contributed code to cachemap. They, or their employers, are the copyright holders of the contributed code. Contributed code is subject to the license restrictions listed in LICENSE (as they were when the code was contributed.) @@ -6,3 +6,4 @@ code was contributed.) Dustin Sallings Jason Mooberry Sergey Shepelev +Peng Gao diff --git a/cache.go b/cache.go index 213e180..cc180ae 100644 --- a/cache.go +++ b/cache.go @@ -21,7 +21,7 @@ func (item Item) Expired() bool { } const ( - // For use with functions that take an expiration time. + // For use with functions that take no expiration time. NoExpiration time.Duration = -1 // For use with functions that take an expiration time. Equivalent to // passing in the same expiration duration as was given to New() or diff --git a/cachemap/main.go b/cachemap/main.go new file mode 100644 index 0000000..362cb1c --- /dev/null +++ b/cachemap/main.go @@ -0,0 +1,361 @@ +package main + +import ( + "flag" + "fmt" + "go/parser" + "go/token" + "os" + "text/template" +) + +var cachemapTemplate = `// Automatically generated file; DO NOT EDIT +package {{ .PackageName }} + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +type Item struct { + Object {{ .ValueType }} + Expiration int64 +} + +// Returns true if the item has expired. +func (item Item) Expired() bool { + if item.Expiration == 0 { + return false + } + return time.Now().UnixNano() > item.Expiration +} + +const ( + // For use with functions that take no expiration time. + NoExpiration time.Duration = -1 + // For use with functions that take an expiration time. Equivalent to + // passing in the same expiration duration as was given to {{ .Cache }}New(). + DefaultExpiration time.Duration = 0 +) + +type {{ .Cache }} struct { + *cache + // If this is confusing, see the comment at the bottom of {{ .Cache }}New() +} + +type cache struct { + defaultExpiration time.Duration + items map[string]Item + mu sync.RWMutex + onEvicted func(string, *{{ .ValueType }}) + janitor *janitor +} + +// Add an item to the cache, replacing any existing item. If the duration is 0 +// (DefaultExpiration), the cache's default expiration time is used. If it is -1 +// (NoExpiration), the item never expires. +func (c *cache) Set(k string, x {{ .ValueType }}, d time.Duration) { + // "Inlining" of set + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.mu.Lock() + c.items[k] = Item{ + Object: x, + Expiration: e, + } + // TODO: Calls to mu.Unlock are currently not deferred because defer + // adds ~200 ns (as of go1.) + c.mu.Unlock() +} + +func (c *cache) set(k string, x {{ .ValueType }}, d time.Duration) { + var e int64 + if d == DefaultExpiration { + d = c.defaultExpiration + } + if d > 0 { + e = time.Now().Add(d).UnixNano() + } + c.items[k] = Item{ + Object: x, + Expiration: e, + } +} + +// Add an item to the cache only if an item doesn't already exist for the given +// key, or if the existing item has expired. Returns an error otherwise. +func (c *cache) Add(k string, x {{ .ValueType }}, d time.Duration) error { + c.mu.Lock() + _, found := c.get(k) + if found { + c.mu.Unlock() + return fmt.Errorf("Item %s already exists", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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 {{ .ValueType }}, d time.Duration) error { + c.mu.Lock() + _, found := c.get(k) + if !found { + c.mu.Unlock() + return fmt.Errorf("Item %s doesn't exist", k) + } + c.set(k, x, d) + c.mu.Unlock() + return nil +} + +// 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) *{{ .ValueType }} { + c.mu.RLock() + // "Inlining" of get and Expired + item, found := c.items[k] + if !found { + c.mu.RUnlock() + return nil + } + if item.Expiration > 0 { + if time.Now().UnixNano() > item.Expiration { + c.mu.RUnlock() + return nil + } + } + c.mu.RUnlock() + return &item.Object +} + +func (c *cache) get(k string) (*{{ .ValueType }}, bool) { + item, found := c.items[k] + if !found { + return nil, false + } + // "Inlining" of Expired + if item.Expiration > 0 { + if time.Now().UnixNano() > item.Expiration { + return nil, false + } + } + return &item.Object, true +} + +// Increment an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// item's value is not an integer, if it was not found, or if it is not +// possible to increment it by n. To retrieve the incremented value, use one +// of the specialized methods, e.g. IncrementInt64. +// TODO: Increment for numberic type. +func (c *cache) Increment(k string, n int64) error { + return nil +} + +// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, +// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the +// item's value is not an integer, if it was not found, or if it is not +// possible to decrement it by n. To retrieve the decremented value, use one +// of the specialized methods, e.g. DecrementInt64. +// TODO: Decrement +func (c *cache) Decrement(k string, n int64) error { + // TODO: Implement Increment and Decrement more cleanly. + // (Cannot do Increment(k, n*-1) for uints.) + return nil +} + +// Delete an item from the cache. Does nothing if the key is not in the cache. +func (c *cache) Delete(k string) { + c.mu.Lock() + v, evicted := c.delete(k) + c.mu.Unlock() + if evicted { + c.onEvicted(k, v) + } +} + +func (c *cache) delete(k string) (*{{ .ValueType }}, bool) { + if c.onEvicted != nil { + if v, found := c.items[k]; found { + delete(c.items, k) + return &v.Object, true + } + } + delete(c.items, k) + return nil, false +} + +type keyAndValue struct { + key string + value *{{ .ValueType }} +} + +// Delete all expired items from the cache. +func (c *cache) DeleteExpired() { + var evictedItems []keyAndValue + now := time.Now().UnixNano() + c.mu.Lock() + for k, v := range c.items { + // "Inlining" of expired + if v.Expiration > 0 && now > v.Expiration { + ov, evicted := c.delete(k) + if evicted { + evictedItems = append(evictedItems, keyAndValue{k, ov}) + } + } + } + c.mu.Unlock() + for _, v := range evictedItems { + c.onEvicted(v.key, v.value) + } +} + +// Sets an (optional) function that is called with the key and value when an +// item is evicted from the cache. (Including when it is deleted manually, but +// not when it is overwritten.) Set to nil to disable. +func (c *cache) OnEvicted(f func(string, *{{ .ValueType }})) { + c.mu.Lock() + c.onEvicted = f + c.mu.Unlock() +} + +// Returns the number of items in the cache. This may include items that have +// expired, but have not yet been cleaned up. Equivalent to len(c.Items()). +func (c *cache) ItemCount() int { + c.mu.RLock() + n := len(c.items) + c.mu.RUnlock() + return n +} + +// Delete all items from the cache. +func (c *cache) Flush() { + c.mu.Lock() + c.items = map[string]Item{} + c.mu.Unlock() +} + +type janitor struct { + Interval time.Duration + stop chan bool +} + +func (j *janitor) Run(c *cache) { + j.stop = make(chan bool) + ticker := time.NewTicker(j.Interval) + for { + select { + case <-ticker.C: + c.DeleteExpired() + case <-j.stop: + ticker.Stop() + return + } + } +} + +func stopJanitor(c *{{ .Cache }}) { + c.janitor.stop <- true +} + +func runJanitor(c *cache, ci time.Duration) { + j := &janitor{ + Interval: ci, + } + c.janitor = j + go j.Run(c) +} + +func newCache(de time.Duration, m map[string]Item) *cache { + if de == 0 { + de = -1 + } + c := &cache{ + defaultExpiration: de, + 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) + // 如果C被回收了,但是c不会,因为stopJanitor是一个一直运行的 + // goroutine对c一直有引用不会被回收,所以加一个Finalizer来停掉 + // 这个goroutine然后让c被回收. + 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 one (or NoExpiration), +// 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 c.DeleteExpired(). +func {{ .Cache }}New(defaultExpiration, cleanupInterval time.Duration) *{{ .Cache }}{ + items := make(map[string]Item) + return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) +}` + +func fatal(v ...interface{}) { + fmt.Fprintln(os.Stderr, v...) + os.Exit(1) +} + +func main() { + keyType := flag.String("k", "", "key type") + valueType := flag.String("v", "", "value type") + flag.Parse() + if *keyType == "" { + fatal("key empty") + } + if *valueType == "" { + fatal("value empty") + } + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments) + if err != nil { + fatal(err) + } + var packageName string + for name := range pkgs { + packageName = name + } + f, err := os.OpenFile(fmt.Sprintf("%s2%s_cachemap.go", *keyType, *valueType), os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + fatal(err) + } + defer f.Close() + tpl, err := template.New("cachemap").Parse(cachemapTemplate) + if err != nil { + fatal(err) + } + err = tpl.Execute( + f, + map[string]string{ + "ValueType": *valueType, + "PackageName": packageName, + "Cache": fmt.Sprintf("String2%sCache", *valueType), + }, + ) + if err != nil { + fatal(err) + } +}