github.com/mjibson/goon@v1.1.0/cache.go (about) 1 /* 2 * Copyright (c) 2012 The Goon Authors 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 package goon 18 19 import ( 20 "container/list" 21 "reflect" 22 "sync" 23 ) 24 25 var cachedValueOverhead int 26 27 func init() { 28 // Calculate the platform dependant overhead size for keeping a value in cache 29 var elem list.Element 30 var ci cacheItem 31 cachedValueOverhead += int(reflect.TypeOf(elem).Size()) // list.Element in cache.accessed 32 cachedValueOverhead += int(reflect.TypeOf(&elem).Size()) // *list.Element in cache.elements 33 cachedValueOverhead += int(reflect.TypeOf(ci.key).Size()) // string in cache.elements as key 34 cachedValueOverhead += int(reflect.TypeOf(ci).Size()) // cacheItem pointed to by list.Element.Value 35 // In addition to the above overhead, the total cache value size must include 36 // the length of the key string in bytes and the cap of the value []byte 37 } 38 39 type cacheItem struct { 40 key string 41 value []byte 42 } 43 44 type cache struct { 45 lock sync.Mutex 46 elements map[string]*list.Element // access via key 47 accessed list.List // most recently accessed in front 48 size int // Total size of all the values in the cache 49 limit int // Maximum size allowed 50 } 51 52 const defaultCacheLimit = 16 << 20 // 16 MiB 53 54 func newCache(limit int) *cache { 55 return &cache{elements: map[string]*list.Element{}, limit: limit} 56 } 57 58 func (c *cache) setLimit(limit int) { 59 c.lock.Lock() 60 c.limit = limit 61 c.meetLimitUnderLock() 62 c.lock.Unlock() 63 } 64 65 // meetLimit must be called under cache.lock 66 func (c *cache) meetLimitUnderLock() { 67 for c.size > c.limit { 68 e := c.accessed.Back() 69 if e == nil { 70 break 71 } 72 c.deleteExistingUnderLock(e) 73 } 74 } 75 76 // setUnderLock must be called under cache.lock 77 func (c *cache) setUnderLock(item *cacheItem) { 78 // Check if there's already an entry for this key 79 if e, ok := c.elements[item.key]; ok { 80 // There already exists a value for this key, so update it 81 ci := e.Value.(*cacheItem) 82 c.size += cap(item.value) - cap(ci.value) 83 // Make sure that item.key is the same pointer as the map key, 84 // as this will ensure faster map lookup via pointer equality. 85 // Not doing so would also lead to double memory usage, as the two key 86 // pointers would be pointing to the same contents in different places. 87 item.key = ci.key 88 e.Value = item 89 c.accessed.MoveToFront(e) 90 } else { 91 // Brand new key, so add it 92 c.size += cachedValueOverhead + len(item.key) + cap(item.value) 93 c.elements[item.key] = c.accessed.PushFront(item) 94 } 95 } 96 97 // Set takes ownership of item and treats it as immutable 98 func (c *cache) Set(item *cacheItem) { 99 c.lock.Lock() 100 c.setUnderLock(item) 101 c.meetLimitUnderLock() 102 c.lock.Unlock() 103 } 104 105 // SetMulti takes ownership of the individual items and treats them as immutable 106 func (c *cache) SetMulti(items []*cacheItem) { 107 c.lock.Lock() 108 for _, item := range items { 109 c.setUnderLock(item) 110 } 111 c.meetLimitUnderLock() 112 c.lock.Unlock() 113 } 114 115 // deleteExistingUnderLock must be called under cache.lock 116 // The specified element must be non-nil and be guaranteed to exist in the cache 117 func (c *cache) deleteExistingUnderLock(e *list.Element) { 118 ci := e.Value.(*cacheItem) 119 c.size -= cachedValueOverhead + len(ci.key) + cap(ci.value) 120 delete(c.elements, ci.key) 121 c.accessed.Remove(e) 122 } 123 124 // deleteUnderLock must be called under cache.lock 125 func (c *cache) deleteUnderLock(key string) { 126 if e, ok := c.elements[key]; ok { 127 c.deleteExistingUnderLock(e) 128 } 129 } 130 131 func (c *cache) Delete(key string) { 132 c.lock.Lock() 133 c.deleteUnderLock(key) 134 c.lock.Unlock() 135 } 136 137 func (c *cache) DeleteMulti(keys []string) { 138 c.lock.Lock() 139 for _, key := range keys { 140 c.deleteUnderLock(key) 141 } 142 c.lock.Unlock() 143 } 144 145 // getUnderLock must be called under cache.lock 146 func (c *cache) getUnderLock(key string) []byte { 147 if e, ok := c.elements[key]; ok { 148 c.accessed.MoveToFront(e) 149 return (e.Value.(*cacheItem)).value 150 } 151 return nil 152 } 153 154 // The cache retains ownership of the []byte, so consider it immutable 155 func (c *cache) Get(key string) []byte { 156 c.lock.Lock() 157 result := c.getUnderLock(key) 158 c.lock.Unlock() 159 return result 160 } 161 162 // The cache retains ownership of the []byte, so consider it immutable 163 func (c *cache) GetMulti(keys []string) [][]byte { 164 c.lock.Lock() 165 result := make([][]byte, 0, len(keys)) 166 for _, key := range keys { 167 result = append(result, c.getUnderLock(key)) 168 } 169 c.lock.Unlock() 170 return result 171 } 172 173 func (c *cache) Flush() { 174 c.lock.Lock() 175 c.size = 0 176 c.elements = map[string]*list.Element{} 177 c.accessed.Init() 178 c.lock.Unlock() 179 }