github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/karlseguin/ccache/layeredcache.go (about) 1 // An LRU cached aimed at high concurrency 2 package ccache 3 4 import ( 5 "container/list" 6 "hash/fnv" 7 "sync/atomic" 8 "time" 9 ) 10 11 type LayeredCache struct { 12 *Configuration 13 list *list.List 14 buckets []*layeredBucket 15 bucketMask uint32 16 size int64 17 deletables chan *Item 18 promotables chan *Item 19 } 20 21 // Create a new layered cache with the specified configuration. 22 // A layered cache used a two keys to identify a value: a primary key 23 // and a secondary key. Get, Set and Delete require both a primary and 24 // secondary key. However, DeleteAll requires only a primary key, deleting 25 // all values that share the same primary key. 26 27 // Layered Cache is useful as an HTTP cache, where an HTTP purge might 28 // delete multiple variants of the same resource: 29 // primary key = "user/44" 30 // secondary key 1 = ".json" 31 // secondary key 2 = ".xml" 32 33 // See ccache.Configure() for creating a configuration 34 func Layered(config *Configuration) *LayeredCache { 35 c := &LayeredCache{ 36 list: list.New(), 37 Configuration: config, 38 bucketMask: uint32(config.buckets) - 1, 39 buckets: make([]*layeredBucket, config.buckets), 40 deletables: make(chan *Item, config.deleteBuffer), 41 promotables: make(chan *Item, config.promoteBuffer), 42 } 43 for i := 0; i < int(config.buckets); i++ { 44 c.buckets[i] = &layeredBucket{ 45 buckets: make(map[string]*bucket), 46 } 47 } 48 go c.worker() 49 return c 50 } 51 52 // Get an item from the cache. Returns nil if the item wasn't found. 53 // This can return an expired item. Use item.Expired() to see if the item 54 // is expired and item.TTL() to see how long until the item expires (which 55 // will be negative for an already expired item). 56 func (c *LayeredCache) Get(primary, secondary string) *Item { 57 item := c.bucket(primary).get(primary, secondary) 58 if item == nil { 59 return nil 60 } 61 if item.expires > time.Now().Unix() { 62 c.promote(item) 63 } 64 return item 65 } 66 67 // Used when the cache was created with the Track() configuration option. 68 // Avoid otherwise 69 func (c *LayeredCache) TrackingGet(primary, secondary string) TrackedItem { 70 item := c.Get(primary, secondary) 71 if item == nil { 72 return NilTracked 73 } 74 item.track() 75 return item 76 } 77 78 // Set the value in the cache for the specified duration 79 func (c *LayeredCache) Set(primary, secondary string, value interface{}, duration time.Duration) { 80 c.set(primary, secondary, value, duration) 81 } 82 83 // Replace the value if it exists, does not set if it doesn't. 84 // Returns true if the item existed an was replaced, false otherwise. 85 // Replace does not reset item's TTL nor does it alter its position in the LRU 86 func (c *LayeredCache) Replace(primary, secondary string, value interface{}) bool { 87 item := c.bucket(primary).get(primary, secondary) 88 if item == nil { 89 return false 90 } 91 c.Set(primary, secondary, value, item.TTL()) 92 return true 93 } 94 95 // Attempts to get the value from the cache and calles fetch on a miss. 96 // If fetch returns an error, no value is cached and the error is returned back 97 // to the caller. 98 func (c *LayeredCache) Fetch(primary, secondary string, duration time.Duration, fetch func() (interface{}, error)) (interface{}, error) { 99 item := c.Get(primary, secondary) 100 if item != nil { 101 return item, nil 102 } 103 value, err := fetch() 104 if err != nil { 105 return nil, err 106 } 107 return c.set(primary, secondary, value, duration), nil 108 } 109 110 // Remove the item from the cache, return true if the item was present, false otherwise. 111 func (c *LayeredCache) Delete(primary, secondary string) bool { 112 item := c.bucket(primary).delete(primary, secondary) 113 if item != nil { 114 c.deletables <- item 115 return true 116 } 117 return false 118 } 119 120 // Deletes all items that share the same primary key 121 func (c *LayeredCache) DeleteAll(primary string) bool { 122 return c.bucket(primary).deleteAll(primary, c.deletables) 123 } 124 125 //this isn't thread safe. It's meant to be called from non-concurrent tests 126 func (c *LayeredCache) Clear() { 127 for _, bucket := range c.buckets { 128 bucket.clear() 129 } 130 c.size = 0 131 c.list = list.New() 132 } 133 134 func (c *LayeredCache) set(primary, secondary string, value interface{}, duration time.Duration) *Item { 135 item, existing := c.bucket(primary).set(primary, secondary, value, duration) 136 if existing != nil { 137 c.deletables <- existing 138 } 139 c.promote(item) 140 return item 141 } 142 143 func (c *LayeredCache) bucket(key string) *layeredBucket { 144 h := fnv.New32a() 145 h.Write([]byte(key)) 146 return c.buckets[h.Sum32()&c.bucketMask] 147 } 148 149 func (c *LayeredCache) promote(item *Item) { 150 c.promotables <- item 151 } 152 153 func (c *LayeredCache) worker() { 154 for { 155 select { 156 case item := <-c.promotables: 157 if c.doPromote(item) && c.size > c.maxSize { 158 c.gc() 159 } 160 case item := <-c.deletables: 161 if item.element == nil { 162 item.promotions = -2 163 } else { 164 c.size -= item.size 165 c.list.Remove(item.element) 166 } 167 } 168 } 169 } 170 171 func (c *LayeredCache) doPromote(item *Item) bool { 172 // deleted before it ever got promoted 173 if item.promotions == -2 { 174 return false 175 } 176 if item.element != nil { //not a new item 177 if item.shouldPromote(c.getsPerPromote) { 178 c.list.MoveToFront(item.element) 179 item.promotions = 0 180 } 181 return false 182 } 183 c.size += item.size 184 item.element = c.list.PushFront(item) 185 return true 186 } 187 188 func (c *LayeredCache) gc() { 189 element := c.list.Back() 190 for i := 0; i < c.itemsToPrune; i++ { 191 if element == nil { 192 return 193 } 194 prev := element.Prev() 195 item := element.Value.(*Item) 196 if c.tracking == false || atomic.LoadInt32(&item.refCount) == 0 { 197 c.bucket(item.group).delete(item.group, item.key) 198 c.size -= item.size 199 c.list.Remove(element) 200 item.promotions = -2 201 } 202 element = prev 203 } 204 }