├── CONTRIBUTORS ├── LICENSE ├── README.md ├── cache.go ├── cache_test.go ├── sharded.go └── sharded_test.go /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | This is a list of people who have contributed code to go-cache. They, or their 2 | employers, are the copyright holders of the contributed code. Contributed code 3 | is subject to the license restrictions listed in LICENSE (as they were when the 4 | code was contributed.) 5 | 6 | Dustin Sallings 7 | Jason Mooberry 8 | Sergey Shepelev 9 | Alex Edwards 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2019 Patrick Mylund Nielsen and the go-cache contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-cache 2 | 3 | go-cache is an in-memory key:value store/cache similar to memcached that is 4 | suitable for applications running on a single machine. Its major advantage is 5 | that, being essentially a thread-safe `map[string]interface{}` with expiration 6 | times, it doesn't need to serialize or transmit its contents over the network. 7 | 8 | Any object can be stored, for a given duration or forever, and the cache can be 9 | safely used by multiple goroutines. 10 | 11 | Although go-cache isn't meant to be used as a persistent datastore, the entire 12 | cache can be saved to and loaded from a file (using `c.Items()` to retrieve the 13 | items map to serialize, and `NewFrom()` to create a cache from a deserialized 14 | one) to recover from downtime quickly. (See the docs for `NewFrom()` for caveats.) 15 | 16 | ### Installation 17 | 18 | `go get github.com/patrickmn/go-cache` 19 | 20 | ### Usage 21 | 22 | ```go 23 | import ( 24 | "fmt" 25 | "github.com/patrickmn/go-cache" 26 | "time" 27 | ) 28 | 29 | func main() { 30 | // Create a cache with a default expiration time of 5 minutes, and which 31 | // purges expired items every 10 minutes 32 | c := cache.New(5*time.Minute, 10*time.Minute) 33 | 34 | // Set the value of the key "foo" to "bar", with the default expiration time 35 | c.Set("foo", "bar", cache.DefaultExpiration) 36 | 37 | // Set the value of the key "baz" to 42, with no expiration time 38 | // (the item won't be removed until it is re-set, or removed using 39 | // c.Delete("baz") 40 | c.Set("baz", 42, cache.NoExpiration) 41 | 42 | // Get the string associated with the key "foo" from the cache 43 | foo, found := c.Get("foo") 44 | if found { 45 | fmt.Println(foo) 46 | } 47 | 48 | // Since Go is statically typed, and cache values can be anything, type 49 | // assertion is needed when values are being passed to functions that don't 50 | // take arbitrary types, (i.e. interface{}). The simplest way to do this for 51 | // values which will only be used once--e.g. for passing to another 52 | // function--is: 53 | foo, found := c.Get("foo") 54 | if found { 55 | MyFunction(foo.(string)) 56 | } 57 | 58 | // This gets tedious if the value is used several times in the same function. 59 | // You might do either of the following instead: 60 | if x, found := c.Get("foo"); found { 61 | foo := x.(string) 62 | // ... 63 | } 64 | // or 65 | var foo string 66 | if x, found := c.Get("foo"); found { 67 | foo = x.(string) 68 | } 69 | // ... 70 | // foo can then be passed around freely as a string 71 | 72 | // Want performance? Store pointers! 73 | c.Set("foo", &MyStruct, cache.DefaultExpiration) 74 | if x, found := c.Get("foo"); found { 75 | foo := x.(*MyStruct) 76 | // ... 77 | } 78 | } 79 | ``` 80 | 81 | ### Reference 82 | 83 | `godoc` or [http://godoc.org/github.com/patrickmn/go-cache](http://godoc.org/github.com/patrickmn/go-cache) 84 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "io" 7 | "os" 8 | "runtime" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Item struct { 14 | Object interface{} 15 | Expiration int64 16 | } 17 | 18 | // Returns true if the item has expired. 19 | func (item Item) Expired() bool { 20 | if item.Expiration == 0 { 21 | return false 22 | } 23 | return time.Now().UnixNano() > item.Expiration 24 | } 25 | 26 | const ( 27 | // For use with functions that take an expiration time. 28 | NoExpiration time.Duration = -1 29 | // For use with functions that take an expiration time. Equivalent to 30 | // passing in the same expiration duration as was given to New() or 31 | // NewFrom() when the cache was created (e.g. 5 minutes.) 32 | DefaultExpiration time.Duration = 0 33 | ) 34 | 35 | type Cache struct { 36 | *cache 37 | // If this is confusing, see the comment at the bottom of New() 38 | } 39 | 40 | type cache struct { 41 | defaultExpiration time.Duration 42 | items map[string]Item 43 | mu sync.RWMutex 44 | onEvicted func(string, interface{}) 45 | janitor *janitor 46 | } 47 | 48 | // Add an item to the cache, replacing any existing item. If the duration is 0 49 | // (DefaultExpiration), the cache's default expiration time is used. If it is -1 50 | // (NoExpiration), the item never expires. 51 | func (c *cache) Set(k string, x interface{}, d time.Duration) { 52 | // "Inlining" of set 53 | var e int64 54 | if d == DefaultExpiration { 55 | d = c.defaultExpiration 56 | } 57 | if d > 0 { 58 | e = time.Now().Add(d).UnixNano() 59 | } 60 | c.mu.Lock() 61 | c.items[k] = Item{ 62 | Object: x, 63 | Expiration: e, 64 | } 65 | // TODO: Calls to mu.Unlock are currently not deferred because defer 66 | // adds ~200 ns (as of go1.) 67 | c.mu.Unlock() 68 | } 69 | 70 | func (c *cache) set(k string, x interface{}, d time.Duration) { 71 | var e int64 72 | if d == DefaultExpiration { 73 | d = c.defaultExpiration 74 | } 75 | if d > 0 { 76 | e = time.Now().Add(d).UnixNano() 77 | } 78 | c.items[k] = Item{ 79 | Object: x, 80 | Expiration: e, 81 | } 82 | } 83 | 84 | // Add an item to the cache, replacing any existing item, using the default 85 | // expiration. 86 | func (c *cache) SetDefault(k string, x interface{}) { 87 | c.Set(k, x, DefaultExpiration) 88 | } 89 | 90 | // Add an item to the cache only if an item doesn't already exist for the given 91 | // key, or if the existing item has expired. Returns an error otherwise. 92 | func (c *cache) Add(k string, x interface{}, d time.Duration) error { 93 | c.mu.Lock() 94 | _, found := c.get(k) 95 | if found { 96 | c.mu.Unlock() 97 | return fmt.Errorf("Item %s already exists", k) 98 | } 99 | c.set(k, x, d) 100 | c.mu.Unlock() 101 | return nil 102 | } 103 | 104 | // Set a new value for the cache key only if it already exists, and the existing 105 | // item hasn't expired. Returns an error otherwise. 106 | func (c *cache) Replace(k string, x interface{}, d time.Duration) error { 107 | c.mu.Lock() 108 | _, found := c.get(k) 109 | if !found { 110 | c.mu.Unlock() 111 | return fmt.Errorf("Item %s doesn't exist", k) 112 | } 113 | c.set(k, x, d) 114 | c.mu.Unlock() 115 | return nil 116 | } 117 | 118 | // Get an item from the cache. Returns the item or nil, and a bool indicating 119 | // whether the key was found. 120 | func (c *cache) Get(k string) (interface{}, bool) { 121 | c.mu.RLock() 122 | // "Inlining" of get and Expired 123 | item, found := c.items[k] 124 | if !found { 125 | c.mu.RUnlock() 126 | return nil, false 127 | } 128 | if item.Expiration > 0 { 129 | if time.Now().UnixNano() > item.Expiration { 130 | c.mu.RUnlock() 131 | return nil, false 132 | } 133 | } 134 | c.mu.RUnlock() 135 | return item.Object, true 136 | } 137 | 138 | // GetWithExpiration returns an item and its expiration time from the cache. 139 | // It returns the item or nil, the expiration time if one is set (if the item 140 | // never expires a zero value for time.Time is returned), and a bool indicating 141 | // whether the key was found. 142 | func (c *cache) GetWithExpiration(k string) (interface{}, time.Time, bool) { 143 | c.mu.RLock() 144 | // "Inlining" of get and Expired 145 | item, found := c.items[k] 146 | if !found { 147 | c.mu.RUnlock() 148 | return nil, time.Time{}, false 149 | } 150 | 151 | if item.Expiration > 0 { 152 | if time.Now().UnixNano() > item.Expiration { 153 | c.mu.RUnlock() 154 | return nil, time.Time{}, false 155 | } 156 | 157 | // Return the item and the expiration time 158 | c.mu.RUnlock() 159 | return item.Object, time.Unix(0, item.Expiration), true 160 | } 161 | 162 | // If expiration <= 0 (i.e. no expiration time set) then return the item 163 | // and a zeroed time.Time 164 | c.mu.RUnlock() 165 | return item.Object, time.Time{}, true 166 | } 167 | 168 | func (c *cache) get(k string) (interface{}, bool) { 169 | item, found := c.items[k] 170 | if !found { 171 | return nil, false 172 | } 173 | // "Inlining" of Expired 174 | if item.Expiration > 0 { 175 | if time.Now().UnixNano() > item.Expiration { 176 | return nil, false 177 | } 178 | } 179 | return item.Object, true 180 | } 181 | 182 | // Increment an item of type int, int8, int16, int32, int64, uintptr, uint, 183 | // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the 184 | // item's value is not an integer, if it was not found, or if it is not 185 | // possible to increment it by n. To retrieve the incremented value, use one 186 | // of the specialized methods, e.g. IncrementInt64. 187 | func (c *cache) Increment(k string, n int64) error { 188 | c.mu.Lock() 189 | v, found := c.items[k] 190 | if !found || v.Expired() { 191 | c.mu.Unlock() 192 | return fmt.Errorf("Item %s not found", k) 193 | } 194 | switch v.Object.(type) { 195 | case int: 196 | v.Object = v.Object.(int) + int(n) 197 | case int8: 198 | v.Object = v.Object.(int8) + int8(n) 199 | case int16: 200 | v.Object = v.Object.(int16) + int16(n) 201 | case int32: 202 | v.Object = v.Object.(int32) + int32(n) 203 | case int64: 204 | v.Object = v.Object.(int64) + n 205 | case uint: 206 | v.Object = v.Object.(uint) + uint(n) 207 | case uintptr: 208 | v.Object = v.Object.(uintptr) + uintptr(n) 209 | case uint8: 210 | v.Object = v.Object.(uint8) + uint8(n) 211 | case uint16: 212 | v.Object = v.Object.(uint16) + uint16(n) 213 | case uint32: 214 | v.Object = v.Object.(uint32) + uint32(n) 215 | case uint64: 216 | v.Object = v.Object.(uint64) + uint64(n) 217 | case float32: 218 | v.Object = v.Object.(float32) + float32(n) 219 | case float64: 220 | v.Object = v.Object.(float64) + float64(n) 221 | default: 222 | c.mu.Unlock() 223 | return fmt.Errorf("The value for %s is not an integer", k) 224 | } 225 | c.items[k] = v 226 | c.mu.Unlock() 227 | return nil 228 | } 229 | 230 | // Increment an item of type float32 or float64 by n. Returns an error if the 231 | // item's value is not floating point, if it was not found, or if it is not 232 | // possible to increment it by n. Pass a negative number to decrement the 233 | // value. To retrieve the incremented value, use one of the specialized methods, 234 | // e.g. IncrementFloat64. 235 | func (c *cache) IncrementFloat(k string, n float64) error { 236 | c.mu.Lock() 237 | v, found := c.items[k] 238 | if !found || v.Expired() { 239 | c.mu.Unlock() 240 | return fmt.Errorf("Item %s not found", k) 241 | } 242 | switch v.Object.(type) { 243 | case float32: 244 | v.Object = v.Object.(float32) + float32(n) 245 | case float64: 246 | v.Object = v.Object.(float64) + n 247 | default: 248 | c.mu.Unlock() 249 | return fmt.Errorf("The value for %s does not have type float32 or float64", k) 250 | } 251 | c.items[k] = v 252 | c.mu.Unlock() 253 | return nil 254 | } 255 | 256 | // Increment an item of type int by n. Returns an error if the item's value is 257 | // not an int, or if it was not found. If there is no error, the incremented 258 | // value is returned. 259 | func (c *cache) IncrementInt(k string, n int) (int, error) { 260 | c.mu.Lock() 261 | v, found := c.items[k] 262 | if !found || v.Expired() { 263 | c.mu.Unlock() 264 | return 0, fmt.Errorf("Item %s not found", k) 265 | } 266 | rv, ok := v.Object.(int) 267 | if !ok { 268 | c.mu.Unlock() 269 | return 0, fmt.Errorf("The value for %s is not an int", k) 270 | } 271 | nv := rv + n 272 | v.Object = nv 273 | c.items[k] = v 274 | c.mu.Unlock() 275 | return nv, nil 276 | } 277 | 278 | // Increment an item of type int8 by n. Returns an error if the item's value is 279 | // not an int8, or if it was not found. If there is no error, the incremented 280 | // value is returned. 281 | func (c *cache) IncrementInt8(k string, n int8) (int8, error) { 282 | c.mu.Lock() 283 | v, found := c.items[k] 284 | if !found || v.Expired() { 285 | c.mu.Unlock() 286 | return 0, fmt.Errorf("Item %s not found", k) 287 | } 288 | rv, ok := v.Object.(int8) 289 | if !ok { 290 | c.mu.Unlock() 291 | return 0, fmt.Errorf("The value for %s is not an int8", k) 292 | } 293 | nv := rv + n 294 | v.Object = nv 295 | c.items[k] = v 296 | c.mu.Unlock() 297 | return nv, nil 298 | } 299 | 300 | // Increment an item of type int16 by n. Returns an error if the item's value is 301 | // not an int16, or if it was not found. If there is no error, the incremented 302 | // value is returned. 303 | func (c *cache) IncrementInt16(k string, n int16) (int16, error) { 304 | c.mu.Lock() 305 | v, found := c.items[k] 306 | if !found || v.Expired() { 307 | c.mu.Unlock() 308 | return 0, fmt.Errorf("Item %s not found", k) 309 | } 310 | rv, ok := v.Object.(int16) 311 | if !ok { 312 | c.mu.Unlock() 313 | return 0, fmt.Errorf("The value for %s is not an int16", k) 314 | } 315 | nv := rv + n 316 | v.Object = nv 317 | c.items[k] = v 318 | c.mu.Unlock() 319 | return nv, nil 320 | } 321 | 322 | // Increment an item of type int32 by n. Returns an error if the item's value is 323 | // not an int32, or if it was not found. If there is no error, the incremented 324 | // value is returned. 325 | func (c *cache) IncrementInt32(k string, n int32) (int32, error) { 326 | c.mu.Lock() 327 | v, found := c.items[k] 328 | if !found || v.Expired() { 329 | c.mu.Unlock() 330 | return 0, fmt.Errorf("Item %s not found", k) 331 | } 332 | rv, ok := v.Object.(int32) 333 | if !ok { 334 | c.mu.Unlock() 335 | return 0, fmt.Errorf("The value for %s is not an int32", k) 336 | } 337 | nv := rv + n 338 | v.Object = nv 339 | c.items[k] = v 340 | c.mu.Unlock() 341 | return nv, nil 342 | } 343 | 344 | // Increment an item of type int64 by n. Returns an error if the item's value is 345 | // not an int64, or if it was not found. If there is no error, the incremented 346 | // value is returned. 347 | func (c *cache) IncrementInt64(k string, n int64) (int64, error) { 348 | c.mu.Lock() 349 | v, found := c.items[k] 350 | if !found || v.Expired() { 351 | c.mu.Unlock() 352 | return 0, fmt.Errorf("Item %s not found", k) 353 | } 354 | rv, ok := v.Object.(int64) 355 | if !ok { 356 | c.mu.Unlock() 357 | return 0, fmt.Errorf("The value for %s is not an int64", k) 358 | } 359 | nv := rv + n 360 | v.Object = nv 361 | c.items[k] = v 362 | c.mu.Unlock() 363 | return nv, nil 364 | } 365 | 366 | // Increment an item of type uint by n. Returns an error if the item's value is 367 | // not an uint, or if it was not found. If there is no error, the incremented 368 | // value is returned. 369 | func (c *cache) IncrementUint(k string, n uint) (uint, error) { 370 | c.mu.Lock() 371 | v, found := c.items[k] 372 | if !found || v.Expired() { 373 | c.mu.Unlock() 374 | return 0, fmt.Errorf("Item %s not found", k) 375 | } 376 | rv, ok := v.Object.(uint) 377 | if !ok { 378 | c.mu.Unlock() 379 | return 0, fmt.Errorf("The value for %s is not an uint", k) 380 | } 381 | nv := rv + n 382 | v.Object = nv 383 | c.items[k] = v 384 | c.mu.Unlock() 385 | return nv, nil 386 | } 387 | 388 | // Increment an item of type uintptr by n. Returns an error if the item's value 389 | // is not an uintptr, or if it was not found. If there is no error, the 390 | // incremented value is returned. 391 | func (c *cache) IncrementUintptr(k string, n uintptr) (uintptr, error) { 392 | c.mu.Lock() 393 | v, found := c.items[k] 394 | if !found || v.Expired() { 395 | c.mu.Unlock() 396 | return 0, fmt.Errorf("Item %s not found", k) 397 | } 398 | rv, ok := v.Object.(uintptr) 399 | if !ok { 400 | c.mu.Unlock() 401 | return 0, fmt.Errorf("The value for %s is not an uintptr", k) 402 | } 403 | nv := rv + n 404 | v.Object = nv 405 | c.items[k] = v 406 | c.mu.Unlock() 407 | return nv, nil 408 | } 409 | 410 | // Increment an item of type uint8 by n. Returns an error if the item's value 411 | // is not an uint8, or if it was not found. If there is no error, the 412 | // incremented value is returned. 413 | func (c *cache) IncrementUint8(k string, n uint8) (uint8, error) { 414 | c.mu.Lock() 415 | v, found := c.items[k] 416 | if !found || v.Expired() { 417 | c.mu.Unlock() 418 | return 0, fmt.Errorf("Item %s not found", k) 419 | } 420 | rv, ok := v.Object.(uint8) 421 | if !ok { 422 | c.mu.Unlock() 423 | return 0, fmt.Errorf("The value for %s is not an uint8", k) 424 | } 425 | nv := rv + n 426 | v.Object = nv 427 | c.items[k] = v 428 | c.mu.Unlock() 429 | return nv, nil 430 | } 431 | 432 | // Increment an item of type uint16 by n. Returns an error if the item's value 433 | // is not an uint16, or if it was not found. If there is no error, the 434 | // incremented value is returned. 435 | func (c *cache) IncrementUint16(k string, n uint16) (uint16, error) { 436 | c.mu.Lock() 437 | v, found := c.items[k] 438 | if !found || v.Expired() { 439 | c.mu.Unlock() 440 | return 0, fmt.Errorf("Item %s not found", k) 441 | } 442 | rv, ok := v.Object.(uint16) 443 | if !ok { 444 | c.mu.Unlock() 445 | return 0, fmt.Errorf("The value for %s is not an uint16", k) 446 | } 447 | nv := rv + n 448 | v.Object = nv 449 | c.items[k] = v 450 | c.mu.Unlock() 451 | return nv, nil 452 | } 453 | 454 | // Increment an item of type uint32 by n. Returns an error if the item's value 455 | // is not an uint32, or if it was not found. If there is no error, the 456 | // incremented value is returned. 457 | func (c *cache) IncrementUint32(k string, n uint32) (uint32, error) { 458 | c.mu.Lock() 459 | v, found := c.items[k] 460 | if !found || v.Expired() { 461 | c.mu.Unlock() 462 | return 0, fmt.Errorf("Item %s not found", k) 463 | } 464 | rv, ok := v.Object.(uint32) 465 | if !ok { 466 | c.mu.Unlock() 467 | return 0, fmt.Errorf("The value for %s is not an uint32", k) 468 | } 469 | nv := rv + n 470 | v.Object = nv 471 | c.items[k] = v 472 | c.mu.Unlock() 473 | return nv, nil 474 | } 475 | 476 | // Increment an item of type uint64 by n. Returns an error if the item's value 477 | // is not an uint64, or if it was not found. If there is no error, the 478 | // incremented value is returned. 479 | func (c *cache) IncrementUint64(k string, n uint64) (uint64, error) { 480 | c.mu.Lock() 481 | v, found := c.items[k] 482 | if !found || v.Expired() { 483 | c.mu.Unlock() 484 | return 0, fmt.Errorf("Item %s not found", k) 485 | } 486 | rv, ok := v.Object.(uint64) 487 | if !ok { 488 | c.mu.Unlock() 489 | return 0, fmt.Errorf("The value for %s is not an uint64", k) 490 | } 491 | nv := rv + n 492 | v.Object = nv 493 | c.items[k] = v 494 | c.mu.Unlock() 495 | return nv, nil 496 | } 497 | 498 | // Increment an item of type float32 by n. Returns an error if the item's value 499 | // is not an float32, or if it was not found. If there is no error, the 500 | // incremented value is returned. 501 | func (c *cache) IncrementFloat32(k string, n float32) (float32, error) { 502 | c.mu.Lock() 503 | v, found := c.items[k] 504 | if !found || v.Expired() { 505 | c.mu.Unlock() 506 | return 0, fmt.Errorf("Item %s not found", k) 507 | } 508 | rv, ok := v.Object.(float32) 509 | if !ok { 510 | c.mu.Unlock() 511 | return 0, fmt.Errorf("The value for %s is not an float32", k) 512 | } 513 | nv := rv + n 514 | v.Object = nv 515 | c.items[k] = v 516 | c.mu.Unlock() 517 | return nv, nil 518 | } 519 | 520 | // Increment an item of type float64 by n. Returns an error if the item's value 521 | // is not an float64, or if it was not found. If there is no error, the 522 | // incremented value is returned. 523 | func (c *cache) IncrementFloat64(k string, n float64) (float64, error) { 524 | c.mu.Lock() 525 | v, found := c.items[k] 526 | if !found || v.Expired() { 527 | c.mu.Unlock() 528 | return 0, fmt.Errorf("Item %s not found", k) 529 | } 530 | rv, ok := v.Object.(float64) 531 | if !ok { 532 | c.mu.Unlock() 533 | return 0, fmt.Errorf("The value for %s is not an float64", k) 534 | } 535 | nv := rv + n 536 | v.Object = nv 537 | c.items[k] = v 538 | c.mu.Unlock() 539 | return nv, nil 540 | } 541 | 542 | // Decrement an item of type int, int8, int16, int32, int64, uintptr, uint, 543 | // uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the 544 | // item's value is not an integer, if it was not found, or if it is not 545 | // possible to decrement it by n. To retrieve the decremented value, use one 546 | // of the specialized methods, e.g. DecrementInt64. 547 | func (c *cache) Decrement(k string, n int64) error { 548 | // TODO: Implement Increment and Decrement more cleanly. 549 | // (Cannot do Increment(k, n*-1) for uints.) 550 | c.mu.Lock() 551 | v, found := c.items[k] 552 | if !found || v.Expired() { 553 | c.mu.Unlock() 554 | return fmt.Errorf("Item not found") 555 | } 556 | switch v.Object.(type) { 557 | case int: 558 | v.Object = v.Object.(int) - int(n) 559 | case int8: 560 | v.Object = v.Object.(int8) - int8(n) 561 | case int16: 562 | v.Object = v.Object.(int16) - int16(n) 563 | case int32: 564 | v.Object = v.Object.(int32) - int32(n) 565 | case int64: 566 | v.Object = v.Object.(int64) - n 567 | case uint: 568 | v.Object = v.Object.(uint) - uint(n) 569 | case uintptr: 570 | v.Object = v.Object.(uintptr) - uintptr(n) 571 | case uint8: 572 | v.Object = v.Object.(uint8) - uint8(n) 573 | case uint16: 574 | v.Object = v.Object.(uint16) - uint16(n) 575 | case uint32: 576 | v.Object = v.Object.(uint32) - uint32(n) 577 | case uint64: 578 | v.Object = v.Object.(uint64) - uint64(n) 579 | case float32: 580 | v.Object = v.Object.(float32) - float32(n) 581 | case float64: 582 | v.Object = v.Object.(float64) - float64(n) 583 | default: 584 | c.mu.Unlock() 585 | return fmt.Errorf("The value for %s is not an integer", k) 586 | } 587 | c.items[k] = v 588 | c.mu.Unlock() 589 | return nil 590 | } 591 | 592 | // Decrement an item of type float32 or float64 by n. Returns an error if the 593 | // item's value is not floating point, if it was not found, or if it is not 594 | // possible to decrement it by n. Pass a negative number to decrement the 595 | // value. To retrieve the decremented value, use one of the specialized methods, 596 | // e.g. DecrementFloat64. 597 | func (c *cache) DecrementFloat(k string, n float64) error { 598 | c.mu.Lock() 599 | v, found := c.items[k] 600 | if !found || v.Expired() { 601 | c.mu.Unlock() 602 | return fmt.Errorf("Item %s not found", k) 603 | } 604 | switch v.Object.(type) { 605 | case float32: 606 | v.Object = v.Object.(float32) - float32(n) 607 | case float64: 608 | v.Object = v.Object.(float64) - n 609 | default: 610 | c.mu.Unlock() 611 | return fmt.Errorf("The value for %s does not have type float32 or float64", k) 612 | } 613 | c.items[k] = v 614 | c.mu.Unlock() 615 | return nil 616 | } 617 | 618 | // Decrement an item of type int by n. Returns an error if the item's value is 619 | // not an int, or if it was not found. If there is no error, the decremented 620 | // value is returned. 621 | func (c *cache) DecrementInt(k string, n int) (int, error) { 622 | c.mu.Lock() 623 | v, found := c.items[k] 624 | if !found || v.Expired() { 625 | c.mu.Unlock() 626 | return 0, fmt.Errorf("Item %s not found", k) 627 | } 628 | rv, ok := v.Object.(int) 629 | if !ok { 630 | c.mu.Unlock() 631 | return 0, fmt.Errorf("The value for %s is not an int", k) 632 | } 633 | nv := rv - n 634 | v.Object = nv 635 | c.items[k] = v 636 | c.mu.Unlock() 637 | return nv, nil 638 | } 639 | 640 | // Decrement an item of type int8 by n. Returns an error if the item's value is 641 | // not an int8, or if it was not found. If there is no error, the decremented 642 | // value is returned. 643 | func (c *cache) DecrementInt8(k string, n int8) (int8, error) { 644 | c.mu.Lock() 645 | v, found := c.items[k] 646 | if !found || v.Expired() { 647 | c.mu.Unlock() 648 | return 0, fmt.Errorf("Item %s not found", k) 649 | } 650 | rv, ok := v.Object.(int8) 651 | if !ok { 652 | c.mu.Unlock() 653 | return 0, fmt.Errorf("The value for %s is not an int8", k) 654 | } 655 | nv := rv - n 656 | v.Object = nv 657 | c.items[k] = v 658 | c.mu.Unlock() 659 | return nv, nil 660 | } 661 | 662 | // Decrement an item of type int16 by n. Returns an error if the item's value is 663 | // not an int16, or if it was not found. If there is no error, the decremented 664 | // value is returned. 665 | func (c *cache) DecrementInt16(k string, n int16) (int16, error) { 666 | c.mu.Lock() 667 | v, found := c.items[k] 668 | if !found || v.Expired() { 669 | c.mu.Unlock() 670 | return 0, fmt.Errorf("Item %s not found", k) 671 | } 672 | rv, ok := v.Object.(int16) 673 | if !ok { 674 | c.mu.Unlock() 675 | return 0, fmt.Errorf("The value for %s is not an int16", k) 676 | } 677 | nv := rv - n 678 | v.Object = nv 679 | c.items[k] = v 680 | c.mu.Unlock() 681 | return nv, nil 682 | } 683 | 684 | // Decrement an item of type int32 by n. Returns an error if the item's value is 685 | // not an int32, or if it was not found. If there is no error, the decremented 686 | // value is returned. 687 | func (c *cache) DecrementInt32(k string, n int32) (int32, error) { 688 | c.mu.Lock() 689 | v, found := c.items[k] 690 | if !found || v.Expired() { 691 | c.mu.Unlock() 692 | return 0, fmt.Errorf("Item %s not found", k) 693 | } 694 | rv, ok := v.Object.(int32) 695 | if !ok { 696 | c.mu.Unlock() 697 | return 0, fmt.Errorf("The value for %s is not an int32", k) 698 | } 699 | nv := rv - n 700 | v.Object = nv 701 | c.items[k] = v 702 | c.mu.Unlock() 703 | return nv, nil 704 | } 705 | 706 | // Decrement an item of type int64 by n. Returns an error if the item's value is 707 | // not an int64, or if it was not found. If there is no error, the decremented 708 | // value is returned. 709 | func (c *cache) DecrementInt64(k string, n int64) (int64, error) { 710 | c.mu.Lock() 711 | v, found := c.items[k] 712 | if !found || v.Expired() { 713 | c.mu.Unlock() 714 | return 0, fmt.Errorf("Item %s not found", k) 715 | } 716 | rv, ok := v.Object.(int64) 717 | if !ok { 718 | c.mu.Unlock() 719 | return 0, fmt.Errorf("The value for %s is not an int64", k) 720 | } 721 | nv := rv - n 722 | v.Object = nv 723 | c.items[k] = v 724 | c.mu.Unlock() 725 | return nv, nil 726 | } 727 | 728 | // Decrement an item of type uint by n. Returns an error if the item's value is 729 | // not an uint, or if it was not found. If there is no error, the decremented 730 | // value is returned. 731 | func (c *cache) DecrementUint(k string, n uint) (uint, error) { 732 | c.mu.Lock() 733 | v, found := c.items[k] 734 | if !found || v.Expired() { 735 | c.mu.Unlock() 736 | return 0, fmt.Errorf("Item %s not found", k) 737 | } 738 | rv, ok := v.Object.(uint) 739 | if !ok { 740 | c.mu.Unlock() 741 | return 0, fmt.Errorf("The value for %s is not an uint", k) 742 | } 743 | nv := rv - n 744 | v.Object = nv 745 | c.items[k] = v 746 | c.mu.Unlock() 747 | return nv, nil 748 | } 749 | 750 | // Decrement an item of type uintptr by n. Returns an error if the item's value 751 | // is not an uintptr, or if it was not found. If there is no error, the 752 | // decremented value is returned. 753 | func (c *cache) DecrementUintptr(k string, n uintptr) (uintptr, error) { 754 | c.mu.Lock() 755 | v, found := c.items[k] 756 | if !found || v.Expired() { 757 | c.mu.Unlock() 758 | return 0, fmt.Errorf("Item %s not found", k) 759 | } 760 | rv, ok := v.Object.(uintptr) 761 | if !ok { 762 | c.mu.Unlock() 763 | return 0, fmt.Errorf("The value for %s is not an uintptr", k) 764 | } 765 | nv := rv - n 766 | v.Object = nv 767 | c.items[k] = v 768 | c.mu.Unlock() 769 | return nv, nil 770 | } 771 | 772 | // Decrement an item of type uint8 by n. Returns an error if the item's value is 773 | // not an uint8, or if it was not found. If there is no error, the decremented 774 | // value is returned. 775 | func (c *cache) DecrementUint8(k string, n uint8) (uint8, error) { 776 | c.mu.Lock() 777 | v, found := c.items[k] 778 | if !found || v.Expired() { 779 | c.mu.Unlock() 780 | return 0, fmt.Errorf("Item %s not found", k) 781 | } 782 | rv, ok := v.Object.(uint8) 783 | if !ok { 784 | c.mu.Unlock() 785 | return 0, fmt.Errorf("The value for %s is not an uint8", k) 786 | } 787 | nv := rv - n 788 | v.Object = nv 789 | c.items[k] = v 790 | c.mu.Unlock() 791 | return nv, nil 792 | } 793 | 794 | // Decrement an item of type uint16 by n. Returns an error if the item's value 795 | // is not an uint16, or if it was not found. If there is no error, the 796 | // decremented value is returned. 797 | func (c *cache) DecrementUint16(k string, n uint16) (uint16, error) { 798 | c.mu.Lock() 799 | v, found := c.items[k] 800 | if !found || v.Expired() { 801 | c.mu.Unlock() 802 | return 0, fmt.Errorf("Item %s not found", k) 803 | } 804 | rv, ok := v.Object.(uint16) 805 | if !ok { 806 | c.mu.Unlock() 807 | return 0, fmt.Errorf("The value for %s is not an uint16", k) 808 | } 809 | nv := rv - n 810 | v.Object = nv 811 | c.items[k] = v 812 | c.mu.Unlock() 813 | return nv, nil 814 | } 815 | 816 | // Decrement an item of type uint32 by n. Returns an error if the item's value 817 | // is not an uint32, or if it was not found. If there is no error, the 818 | // decremented value is returned. 819 | func (c *cache) DecrementUint32(k string, n uint32) (uint32, error) { 820 | c.mu.Lock() 821 | v, found := c.items[k] 822 | if !found || v.Expired() { 823 | c.mu.Unlock() 824 | return 0, fmt.Errorf("Item %s not found", k) 825 | } 826 | rv, ok := v.Object.(uint32) 827 | if !ok { 828 | c.mu.Unlock() 829 | return 0, fmt.Errorf("The value for %s is not an uint32", k) 830 | } 831 | nv := rv - n 832 | v.Object = nv 833 | c.items[k] = v 834 | c.mu.Unlock() 835 | return nv, nil 836 | } 837 | 838 | // Decrement an item of type uint64 by n. Returns an error if the item's value 839 | // is not an uint64, or if it was not found. If there is no error, the 840 | // decremented value is returned. 841 | func (c *cache) DecrementUint64(k string, n uint64) (uint64, error) { 842 | c.mu.Lock() 843 | v, found := c.items[k] 844 | if !found || v.Expired() { 845 | c.mu.Unlock() 846 | return 0, fmt.Errorf("Item %s not found", k) 847 | } 848 | rv, ok := v.Object.(uint64) 849 | if !ok { 850 | c.mu.Unlock() 851 | return 0, fmt.Errorf("The value for %s is not an uint64", k) 852 | } 853 | nv := rv - n 854 | v.Object = nv 855 | c.items[k] = v 856 | c.mu.Unlock() 857 | return nv, nil 858 | } 859 | 860 | // Decrement an item of type float32 by n. Returns an error if the item's value 861 | // is not an float32, or if it was not found. If there is no error, the 862 | // decremented value is returned. 863 | func (c *cache) DecrementFloat32(k string, n float32) (float32, error) { 864 | c.mu.Lock() 865 | v, found := c.items[k] 866 | if !found || v.Expired() { 867 | c.mu.Unlock() 868 | return 0, fmt.Errorf("Item %s not found", k) 869 | } 870 | rv, ok := v.Object.(float32) 871 | if !ok { 872 | c.mu.Unlock() 873 | return 0, fmt.Errorf("The value for %s is not an float32", k) 874 | } 875 | nv := rv - n 876 | v.Object = nv 877 | c.items[k] = v 878 | c.mu.Unlock() 879 | return nv, nil 880 | } 881 | 882 | // Decrement an item of type float64 by n. Returns an error if the item's value 883 | // is not an float64, or if it was not found. If there is no error, the 884 | // decremented value is returned. 885 | func (c *cache) DecrementFloat64(k string, n float64) (float64, error) { 886 | c.mu.Lock() 887 | v, found := c.items[k] 888 | if !found || v.Expired() { 889 | c.mu.Unlock() 890 | return 0, fmt.Errorf("Item %s not found", k) 891 | } 892 | rv, ok := v.Object.(float64) 893 | if !ok { 894 | c.mu.Unlock() 895 | return 0, fmt.Errorf("The value for %s is not an float64", k) 896 | } 897 | nv := rv - n 898 | v.Object = nv 899 | c.items[k] = v 900 | c.mu.Unlock() 901 | return nv, nil 902 | } 903 | 904 | // Delete an item from the cache. Does nothing if the key is not in the cache. 905 | func (c *cache) Delete(k string) { 906 | c.mu.Lock() 907 | v, evicted := c.delete(k) 908 | c.mu.Unlock() 909 | if evicted { 910 | c.onEvicted(k, v) 911 | } 912 | } 913 | 914 | func (c *cache) delete(k string) (interface{}, bool) { 915 | if c.onEvicted != nil { 916 | if v, found := c.items[k]; found { 917 | delete(c.items, k) 918 | return v.Object, true 919 | } 920 | } 921 | delete(c.items, k) 922 | return nil, false 923 | } 924 | 925 | type keyAndValue struct { 926 | key string 927 | value interface{} 928 | } 929 | 930 | // Delete all expired items from the cache. 931 | func (c *cache) DeleteExpired() { 932 | var evictedItems []keyAndValue 933 | now := time.Now().UnixNano() 934 | c.mu.Lock() 935 | for k, v := range c.items { 936 | // "Inlining" of expired 937 | if v.Expiration > 0 && now > v.Expiration { 938 | ov, evicted := c.delete(k) 939 | if evicted { 940 | evictedItems = append(evictedItems, keyAndValue{k, ov}) 941 | } 942 | } 943 | } 944 | c.mu.Unlock() 945 | for _, v := range evictedItems { 946 | c.onEvicted(v.key, v.value) 947 | } 948 | } 949 | 950 | // Sets an (optional) function that is called with the key and value when an 951 | // item is evicted from the cache. (Including when it is deleted manually, but 952 | // not when it is overwritten.) Set to nil to disable. 953 | func (c *cache) OnEvicted(f func(string, interface{})) { 954 | c.mu.Lock() 955 | c.onEvicted = f 956 | c.mu.Unlock() 957 | } 958 | 959 | // Write the cache's items (using Gob) to an io.Writer. 960 | // 961 | // NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the 962 | // documentation for NewFrom().) 963 | func (c *cache) Save(w io.Writer) (err error) { 964 | enc := gob.NewEncoder(w) 965 | defer func() { 966 | if x := recover(); x != nil { 967 | err = fmt.Errorf("Error registering item types with Gob library") 968 | } 969 | }() 970 | c.mu.RLock() 971 | defer c.mu.RUnlock() 972 | for _, v := range c.items { 973 | gob.Register(v.Object) 974 | } 975 | err = enc.Encode(&c.items) 976 | return 977 | } 978 | 979 | // Save the cache's items to the given filename, creating the file if it 980 | // doesn't exist, and overwriting it if it does. 981 | // 982 | // NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the 983 | // documentation for NewFrom().) 984 | func (c *cache) SaveFile(fname string) error { 985 | fp, err := os.Create(fname) 986 | if err != nil { 987 | return err 988 | } 989 | err = c.Save(fp) 990 | if err != nil { 991 | fp.Close() 992 | return err 993 | } 994 | return fp.Close() 995 | } 996 | 997 | // Add (Gob-serialized) cache items from an io.Reader, excluding any items with 998 | // keys that already exist (and haven't expired) in the current cache. 999 | // 1000 | // NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the 1001 | // documentation for NewFrom().) 1002 | func (c *cache) Load(r io.Reader) error { 1003 | dec := gob.NewDecoder(r) 1004 | items := map[string]Item{} 1005 | err := dec.Decode(&items) 1006 | if err == nil { 1007 | c.mu.Lock() 1008 | defer c.mu.Unlock() 1009 | for k, v := range items { 1010 | ov, found := c.items[k] 1011 | if !found || ov.Expired() { 1012 | c.items[k] = v 1013 | } 1014 | } 1015 | } 1016 | return err 1017 | } 1018 | 1019 | // Load and add cache items from the given filename, excluding any items with 1020 | // keys that already exist in the current cache. 1021 | // 1022 | // NOTE: This method is deprecated in favor of c.Items() and NewFrom() (see the 1023 | // documentation for NewFrom().) 1024 | func (c *cache) LoadFile(fname string) error { 1025 | fp, err := os.Open(fname) 1026 | if err != nil { 1027 | return err 1028 | } 1029 | err = c.Load(fp) 1030 | if err != nil { 1031 | fp.Close() 1032 | return err 1033 | } 1034 | return fp.Close() 1035 | } 1036 | 1037 | // Copies all unexpired items in the cache into a new map and returns it. 1038 | func (c *cache) Items() map[string]Item { 1039 | c.mu.RLock() 1040 | defer c.mu.RUnlock() 1041 | m := make(map[string]Item, len(c.items)) 1042 | now := time.Now().UnixNano() 1043 | for k, v := range c.items { 1044 | // "Inlining" of Expired 1045 | if v.Expiration > 0 { 1046 | if now > v.Expiration { 1047 | continue 1048 | } 1049 | } 1050 | m[k] = v 1051 | } 1052 | return m 1053 | } 1054 | 1055 | // Returns the number of items in the cache. This may include items that have 1056 | // expired, but have not yet been cleaned up. 1057 | func (c *cache) ItemCount() int { 1058 | c.mu.RLock() 1059 | n := len(c.items) 1060 | c.mu.RUnlock() 1061 | return n 1062 | } 1063 | 1064 | // Delete all items from the cache. 1065 | func (c *cache) Flush() { 1066 | c.mu.Lock() 1067 | c.items = map[string]Item{} 1068 | c.mu.Unlock() 1069 | } 1070 | 1071 | type janitor struct { 1072 | Interval time.Duration 1073 | stop chan bool 1074 | } 1075 | 1076 | func (j *janitor) Run(c *cache) { 1077 | ticker := time.NewTicker(j.Interval) 1078 | for { 1079 | select { 1080 | case <-ticker.C: 1081 | c.DeleteExpired() 1082 | case <-j.stop: 1083 | ticker.Stop() 1084 | return 1085 | } 1086 | } 1087 | } 1088 | 1089 | func stopJanitor(c *Cache) { 1090 | c.janitor.stop <- true 1091 | } 1092 | 1093 | func runJanitor(c *cache, ci time.Duration) { 1094 | j := &janitor{ 1095 | Interval: ci, 1096 | stop: make(chan bool), 1097 | } 1098 | c.janitor = j 1099 | go j.Run(c) 1100 | } 1101 | 1102 | func newCache(de time.Duration, m map[string]Item) *cache { 1103 | if de == 0 { 1104 | de = -1 1105 | } 1106 | c := &cache{ 1107 | defaultExpiration: de, 1108 | items: m, 1109 | } 1110 | return c 1111 | } 1112 | 1113 | func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache { 1114 | c := newCache(de, m) 1115 | // This trick ensures that the janitor goroutine (which--granted it 1116 | // was enabled--is running DeleteExpired on c forever) does not keep 1117 | // the returned C object from being garbage collected. When it is 1118 | // garbage collected, the finalizer stops the janitor goroutine, after 1119 | // which c can be collected. 1120 | C := &Cache{c} 1121 | if ci > 0 { 1122 | runJanitor(c, ci) 1123 | runtime.SetFinalizer(C, stopJanitor) 1124 | } 1125 | return C 1126 | } 1127 | 1128 | // Return a new cache with a given default expiration duration and cleanup 1129 | // interval. If the expiration duration is less than one (or NoExpiration), 1130 | // the items in the cache never expire (by default), and must be deleted 1131 | // manually. If the cleanup interval is less than one, expired items are not 1132 | // deleted from the cache before calling c.DeleteExpired(). 1133 | func New(defaultExpiration, cleanupInterval time.Duration) *Cache { 1134 | items := make(map[string]Item) 1135 | return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) 1136 | } 1137 | 1138 | // Return a new cache with a given default expiration duration and cleanup 1139 | // interval. If the expiration duration is less than one (or NoExpiration), 1140 | // the items in the cache never expire (by default), and must be deleted 1141 | // manually. If the cleanup interval is less than one, expired items are not 1142 | // deleted from the cache before calling c.DeleteExpired(). 1143 | // 1144 | // NewFrom() also accepts an items map which will serve as the underlying map 1145 | // for the cache. This is useful for starting from a deserialized cache 1146 | // (serialized using e.g. gob.Encode() on c.Items()), or passing in e.g. 1147 | // make(map[string]Item, 500) to improve startup performance when the cache 1148 | // is expected to reach a certain minimum size. 1149 | // 1150 | // Only the cache's methods synchronize access to this map, so it is not 1151 | // recommended to keep any references to the map around after creating a cache. 1152 | // If need be, the map can be accessed at a later point using c.Items() (subject 1153 | // to the same caveat.) 1154 | // 1155 | // Note regarding serialization: When using e.g. gob, make sure to 1156 | // gob.Register() the individual types stored in the cache before encoding a 1157 | // map retrieved with c.Items(), and to register those same types before 1158 | // decoding a blob containing an items map. 1159 | func NewFrom(defaultExpiration, cleanupInterval time.Duration, items map[string]Item) *Cache { 1160 | return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) 1161 | } 1162 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "runtime" 7 | "strconv" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | type TestStruct struct { 14 | Num int 15 | Children []*TestStruct 16 | } 17 | 18 | func TestCache(t *testing.T) { 19 | tc := New(DefaultExpiration, 0) 20 | 21 | a, found := tc.Get("a") 22 | if found || a != nil { 23 | t.Error("Getting A found value that shouldn't exist:", a) 24 | } 25 | 26 | b, found := tc.Get("b") 27 | if found || b != nil { 28 | t.Error("Getting B found value that shouldn't exist:", b) 29 | } 30 | 31 | c, found := tc.Get("c") 32 | if found || c != nil { 33 | t.Error("Getting C found value that shouldn't exist:", c) 34 | } 35 | 36 | tc.Set("a", 1, DefaultExpiration) 37 | tc.Set("b", "b", DefaultExpiration) 38 | tc.Set("c", 3.5, DefaultExpiration) 39 | 40 | x, found := tc.Get("a") 41 | if !found { 42 | t.Error("a was not found while getting a2") 43 | } 44 | if x == nil { 45 | t.Error("x for a is nil") 46 | } else if a2 := x.(int); a2+2 != 3 { 47 | t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) 48 | } 49 | 50 | x, found = tc.Get("b") 51 | if !found { 52 | t.Error("b was not found while getting b2") 53 | } 54 | if x == nil { 55 | t.Error("x for b is nil") 56 | } else if b2 := x.(string); b2+"B" != "bB" { 57 | t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) 58 | } 59 | 60 | x, found = tc.Get("c") 61 | if !found { 62 | t.Error("c was not found while getting c2") 63 | } 64 | if x == nil { 65 | t.Error("x for c is nil") 66 | } else if c2 := x.(float64); c2+1.2 != 4.7 { 67 | t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) 68 | } 69 | } 70 | 71 | func TestCacheTimes(t *testing.T) { 72 | var found bool 73 | 74 | tc := New(50*time.Millisecond, 1*time.Millisecond) 75 | tc.Set("a", 1, DefaultExpiration) 76 | tc.Set("b", 2, NoExpiration) 77 | tc.Set("c", 3, 20*time.Millisecond) 78 | tc.Set("d", 4, 70*time.Millisecond) 79 | 80 | <-time.After(25 * time.Millisecond) 81 | _, found = tc.Get("c") 82 | if found { 83 | t.Error("Found c when it should have been automatically deleted") 84 | } 85 | 86 | <-time.After(30 * time.Millisecond) 87 | _, found = tc.Get("a") 88 | if found { 89 | t.Error("Found a when it should have been automatically deleted") 90 | } 91 | 92 | _, found = tc.Get("b") 93 | if !found { 94 | t.Error("Did not find b even though it was set to never expire") 95 | } 96 | 97 | _, found = tc.Get("d") 98 | if !found { 99 | t.Error("Did not find d even though it was set to expire later than the default") 100 | } 101 | 102 | <-time.After(20 * time.Millisecond) 103 | _, found = tc.Get("d") 104 | if found { 105 | t.Error("Found d when it should have been automatically deleted (later than the default)") 106 | } 107 | } 108 | 109 | func TestNewFrom(t *testing.T) { 110 | m := map[string]Item{ 111 | "a": Item{ 112 | Object: 1, 113 | Expiration: 0, 114 | }, 115 | "b": Item{ 116 | Object: 2, 117 | Expiration: 0, 118 | }, 119 | } 120 | tc := NewFrom(DefaultExpiration, 0, m) 121 | a, found := tc.Get("a") 122 | if !found { 123 | t.Fatal("Did not find a") 124 | } 125 | if a.(int) != 1 { 126 | t.Fatal("a is not 1") 127 | } 128 | b, found := tc.Get("b") 129 | if !found { 130 | t.Fatal("Did not find b") 131 | } 132 | if b.(int) != 2 { 133 | t.Fatal("b is not 2") 134 | } 135 | } 136 | 137 | func TestStorePointerToStruct(t *testing.T) { 138 | tc := New(DefaultExpiration, 0) 139 | tc.Set("foo", &TestStruct{Num: 1}, DefaultExpiration) 140 | x, found := tc.Get("foo") 141 | if !found { 142 | t.Fatal("*TestStruct was not found for foo") 143 | } 144 | foo := x.(*TestStruct) 145 | foo.Num++ 146 | 147 | y, found := tc.Get("foo") 148 | if !found { 149 | t.Fatal("*TestStruct was not found for foo (second time)") 150 | } 151 | bar := y.(*TestStruct) 152 | if bar.Num != 2 { 153 | t.Fatal("TestStruct.Num is not 2") 154 | } 155 | } 156 | 157 | func TestIncrementWithInt(t *testing.T) { 158 | tc := New(DefaultExpiration, 0) 159 | tc.Set("tint", 1, DefaultExpiration) 160 | err := tc.Increment("tint", 2) 161 | if err != nil { 162 | t.Error("Error incrementing:", err) 163 | } 164 | x, found := tc.Get("tint") 165 | if !found { 166 | t.Error("tint was not found") 167 | } 168 | if x.(int) != 3 { 169 | t.Error("tint is not 3:", x) 170 | } 171 | } 172 | 173 | func TestIncrementWithInt8(t *testing.T) { 174 | tc := New(DefaultExpiration, 0) 175 | tc.Set("tint8", int8(1), DefaultExpiration) 176 | err := tc.Increment("tint8", 2) 177 | if err != nil { 178 | t.Error("Error incrementing:", err) 179 | } 180 | x, found := tc.Get("tint8") 181 | if !found { 182 | t.Error("tint8 was not found") 183 | } 184 | if x.(int8) != 3 { 185 | t.Error("tint8 is not 3:", x) 186 | } 187 | } 188 | 189 | func TestIncrementWithInt16(t *testing.T) { 190 | tc := New(DefaultExpiration, 0) 191 | tc.Set("tint16", int16(1), DefaultExpiration) 192 | err := tc.Increment("tint16", 2) 193 | if err != nil { 194 | t.Error("Error incrementing:", err) 195 | } 196 | x, found := tc.Get("tint16") 197 | if !found { 198 | t.Error("tint16 was not found") 199 | } 200 | if x.(int16) != 3 { 201 | t.Error("tint16 is not 3:", x) 202 | } 203 | } 204 | 205 | func TestIncrementWithInt32(t *testing.T) { 206 | tc := New(DefaultExpiration, 0) 207 | tc.Set("tint32", int32(1), DefaultExpiration) 208 | err := tc.Increment("tint32", 2) 209 | if err != nil { 210 | t.Error("Error incrementing:", err) 211 | } 212 | x, found := tc.Get("tint32") 213 | if !found { 214 | t.Error("tint32 was not found") 215 | } 216 | if x.(int32) != 3 { 217 | t.Error("tint32 is not 3:", x) 218 | } 219 | } 220 | 221 | func TestIncrementWithInt64(t *testing.T) { 222 | tc := New(DefaultExpiration, 0) 223 | tc.Set("tint64", int64(1), DefaultExpiration) 224 | err := tc.Increment("tint64", 2) 225 | if err != nil { 226 | t.Error("Error incrementing:", err) 227 | } 228 | x, found := tc.Get("tint64") 229 | if !found { 230 | t.Error("tint64 was not found") 231 | } 232 | if x.(int64) != 3 { 233 | t.Error("tint64 is not 3:", x) 234 | } 235 | } 236 | 237 | func TestIncrementWithUint(t *testing.T) { 238 | tc := New(DefaultExpiration, 0) 239 | tc.Set("tuint", uint(1), DefaultExpiration) 240 | err := tc.Increment("tuint", 2) 241 | if err != nil { 242 | t.Error("Error incrementing:", err) 243 | } 244 | x, found := tc.Get("tuint") 245 | if !found { 246 | t.Error("tuint was not found") 247 | } 248 | if x.(uint) != 3 { 249 | t.Error("tuint is not 3:", x) 250 | } 251 | } 252 | 253 | func TestIncrementWithUintptr(t *testing.T) { 254 | tc := New(DefaultExpiration, 0) 255 | tc.Set("tuintptr", uintptr(1), DefaultExpiration) 256 | err := tc.Increment("tuintptr", 2) 257 | if err != nil { 258 | t.Error("Error incrementing:", err) 259 | } 260 | 261 | x, found := tc.Get("tuintptr") 262 | if !found { 263 | t.Error("tuintptr was not found") 264 | } 265 | if x.(uintptr) != 3 { 266 | t.Error("tuintptr is not 3:", x) 267 | } 268 | } 269 | 270 | func TestIncrementWithUint8(t *testing.T) { 271 | tc := New(DefaultExpiration, 0) 272 | tc.Set("tuint8", uint8(1), DefaultExpiration) 273 | err := tc.Increment("tuint8", 2) 274 | if err != nil { 275 | t.Error("Error incrementing:", err) 276 | } 277 | x, found := tc.Get("tuint8") 278 | if !found { 279 | t.Error("tuint8 was not found") 280 | } 281 | if x.(uint8) != 3 { 282 | t.Error("tuint8 is not 3:", x) 283 | } 284 | } 285 | 286 | func TestIncrementWithUint16(t *testing.T) { 287 | tc := New(DefaultExpiration, 0) 288 | tc.Set("tuint16", uint16(1), DefaultExpiration) 289 | err := tc.Increment("tuint16", 2) 290 | if err != nil { 291 | t.Error("Error incrementing:", err) 292 | } 293 | 294 | x, found := tc.Get("tuint16") 295 | if !found { 296 | t.Error("tuint16 was not found") 297 | } 298 | if x.(uint16) != 3 { 299 | t.Error("tuint16 is not 3:", x) 300 | } 301 | } 302 | 303 | func TestIncrementWithUint32(t *testing.T) { 304 | tc := New(DefaultExpiration, 0) 305 | tc.Set("tuint32", uint32(1), DefaultExpiration) 306 | err := tc.Increment("tuint32", 2) 307 | if err != nil { 308 | t.Error("Error incrementing:", err) 309 | } 310 | x, found := tc.Get("tuint32") 311 | if !found { 312 | t.Error("tuint32 was not found") 313 | } 314 | if x.(uint32) != 3 { 315 | t.Error("tuint32 is not 3:", x) 316 | } 317 | } 318 | 319 | func TestIncrementWithUint64(t *testing.T) { 320 | tc := New(DefaultExpiration, 0) 321 | tc.Set("tuint64", uint64(1), DefaultExpiration) 322 | err := tc.Increment("tuint64", 2) 323 | if err != nil { 324 | t.Error("Error incrementing:", err) 325 | } 326 | 327 | x, found := tc.Get("tuint64") 328 | if !found { 329 | t.Error("tuint64 was not found") 330 | } 331 | if x.(uint64) != 3 { 332 | t.Error("tuint64 is not 3:", x) 333 | } 334 | } 335 | 336 | func TestIncrementWithFloat32(t *testing.T) { 337 | tc := New(DefaultExpiration, 0) 338 | tc.Set("float32", float32(1.5), DefaultExpiration) 339 | err := tc.Increment("float32", 2) 340 | if err != nil { 341 | t.Error("Error incrementing:", err) 342 | } 343 | x, found := tc.Get("float32") 344 | if !found { 345 | t.Error("float32 was not found") 346 | } 347 | if x.(float32) != 3.5 { 348 | t.Error("float32 is not 3.5:", x) 349 | } 350 | } 351 | 352 | func TestIncrementWithFloat64(t *testing.T) { 353 | tc := New(DefaultExpiration, 0) 354 | tc.Set("float64", float64(1.5), DefaultExpiration) 355 | err := tc.Increment("float64", 2) 356 | if err != nil { 357 | t.Error("Error incrementing:", err) 358 | } 359 | x, found := tc.Get("float64") 360 | if !found { 361 | t.Error("float64 was not found") 362 | } 363 | if x.(float64) != 3.5 { 364 | t.Error("float64 is not 3.5:", x) 365 | } 366 | } 367 | 368 | func TestIncrementFloatWithFloat32(t *testing.T) { 369 | tc := New(DefaultExpiration, 0) 370 | tc.Set("float32", float32(1.5), DefaultExpiration) 371 | err := tc.IncrementFloat("float32", 2) 372 | if err != nil { 373 | t.Error("Error incrementfloating:", err) 374 | } 375 | x, found := tc.Get("float32") 376 | if !found { 377 | t.Error("float32 was not found") 378 | } 379 | if x.(float32) != 3.5 { 380 | t.Error("float32 is not 3.5:", x) 381 | } 382 | } 383 | 384 | func TestIncrementFloatWithFloat64(t *testing.T) { 385 | tc := New(DefaultExpiration, 0) 386 | tc.Set("float64", float64(1.5), DefaultExpiration) 387 | err := tc.IncrementFloat("float64", 2) 388 | if err != nil { 389 | t.Error("Error incrementfloating:", err) 390 | } 391 | x, found := tc.Get("float64") 392 | if !found { 393 | t.Error("float64 was not found") 394 | } 395 | if x.(float64) != 3.5 { 396 | t.Error("float64 is not 3.5:", x) 397 | } 398 | } 399 | 400 | func TestDecrementWithInt(t *testing.T) { 401 | tc := New(DefaultExpiration, 0) 402 | tc.Set("int", int(5), DefaultExpiration) 403 | err := tc.Decrement("int", 2) 404 | if err != nil { 405 | t.Error("Error decrementing:", err) 406 | } 407 | x, found := tc.Get("int") 408 | if !found { 409 | t.Error("int was not found") 410 | } 411 | if x.(int) != 3 { 412 | t.Error("int is not 3:", x) 413 | } 414 | } 415 | 416 | func TestDecrementWithInt8(t *testing.T) { 417 | tc := New(DefaultExpiration, 0) 418 | tc.Set("int8", int8(5), DefaultExpiration) 419 | err := tc.Decrement("int8", 2) 420 | if err != nil { 421 | t.Error("Error decrementing:", err) 422 | } 423 | x, found := tc.Get("int8") 424 | if !found { 425 | t.Error("int8 was not found") 426 | } 427 | if x.(int8) != 3 { 428 | t.Error("int8 is not 3:", x) 429 | } 430 | } 431 | 432 | func TestDecrementWithInt16(t *testing.T) { 433 | tc := New(DefaultExpiration, 0) 434 | tc.Set("int16", int16(5), DefaultExpiration) 435 | err := tc.Decrement("int16", 2) 436 | if err != nil { 437 | t.Error("Error decrementing:", err) 438 | } 439 | x, found := tc.Get("int16") 440 | if !found { 441 | t.Error("int16 was not found") 442 | } 443 | if x.(int16) != 3 { 444 | t.Error("int16 is not 3:", x) 445 | } 446 | } 447 | 448 | func TestDecrementWithInt32(t *testing.T) { 449 | tc := New(DefaultExpiration, 0) 450 | tc.Set("int32", int32(5), DefaultExpiration) 451 | err := tc.Decrement("int32", 2) 452 | if err != nil { 453 | t.Error("Error decrementing:", err) 454 | } 455 | x, found := tc.Get("int32") 456 | if !found { 457 | t.Error("int32 was not found") 458 | } 459 | if x.(int32) != 3 { 460 | t.Error("int32 is not 3:", x) 461 | } 462 | } 463 | 464 | func TestDecrementWithInt64(t *testing.T) { 465 | tc := New(DefaultExpiration, 0) 466 | tc.Set("int64", int64(5), DefaultExpiration) 467 | err := tc.Decrement("int64", 2) 468 | if err != nil { 469 | t.Error("Error decrementing:", err) 470 | } 471 | x, found := tc.Get("int64") 472 | if !found { 473 | t.Error("int64 was not found") 474 | } 475 | if x.(int64) != 3 { 476 | t.Error("int64 is not 3:", x) 477 | } 478 | } 479 | 480 | func TestDecrementWithUint(t *testing.T) { 481 | tc := New(DefaultExpiration, 0) 482 | tc.Set("uint", uint(5), DefaultExpiration) 483 | err := tc.Decrement("uint", 2) 484 | if err != nil { 485 | t.Error("Error decrementing:", err) 486 | } 487 | x, found := tc.Get("uint") 488 | if !found { 489 | t.Error("uint was not found") 490 | } 491 | if x.(uint) != 3 { 492 | t.Error("uint is not 3:", x) 493 | } 494 | } 495 | 496 | func TestDecrementWithUintptr(t *testing.T) { 497 | tc := New(DefaultExpiration, 0) 498 | tc.Set("uintptr", uintptr(5), DefaultExpiration) 499 | err := tc.Decrement("uintptr", 2) 500 | if err != nil { 501 | t.Error("Error decrementing:", err) 502 | } 503 | x, found := tc.Get("uintptr") 504 | if !found { 505 | t.Error("uintptr was not found") 506 | } 507 | if x.(uintptr) != 3 { 508 | t.Error("uintptr is not 3:", x) 509 | } 510 | } 511 | 512 | func TestDecrementWithUint8(t *testing.T) { 513 | tc := New(DefaultExpiration, 0) 514 | tc.Set("uint8", uint8(5), DefaultExpiration) 515 | err := tc.Decrement("uint8", 2) 516 | if err != nil { 517 | t.Error("Error decrementing:", err) 518 | } 519 | x, found := tc.Get("uint8") 520 | if !found { 521 | t.Error("uint8 was not found") 522 | } 523 | if x.(uint8) != 3 { 524 | t.Error("uint8 is not 3:", x) 525 | } 526 | } 527 | 528 | func TestDecrementWithUint16(t *testing.T) { 529 | tc := New(DefaultExpiration, 0) 530 | tc.Set("uint16", uint16(5), DefaultExpiration) 531 | err := tc.Decrement("uint16", 2) 532 | if err != nil { 533 | t.Error("Error decrementing:", err) 534 | } 535 | x, found := tc.Get("uint16") 536 | if !found { 537 | t.Error("uint16 was not found") 538 | } 539 | if x.(uint16) != 3 { 540 | t.Error("uint16 is not 3:", x) 541 | } 542 | } 543 | 544 | func TestDecrementWithUint32(t *testing.T) { 545 | tc := New(DefaultExpiration, 0) 546 | tc.Set("uint32", uint32(5), DefaultExpiration) 547 | err := tc.Decrement("uint32", 2) 548 | if err != nil { 549 | t.Error("Error decrementing:", err) 550 | } 551 | x, found := tc.Get("uint32") 552 | if !found { 553 | t.Error("uint32 was not found") 554 | } 555 | if x.(uint32) != 3 { 556 | t.Error("uint32 is not 3:", x) 557 | } 558 | } 559 | 560 | func TestDecrementWithUint64(t *testing.T) { 561 | tc := New(DefaultExpiration, 0) 562 | tc.Set("uint64", uint64(5), DefaultExpiration) 563 | err := tc.Decrement("uint64", 2) 564 | if err != nil { 565 | t.Error("Error decrementing:", err) 566 | } 567 | x, found := tc.Get("uint64") 568 | if !found { 569 | t.Error("uint64 was not found") 570 | } 571 | if x.(uint64) != 3 { 572 | t.Error("uint64 is not 3:", x) 573 | } 574 | } 575 | 576 | func TestDecrementWithFloat32(t *testing.T) { 577 | tc := New(DefaultExpiration, 0) 578 | tc.Set("float32", float32(5.5), DefaultExpiration) 579 | err := tc.Decrement("float32", 2) 580 | if err != nil { 581 | t.Error("Error decrementing:", err) 582 | } 583 | x, found := tc.Get("float32") 584 | if !found { 585 | t.Error("float32 was not found") 586 | } 587 | if x.(float32) != 3.5 { 588 | t.Error("float32 is not 3:", x) 589 | } 590 | } 591 | 592 | func TestDecrementWithFloat64(t *testing.T) { 593 | tc := New(DefaultExpiration, 0) 594 | tc.Set("float64", float64(5.5), DefaultExpiration) 595 | err := tc.Decrement("float64", 2) 596 | if err != nil { 597 | t.Error("Error decrementing:", err) 598 | } 599 | x, found := tc.Get("float64") 600 | if !found { 601 | t.Error("float64 was not found") 602 | } 603 | if x.(float64) != 3.5 { 604 | t.Error("float64 is not 3:", x) 605 | } 606 | } 607 | 608 | func TestDecrementFloatWithFloat32(t *testing.T) { 609 | tc := New(DefaultExpiration, 0) 610 | tc.Set("float32", float32(5.5), DefaultExpiration) 611 | err := tc.DecrementFloat("float32", 2) 612 | if err != nil { 613 | t.Error("Error decrementing:", err) 614 | } 615 | x, found := tc.Get("float32") 616 | if !found { 617 | t.Error("float32 was not found") 618 | } 619 | if x.(float32) != 3.5 { 620 | t.Error("float32 is not 3:", x) 621 | } 622 | } 623 | 624 | func TestDecrementFloatWithFloat64(t *testing.T) { 625 | tc := New(DefaultExpiration, 0) 626 | tc.Set("float64", float64(5.5), DefaultExpiration) 627 | err := tc.DecrementFloat("float64", 2) 628 | if err != nil { 629 | t.Error("Error decrementing:", err) 630 | } 631 | x, found := tc.Get("float64") 632 | if !found { 633 | t.Error("float64 was not found") 634 | } 635 | if x.(float64) != 3.5 { 636 | t.Error("float64 is not 3:", x) 637 | } 638 | } 639 | 640 | func TestIncrementInt(t *testing.T) { 641 | tc := New(DefaultExpiration, 0) 642 | tc.Set("tint", 1, DefaultExpiration) 643 | n, err := tc.IncrementInt("tint", 2) 644 | if err != nil { 645 | t.Error("Error incrementing:", err) 646 | } 647 | if n != 3 { 648 | t.Error("Returned number is not 3:", n) 649 | } 650 | x, found := tc.Get("tint") 651 | if !found { 652 | t.Error("tint was not found") 653 | } 654 | if x.(int) != 3 { 655 | t.Error("tint is not 3:", x) 656 | } 657 | } 658 | 659 | func TestIncrementInt8(t *testing.T) { 660 | tc := New(DefaultExpiration, 0) 661 | tc.Set("tint8", int8(1), DefaultExpiration) 662 | n, err := tc.IncrementInt8("tint8", 2) 663 | if err != nil { 664 | t.Error("Error incrementing:", err) 665 | } 666 | if n != 3 { 667 | t.Error("Returned number is not 3:", n) 668 | } 669 | x, found := tc.Get("tint8") 670 | if !found { 671 | t.Error("tint8 was not found") 672 | } 673 | if x.(int8) != 3 { 674 | t.Error("tint8 is not 3:", x) 675 | } 676 | } 677 | 678 | func TestIncrementInt16(t *testing.T) { 679 | tc := New(DefaultExpiration, 0) 680 | tc.Set("tint16", int16(1), DefaultExpiration) 681 | n, err := tc.IncrementInt16("tint16", 2) 682 | if err != nil { 683 | t.Error("Error incrementing:", err) 684 | } 685 | if n != 3 { 686 | t.Error("Returned number is not 3:", n) 687 | } 688 | x, found := tc.Get("tint16") 689 | if !found { 690 | t.Error("tint16 was not found") 691 | } 692 | if x.(int16) != 3 { 693 | t.Error("tint16 is not 3:", x) 694 | } 695 | } 696 | 697 | func TestIncrementInt32(t *testing.T) { 698 | tc := New(DefaultExpiration, 0) 699 | tc.Set("tint32", int32(1), DefaultExpiration) 700 | n, err := tc.IncrementInt32("tint32", 2) 701 | if err != nil { 702 | t.Error("Error incrementing:", err) 703 | } 704 | if n != 3 { 705 | t.Error("Returned number is not 3:", n) 706 | } 707 | x, found := tc.Get("tint32") 708 | if !found { 709 | t.Error("tint32 was not found") 710 | } 711 | if x.(int32) != 3 { 712 | t.Error("tint32 is not 3:", x) 713 | } 714 | } 715 | 716 | func TestIncrementInt64(t *testing.T) { 717 | tc := New(DefaultExpiration, 0) 718 | tc.Set("tint64", int64(1), DefaultExpiration) 719 | n, err := tc.IncrementInt64("tint64", 2) 720 | if err != nil { 721 | t.Error("Error incrementing:", err) 722 | } 723 | if n != 3 { 724 | t.Error("Returned number is not 3:", n) 725 | } 726 | x, found := tc.Get("tint64") 727 | if !found { 728 | t.Error("tint64 was not found") 729 | } 730 | if x.(int64) != 3 { 731 | t.Error("tint64 is not 3:", x) 732 | } 733 | } 734 | 735 | func TestIncrementUint(t *testing.T) { 736 | tc := New(DefaultExpiration, 0) 737 | tc.Set("tuint", uint(1), DefaultExpiration) 738 | n, err := tc.IncrementUint("tuint", 2) 739 | if err != nil { 740 | t.Error("Error incrementing:", err) 741 | } 742 | if n != 3 { 743 | t.Error("Returned number is not 3:", n) 744 | } 745 | x, found := tc.Get("tuint") 746 | if !found { 747 | t.Error("tuint was not found") 748 | } 749 | if x.(uint) != 3 { 750 | t.Error("tuint is not 3:", x) 751 | } 752 | } 753 | 754 | func TestIncrementUintptr(t *testing.T) { 755 | tc := New(DefaultExpiration, 0) 756 | tc.Set("tuintptr", uintptr(1), DefaultExpiration) 757 | n, err := tc.IncrementUintptr("tuintptr", 2) 758 | if err != nil { 759 | t.Error("Error incrementing:", err) 760 | } 761 | if n != 3 { 762 | t.Error("Returned number is not 3:", n) 763 | } 764 | x, found := tc.Get("tuintptr") 765 | if !found { 766 | t.Error("tuintptr was not found") 767 | } 768 | if x.(uintptr) != 3 { 769 | t.Error("tuintptr is not 3:", x) 770 | } 771 | } 772 | 773 | func TestIncrementUint8(t *testing.T) { 774 | tc := New(DefaultExpiration, 0) 775 | tc.Set("tuint8", uint8(1), DefaultExpiration) 776 | n, err := tc.IncrementUint8("tuint8", 2) 777 | if err != nil { 778 | t.Error("Error incrementing:", err) 779 | } 780 | if n != 3 { 781 | t.Error("Returned number is not 3:", n) 782 | } 783 | x, found := tc.Get("tuint8") 784 | if !found { 785 | t.Error("tuint8 was not found") 786 | } 787 | if x.(uint8) != 3 { 788 | t.Error("tuint8 is not 3:", x) 789 | } 790 | } 791 | 792 | func TestIncrementUint16(t *testing.T) { 793 | tc := New(DefaultExpiration, 0) 794 | tc.Set("tuint16", uint16(1), DefaultExpiration) 795 | n, err := tc.IncrementUint16("tuint16", 2) 796 | if err != nil { 797 | t.Error("Error incrementing:", err) 798 | } 799 | if n != 3 { 800 | t.Error("Returned number is not 3:", n) 801 | } 802 | x, found := tc.Get("tuint16") 803 | if !found { 804 | t.Error("tuint16 was not found") 805 | } 806 | if x.(uint16) != 3 { 807 | t.Error("tuint16 is not 3:", x) 808 | } 809 | } 810 | 811 | func TestIncrementUint32(t *testing.T) { 812 | tc := New(DefaultExpiration, 0) 813 | tc.Set("tuint32", uint32(1), DefaultExpiration) 814 | n, err := tc.IncrementUint32("tuint32", 2) 815 | if err != nil { 816 | t.Error("Error incrementing:", err) 817 | } 818 | if n != 3 { 819 | t.Error("Returned number is not 3:", n) 820 | } 821 | x, found := tc.Get("tuint32") 822 | if !found { 823 | t.Error("tuint32 was not found") 824 | } 825 | if x.(uint32) != 3 { 826 | t.Error("tuint32 is not 3:", x) 827 | } 828 | } 829 | 830 | func TestIncrementUint64(t *testing.T) { 831 | tc := New(DefaultExpiration, 0) 832 | tc.Set("tuint64", uint64(1), DefaultExpiration) 833 | n, err := tc.IncrementUint64("tuint64", 2) 834 | if err != nil { 835 | t.Error("Error incrementing:", err) 836 | } 837 | if n != 3 { 838 | t.Error("Returned number is not 3:", n) 839 | } 840 | x, found := tc.Get("tuint64") 841 | if !found { 842 | t.Error("tuint64 was not found") 843 | } 844 | if x.(uint64) != 3 { 845 | t.Error("tuint64 is not 3:", x) 846 | } 847 | } 848 | 849 | func TestIncrementFloat32(t *testing.T) { 850 | tc := New(DefaultExpiration, 0) 851 | tc.Set("float32", float32(1.5), DefaultExpiration) 852 | n, err := tc.IncrementFloat32("float32", 2) 853 | if err != nil { 854 | t.Error("Error incrementing:", err) 855 | } 856 | if n != 3.5 { 857 | t.Error("Returned number is not 3.5:", n) 858 | } 859 | x, found := tc.Get("float32") 860 | if !found { 861 | t.Error("float32 was not found") 862 | } 863 | if x.(float32) != 3.5 { 864 | t.Error("float32 is not 3.5:", x) 865 | } 866 | } 867 | 868 | func TestIncrementFloat64(t *testing.T) { 869 | tc := New(DefaultExpiration, 0) 870 | tc.Set("float64", float64(1.5), DefaultExpiration) 871 | n, err := tc.IncrementFloat64("float64", 2) 872 | if err != nil { 873 | t.Error("Error incrementing:", err) 874 | } 875 | if n != 3.5 { 876 | t.Error("Returned number is not 3.5:", n) 877 | } 878 | x, found := tc.Get("float64") 879 | if !found { 880 | t.Error("float64 was not found") 881 | } 882 | if x.(float64) != 3.5 { 883 | t.Error("float64 is not 3.5:", x) 884 | } 885 | } 886 | 887 | func TestDecrementInt8(t *testing.T) { 888 | tc := New(DefaultExpiration, 0) 889 | tc.Set("int8", int8(5), DefaultExpiration) 890 | n, err := tc.DecrementInt8("int8", 2) 891 | if err != nil { 892 | t.Error("Error decrementing:", err) 893 | } 894 | if n != 3 { 895 | t.Error("Returned number is not 3:", n) 896 | } 897 | x, found := tc.Get("int8") 898 | if !found { 899 | t.Error("int8 was not found") 900 | } 901 | if x.(int8) != 3 { 902 | t.Error("int8 is not 3:", x) 903 | } 904 | } 905 | 906 | func TestDecrementInt16(t *testing.T) { 907 | tc := New(DefaultExpiration, 0) 908 | tc.Set("int16", int16(5), DefaultExpiration) 909 | n, err := tc.DecrementInt16("int16", 2) 910 | if err != nil { 911 | t.Error("Error decrementing:", err) 912 | } 913 | if n != 3 { 914 | t.Error("Returned number is not 3:", n) 915 | } 916 | x, found := tc.Get("int16") 917 | if !found { 918 | t.Error("int16 was not found") 919 | } 920 | if x.(int16) != 3 { 921 | t.Error("int16 is not 3:", x) 922 | } 923 | } 924 | 925 | func TestDecrementInt32(t *testing.T) { 926 | tc := New(DefaultExpiration, 0) 927 | tc.Set("int32", int32(5), DefaultExpiration) 928 | n, err := tc.DecrementInt32("int32", 2) 929 | if err != nil { 930 | t.Error("Error decrementing:", err) 931 | } 932 | if n != 3 { 933 | t.Error("Returned number is not 3:", n) 934 | } 935 | x, found := tc.Get("int32") 936 | if !found { 937 | t.Error("int32 was not found") 938 | } 939 | if x.(int32) != 3 { 940 | t.Error("int32 is not 3:", x) 941 | } 942 | } 943 | 944 | func TestDecrementInt64(t *testing.T) { 945 | tc := New(DefaultExpiration, 0) 946 | tc.Set("int64", int64(5), DefaultExpiration) 947 | n, err := tc.DecrementInt64("int64", 2) 948 | if err != nil { 949 | t.Error("Error decrementing:", err) 950 | } 951 | if n != 3 { 952 | t.Error("Returned number is not 3:", n) 953 | } 954 | x, found := tc.Get("int64") 955 | if !found { 956 | t.Error("int64 was not found") 957 | } 958 | if x.(int64) != 3 { 959 | t.Error("int64 is not 3:", x) 960 | } 961 | } 962 | 963 | func TestDecrementUint(t *testing.T) { 964 | tc := New(DefaultExpiration, 0) 965 | tc.Set("uint", uint(5), DefaultExpiration) 966 | n, err := tc.DecrementUint("uint", 2) 967 | if err != nil { 968 | t.Error("Error decrementing:", err) 969 | } 970 | if n != 3 { 971 | t.Error("Returned number is not 3:", n) 972 | } 973 | x, found := tc.Get("uint") 974 | if !found { 975 | t.Error("uint was not found") 976 | } 977 | if x.(uint) != 3 { 978 | t.Error("uint is not 3:", x) 979 | } 980 | } 981 | 982 | func TestDecrementUintptr(t *testing.T) { 983 | tc := New(DefaultExpiration, 0) 984 | tc.Set("uintptr", uintptr(5), DefaultExpiration) 985 | n, err := tc.DecrementUintptr("uintptr", 2) 986 | if err != nil { 987 | t.Error("Error decrementing:", err) 988 | } 989 | if n != 3 { 990 | t.Error("Returned number is not 3:", n) 991 | } 992 | x, found := tc.Get("uintptr") 993 | if !found { 994 | t.Error("uintptr was not found") 995 | } 996 | if x.(uintptr) != 3 { 997 | t.Error("uintptr is not 3:", x) 998 | } 999 | } 1000 | 1001 | func TestDecrementUint8(t *testing.T) { 1002 | tc := New(DefaultExpiration, 0) 1003 | tc.Set("uint8", uint8(5), DefaultExpiration) 1004 | n, err := tc.DecrementUint8("uint8", 2) 1005 | if err != nil { 1006 | t.Error("Error decrementing:", err) 1007 | } 1008 | if n != 3 { 1009 | t.Error("Returned number is not 3:", n) 1010 | } 1011 | x, found := tc.Get("uint8") 1012 | if !found { 1013 | t.Error("uint8 was not found") 1014 | } 1015 | if x.(uint8) != 3 { 1016 | t.Error("uint8 is not 3:", x) 1017 | } 1018 | } 1019 | 1020 | func TestDecrementUint16(t *testing.T) { 1021 | tc := New(DefaultExpiration, 0) 1022 | tc.Set("uint16", uint16(5), DefaultExpiration) 1023 | n, err := tc.DecrementUint16("uint16", 2) 1024 | if err != nil { 1025 | t.Error("Error decrementing:", err) 1026 | } 1027 | if n != 3 { 1028 | t.Error("Returned number is not 3:", n) 1029 | } 1030 | x, found := tc.Get("uint16") 1031 | if !found { 1032 | t.Error("uint16 was not found") 1033 | } 1034 | if x.(uint16) != 3 { 1035 | t.Error("uint16 is not 3:", x) 1036 | } 1037 | } 1038 | 1039 | func TestDecrementUint32(t *testing.T) { 1040 | tc := New(DefaultExpiration, 0) 1041 | tc.Set("uint32", uint32(5), DefaultExpiration) 1042 | n, err := tc.DecrementUint32("uint32", 2) 1043 | if err != nil { 1044 | t.Error("Error decrementing:", err) 1045 | } 1046 | if n != 3 { 1047 | t.Error("Returned number is not 3:", n) 1048 | } 1049 | x, found := tc.Get("uint32") 1050 | if !found { 1051 | t.Error("uint32 was not found") 1052 | } 1053 | if x.(uint32) != 3 { 1054 | t.Error("uint32 is not 3:", x) 1055 | } 1056 | } 1057 | 1058 | func TestDecrementUint64(t *testing.T) { 1059 | tc := New(DefaultExpiration, 0) 1060 | tc.Set("uint64", uint64(5), DefaultExpiration) 1061 | n, err := tc.DecrementUint64("uint64", 2) 1062 | if err != nil { 1063 | t.Error("Error decrementing:", err) 1064 | } 1065 | if n != 3 { 1066 | t.Error("Returned number is not 3:", n) 1067 | } 1068 | x, found := tc.Get("uint64") 1069 | if !found { 1070 | t.Error("uint64 was not found") 1071 | } 1072 | if x.(uint64) != 3 { 1073 | t.Error("uint64 is not 3:", x) 1074 | } 1075 | } 1076 | 1077 | func TestDecrementFloat32(t *testing.T) { 1078 | tc := New(DefaultExpiration, 0) 1079 | tc.Set("float32", float32(5), DefaultExpiration) 1080 | n, err := tc.DecrementFloat32("float32", 2) 1081 | if err != nil { 1082 | t.Error("Error decrementing:", err) 1083 | } 1084 | if n != 3 { 1085 | t.Error("Returned number is not 3:", n) 1086 | } 1087 | x, found := tc.Get("float32") 1088 | if !found { 1089 | t.Error("float32 was not found") 1090 | } 1091 | if x.(float32) != 3 { 1092 | t.Error("float32 is not 3:", x) 1093 | } 1094 | } 1095 | 1096 | func TestDecrementFloat64(t *testing.T) { 1097 | tc := New(DefaultExpiration, 0) 1098 | tc.Set("float64", float64(5), DefaultExpiration) 1099 | n, err := tc.DecrementFloat64("float64", 2) 1100 | if err != nil { 1101 | t.Error("Error decrementing:", err) 1102 | } 1103 | if n != 3 { 1104 | t.Error("Returned number is not 3:", n) 1105 | } 1106 | x, found := tc.Get("float64") 1107 | if !found { 1108 | t.Error("float64 was not found") 1109 | } 1110 | if x.(float64) != 3 { 1111 | t.Error("float64 is not 3:", x) 1112 | } 1113 | } 1114 | 1115 | func TestAdd(t *testing.T) { 1116 | tc := New(DefaultExpiration, 0) 1117 | err := tc.Add("foo", "bar", DefaultExpiration) 1118 | if err != nil { 1119 | t.Error("Couldn't add foo even though it shouldn't exist") 1120 | } 1121 | err = tc.Add("foo", "baz", DefaultExpiration) 1122 | if err == nil { 1123 | t.Error("Successfully added another foo when it should have returned an error") 1124 | } 1125 | } 1126 | 1127 | func TestReplace(t *testing.T) { 1128 | tc := New(DefaultExpiration, 0) 1129 | err := tc.Replace("foo", "bar", DefaultExpiration) 1130 | if err == nil { 1131 | t.Error("Replaced foo when it shouldn't exist") 1132 | } 1133 | tc.Set("foo", "bar", DefaultExpiration) 1134 | err = tc.Replace("foo", "bar", DefaultExpiration) 1135 | if err != nil { 1136 | t.Error("Couldn't replace existing key foo") 1137 | } 1138 | } 1139 | 1140 | func TestDelete(t *testing.T) { 1141 | tc := New(DefaultExpiration, 0) 1142 | tc.Set("foo", "bar", DefaultExpiration) 1143 | tc.Delete("foo") 1144 | x, found := tc.Get("foo") 1145 | if found { 1146 | t.Error("foo was found, but it should have been deleted") 1147 | } 1148 | if x != nil { 1149 | t.Error("x is not nil:", x) 1150 | } 1151 | } 1152 | 1153 | func TestItemCount(t *testing.T) { 1154 | tc := New(DefaultExpiration, 0) 1155 | tc.Set("foo", "1", DefaultExpiration) 1156 | tc.Set("bar", "2", DefaultExpiration) 1157 | tc.Set("baz", "3", DefaultExpiration) 1158 | if n := tc.ItemCount(); n != 3 { 1159 | t.Errorf("Item count is not 3: %d", n) 1160 | } 1161 | } 1162 | 1163 | func TestFlush(t *testing.T) { 1164 | tc := New(DefaultExpiration, 0) 1165 | tc.Set("foo", "bar", DefaultExpiration) 1166 | tc.Set("baz", "yes", DefaultExpiration) 1167 | tc.Flush() 1168 | x, found := tc.Get("foo") 1169 | if found { 1170 | t.Error("foo was found, but it should have been deleted") 1171 | } 1172 | if x != nil { 1173 | t.Error("x is not nil:", x) 1174 | } 1175 | x, found = tc.Get("baz") 1176 | if found { 1177 | t.Error("baz was found, but it should have been deleted") 1178 | } 1179 | if x != nil { 1180 | t.Error("x is not nil:", x) 1181 | } 1182 | } 1183 | 1184 | func TestIncrementOverflowInt(t *testing.T) { 1185 | tc := New(DefaultExpiration, 0) 1186 | tc.Set("int8", int8(127), DefaultExpiration) 1187 | err := tc.Increment("int8", 1) 1188 | if err != nil { 1189 | t.Error("Error incrementing int8:", err) 1190 | } 1191 | x, _ := tc.Get("int8") 1192 | int8 := x.(int8) 1193 | if int8 != -128 { 1194 | t.Error("int8 did not overflow as expected; value:", int8) 1195 | } 1196 | 1197 | } 1198 | 1199 | func TestIncrementOverflowUint(t *testing.T) { 1200 | tc := New(DefaultExpiration, 0) 1201 | tc.Set("uint8", uint8(255), DefaultExpiration) 1202 | err := tc.Increment("uint8", 1) 1203 | if err != nil { 1204 | t.Error("Error incrementing int8:", err) 1205 | } 1206 | x, _ := tc.Get("uint8") 1207 | uint8 := x.(uint8) 1208 | if uint8 != 0 { 1209 | t.Error("uint8 did not overflow as expected; value:", uint8) 1210 | } 1211 | } 1212 | 1213 | func TestDecrementUnderflowUint(t *testing.T) { 1214 | tc := New(DefaultExpiration, 0) 1215 | tc.Set("uint8", uint8(0), DefaultExpiration) 1216 | err := tc.Decrement("uint8", 1) 1217 | if err != nil { 1218 | t.Error("Error decrementing int8:", err) 1219 | } 1220 | x, _ := tc.Get("uint8") 1221 | uint8 := x.(uint8) 1222 | if uint8 != 255 { 1223 | t.Error("uint8 did not underflow as expected; value:", uint8) 1224 | } 1225 | } 1226 | 1227 | func TestOnEvicted(t *testing.T) { 1228 | tc := New(DefaultExpiration, 0) 1229 | tc.Set("foo", 3, DefaultExpiration) 1230 | if tc.onEvicted != nil { 1231 | t.Fatal("tc.onEvicted is not nil") 1232 | } 1233 | works := false 1234 | tc.OnEvicted(func(k string, v interface{}) { 1235 | if k == "foo" && v.(int) == 3 { 1236 | works = true 1237 | } 1238 | tc.Set("bar", 4, DefaultExpiration) 1239 | }) 1240 | tc.Delete("foo") 1241 | x, _ := tc.Get("bar") 1242 | if !works { 1243 | t.Error("works bool not true") 1244 | } 1245 | if x.(int) != 4 { 1246 | t.Error("bar was not 4") 1247 | } 1248 | } 1249 | 1250 | func TestCacheSerialization(t *testing.T) { 1251 | tc := New(DefaultExpiration, 0) 1252 | testFillAndSerialize(t, tc) 1253 | 1254 | // Check if gob.Register behaves properly even after multiple gob.Register 1255 | // on c.Items (many of which will be the same type) 1256 | testFillAndSerialize(t, tc) 1257 | } 1258 | 1259 | func testFillAndSerialize(t *testing.T, tc *Cache) { 1260 | tc.Set("a", "a", DefaultExpiration) 1261 | tc.Set("b", "b", DefaultExpiration) 1262 | tc.Set("c", "c", DefaultExpiration) 1263 | tc.Set("expired", "foo", 1*time.Millisecond) 1264 | tc.Set("*struct", &TestStruct{Num: 1}, DefaultExpiration) 1265 | tc.Set("[]struct", []TestStruct{ 1266 | {Num: 2}, 1267 | {Num: 3}, 1268 | }, DefaultExpiration) 1269 | tc.Set("[]*struct", []*TestStruct{ 1270 | &TestStruct{Num: 4}, 1271 | &TestStruct{Num: 5}, 1272 | }, DefaultExpiration) 1273 | tc.Set("structception", &TestStruct{ 1274 | Num: 42, 1275 | Children: []*TestStruct{ 1276 | &TestStruct{Num: 6174}, 1277 | &TestStruct{Num: 4716}, 1278 | }, 1279 | }, DefaultExpiration) 1280 | 1281 | fp := &bytes.Buffer{} 1282 | err := tc.Save(fp) 1283 | if err != nil { 1284 | t.Fatal("Couldn't save cache to fp:", err) 1285 | } 1286 | 1287 | oc := New(DefaultExpiration, 0) 1288 | err = oc.Load(fp) 1289 | if err != nil { 1290 | t.Fatal("Couldn't load cache from fp:", err) 1291 | } 1292 | 1293 | a, found := oc.Get("a") 1294 | if !found { 1295 | t.Error("a was not found") 1296 | } 1297 | if a.(string) != "a" { 1298 | t.Error("a is not a") 1299 | } 1300 | 1301 | b, found := oc.Get("b") 1302 | if !found { 1303 | t.Error("b was not found") 1304 | } 1305 | if b.(string) != "b" { 1306 | t.Error("b is not b") 1307 | } 1308 | 1309 | c, found := oc.Get("c") 1310 | if !found { 1311 | t.Error("c was not found") 1312 | } 1313 | if c.(string) != "c" { 1314 | t.Error("c is not c") 1315 | } 1316 | 1317 | <-time.After(5 * time.Millisecond) 1318 | _, found = oc.Get("expired") 1319 | if found { 1320 | t.Error("expired was found") 1321 | } 1322 | 1323 | s1, found := oc.Get("*struct") 1324 | if !found { 1325 | t.Error("*struct was not found") 1326 | } 1327 | if s1.(*TestStruct).Num != 1 { 1328 | t.Error("*struct.Num is not 1") 1329 | } 1330 | 1331 | s2, found := oc.Get("[]struct") 1332 | if !found { 1333 | t.Error("[]struct was not found") 1334 | } 1335 | s2r := s2.([]TestStruct) 1336 | if len(s2r) != 2 { 1337 | t.Error("Length of s2r is not 2") 1338 | } 1339 | if s2r[0].Num != 2 { 1340 | t.Error("s2r[0].Num is not 2") 1341 | } 1342 | if s2r[1].Num != 3 { 1343 | t.Error("s2r[1].Num is not 3") 1344 | } 1345 | 1346 | s3, found := oc.get("[]*struct") 1347 | if !found { 1348 | t.Error("[]*struct was not found") 1349 | } 1350 | s3r := s3.([]*TestStruct) 1351 | if len(s3r) != 2 { 1352 | t.Error("Length of s3r is not 2") 1353 | } 1354 | if s3r[0].Num != 4 { 1355 | t.Error("s3r[0].Num is not 4") 1356 | } 1357 | if s3r[1].Num != 5 { 1358 | t.Error("s3r[1].Num is not 5") 1359 | } 1360 | 1361 | s4, found := oc.get("structception") 1362 | if !found { 1363 | t.Error("structception was not found") 1364 | } 1365 | s4r := s4.(*TestStruct) 1366 | if len(s4r.Children) != 2 { 1367 | t.Error("Length of s4r.Children is not 2") 1368 | } 1369 | if s4r.Children[0].Num != 6174 { 1370 | t.Error("s4r.Children[0].Num is not 6174") 1371 | } 1372 | if s4r.Children[1].Num != 4716 { 1373 | t.Error("s4r.Children[1].Num is not 4716") 1374 | } 1375 | } 1376 | 1377 | func TestFileSerialization(t *testing.T) { 1378 | tc := New(DefaultExpiration, 0) 1379 | tc.Add("a", "a", DefaultExpiration) 1380 | tc.Add("b", "b", DefaultExpiration) 1381 | f, err := ioutil.TempFile("", "go-cache-cache.dat") 1382 | if err != nil { 1383 | t.Fatal("Couldn't create cache file:", err) 1384 | } 1385 | fname := f.Name() 1386 | f.Close() 1387 | tc.SaveFile(fname) 1388 | 1389 | oc := New(DefaultExpiration, 0) 1390 | oc.Add("a", "aa", 0) // this should not be overwritten 1391 | err = oc.LoadFile(fname) 1392 | if err != nil { 1393 | t.Error(err) 1394 | } 1395 | a, found := oc.Get("a") 1396 | if !found { 1397 | t.Error("a was not found") 1398 | } 1399 | astr := a.(string) 1400 | if astr != "aa" { 1401 | if astr == "a" { 1402 | t.Error("a was overwritten") 1403 | } else { 1404 | t.Error("a is not aa") 1405 | } 1406 | } 1407 | b, found := oc.Get("b") 1408 | if !found { 1409 | t.Error("b was not found") 1410 | } 1411 | if b.(string) != "b" { 1412 | t.Error("b is not b") 1413 | } 1414 | } 1415 | 1416 | func TestSerializeUnserializable(t *testing.T) { 1417 | tc := New(DefaultExpiration, 0) 1418 | ch := make(chan bool, 1) 1419 | ch <- true 1420 | tc.Set("chan", ch, DefaultExpiration) 1421 | fp := &bytes.Buffer{} 1422 | err := tc.Save(fp) // this should fail gracefully 1423 | if err.Error() != "gob NewTypeObject can't handle type: chan bool" { 1424 | t.Error("Error from Save was not gob NewTypeObject can't handle type chan bool:", err) 1425 | } 1426 | } 1427 | 1428 | func BenchmarkCacheGetExpiring(b *testing.B) { 1429 | benchmarkCacheGet(b, 5*time.Minute) 1430 | } 1431 | 1432 | func BenchmarkCacheGetNotExpiring(b *testing.B) { 1433 | benchmarkCacheGet(b, NoExpiration) 1434 | } 1435 | 1436 | func benchmarkCacheGet(b *testing.B, exp time.Duration) { 1437 | b.StopTimer() 1438 | tc := New(exp, 0) 1439 | tc.Set("foo", "bar", DefaultExpiration) 1440 | b.StartTimer() 1441 | for i := 0; i < b.N; i++ { 1442 | tc.Get("foo") 1443 | } 1444 | } 1445 | 1446 | func BenchmarkRWMutexMapGet(b *testing.B) { 1447 | b.StopTimer() 1448 | m := map[string]string{ 1449 | "foo": "bar", 1450 | } 1451 | mu := sync.RWMutex{} 1452 | b.StartTimer() 1453 | for i := 0; i < b.N; i++ { 1454 | mu.RLock() 1455 | _, _ = m["foo"] 1456 | mu.RUnlock() 1457 | } 1458 | } 1459 | 1460 | func BenchmarkRWMutexInterfaceMapGetStruct(b *testing.B) { 1461 | b.StopTimer() 1462 | s := struct{ name string }{name: "foo"} 1463 | m := map[interface{}]string{ 1464 | s: "bar", 1465 | } 1466 | mu := sync.RWMutex{} 1467 | b.StartTimer() 1468 | for i := 0; i < b.N; i++ { 1469 | mu.RLock() 1470 | _, _ = m[s] 1471 | mu.RUnlock() 1472 | } 1473 | } 1474 | 1475 | func BenchmarkRWMutexInterfaceMapGetString(b *testing.B) { 1476 | b.StopTimer() 1477 | m := map[interface{}]string{ 1478 | "foo": "bar", 1479 | } 1480 | mu := sync.RWMutex{} 1481 | b.StartTimer() 1482 | for i := 0; i < b.N; i++ { 1483 | mu.RLock() 1484 | _, _ = m["foo"] 1485 | mu.RUnlock() 1486 | } 1487 | } 1488 | 1489 | func BenchmarkCacheGetConcurrentExpiring(b *testing.B) { 1490 | benchmarkCacheGetConcurrent(b, 5*time.Minute) 1491 | } 1492 | 1493 | func BenchmarkCacheGetConcurrentNotExpiring(b *testing.B) { 1494 | benchmarkCacheGetConcurrent(b, NoExpiration) 1495 | } 1496 | 1497 | func benchmarkCacheGetConcurrent(b *testing.B, exp time.Duration) { 1498 | b.StopTimer() 1499 | tc := New(exp, 0) 1500 | tc.Set("foo", "bar", DefaultExpiration) 1501 | wg := new(sync.WaitGroup) 1502 | workers := runtime.NumCPU() 1503 | each := b.N / workers 1504 | wg.Add(workers) 1505 | b.StartTimer() 1506 | for i := 0; i < workers; i++ { 1507 | go func() { 1508 | for j := 0; j < each; j++ { 1509 | tc.Get("foo") 1510 | } 1511 | wg.Done() 1512 | }() 1513 | } 1514 | wg.Wait() 1515 | } 1516 | 1517 | func BenchmarkRWMutexMapGetConcurrent(b *testing.B) { 1518 | b.StopTimer() 1519 | m := map[string]string{ 1520 | "foo": "bar", 1521 | } 1522 | mu := sync.RWMutex{} 1523 | wg := new(sync.WaitGroup) 1524 | workers := runtime.NumCPU() 1525 | each := b.N / workers 1526 | wg.Add(workers) 1527 | b.StartTimer() 1528 | for i := 0; i < workers; i++ { 1529 | go func() { 1530 | for j := 0; j < each; j++ { 1531 | mu.RLock() 1532 | _, _ = m["foo"] 1533 | mu.RUnlock() 1534 | } 1535 | wg.Done() 1536 | }() 1537 | } 1538 | wg.Wait() 1539 | } 1540 | 1541 | func BenchmarkCacheGetManyConcurrentExpiring(b *testing.B) { 1542 | benchmarkCacheGetManyConcurrent(b, 5*time.Minute) 1543 | } 1544 | 1545 | func BenchmarkCacheGetManyConcurrentNotExpiring(b *testing.B) { 1546 | benchmarkCacheGetManyConcurrent(b, NoExpiration) 1547 | } 1548 | 1549 | func benchmarkCacheGetManyConcurrent(b *testing.B, exp time.Duration) { 1550 | // This is the same as BenchmarkCacheGetConcurrent, but its result 1551 | // can be compared against BenchmarkShardedCacheGetManyConcurrent 1552 | // in sharded_test.go. 1553 | b.StopTimer() 1554 | n := 10000 1555 | tc := New(exp, 0) 1556 | keys := make([]string, n) 1557 | for i := 0; i < n; i++ { 1558 | k := "foo" + strconv.Itoa(i) 1559 | keys[i] = k 1560 | tc.Set(k, "bar", DefaultExpiration) 1561 | } 1562 | each := b.N / n 1563 | wg := new(sync.WaitGroup) 1564 | wg.Add(n) 1565 | for _, v := range keys { 1566 | go func(k string) { 1567 | for j := 0; j < each; j++ { 1568 | tc.Get(k) 1569 | } 1570 | wg.Done() 1571 | }(v) 1572 | } 1573 | b.StartTimer() 1574 | wg.Wait() 1575 | } 1576 | 1577 | func BenchmarkCacheSetExpiring(b *testing.B) { 1578 | benchmarkCacheSet(b, 5*time.Minute) 1579 | } 1580 | 1581 | func BenchmarkCacheSetNotExpiring(b *testing.B) { 1582 | benchmarkCacheSet(b, NoExpiration) 1583 | } 1584 | 1585 | func benchmarkCacheSet(b *testing.B, exp time.Duration) { 1586 | b.StopTimer() 1587 | tc := New(exp, 0) 1588 | b.StartTimer() 1589 | for i := 0; i < b.N; i++ { 1590 | tc.Set("foo", "bar", DefaultExpiration) 1591 | } 1592 | } 1593 | 1594 | func BenchmarkRWMutexMapSet(b *testing.B) { 1595 | b.StopTimer() 1596 | m := map[string]string{} 1597 | mu := sync.RWMutex{} 1598 | b.StartTimer() 1599 | for i := 0; i < b.N; i++ { 1600 | mu.Lock() 1601 | m["foo"] = "bar" 1602 | mu.Unlock() 1603 | } 1604 | } 1605 | 1606 | func BenchmarkCacheSetDelete(b *testing.B) { 1607 | b.StopTimer() 1608 | tc := New(DefaultExpiration, 0) 1609 | b.StartTimer() 1610 | for i := 0; i < b.N; i++ { 1611 | tc.Set("foo", "bar", DefaultExpiration) 1612 | tc.Delete("foo") 1613 | } 1614 | } 1615 | 1616 | func BenchmarkRWMutexMapSetDelete(b *testing.B) { 1617 | b.StopTimer() 1618 | m := map[string]string{} 1619 | mu := sync.RWMutex{} 1620 | b.StartTimer() 1621 | for i := 0; i < b.N; i++ { 1622 | mu.Lock() 1623 | m["foo"] = "bar" 1624 | mu.Unlock() 1625 | mu.Lock() 1626 | delete(m, "foo") 1627 | mu.Unlock() 1628 | } 1629 | } 1630 | 1631 | func BenchmarkCacheSetDeleteSingleLock(b *testing.B) { 1632 | b.StopTimer() 1633 | tc := New(DefaultExpiration, 0) 1634 | b.StartTimer() 1635 | for i := 0; i < b.N; i++ { 1636 | tc.mu.Lock() 1637 | tc.set("foo", "bar", DefaultExpiration) 1638 | tc.delete("foo") 1639 | tc.mu.Unlock() 1640 | } 1641 | } 1642 | 1643 | func BenchmarkRWMutexMapSetDeleteSingleLock(b *testing.B) { 1644 | b.StopTimer() 1645 | m := map[string]string{} 1646 | mu := sync.RWMutex{} 1647 | b.StartTimer() 1648 | for i := 0; i < b.N; i++ { 1649 | mu.Lock() 1650 | m["foo"] = "bar" 1651 | delete(m, "foo") 1652 | mu.Unlock() 1653 | } 1654 | } 1655 | 1656 | func BenchmarkIncrementInt(b *testing.B) { 1657 | b.StopTimer() 1658 | tc := New(DefaultExpiration, 0) 1659 | tc.Set("foo", 0, DefaultExpiration) 1660 | b.StartTimer() 1661 | for i := 0; i < b.N; i++ { 1662 | tc.IncrementInt("foo", 1) 1663 | } 1664 | } 1665 | 1666 | func BenchmarkDeleteExpiredLoop(b *testing.B) { 1667 | b.StopTimer() 1668 | tc := New(5*time.Minute, 0) 1669 | tc.mu.Lock() 1670 | for i := 0; i < 100000; i++ { 1671 | tc.set(strconv.Itoa(i), "bar", DefaultExpiration) 1672 | } 1673 | tc.mu.Unlock() 1674 | b.StartTimer() 1675 | for i := 0; i < b.N; i++ { 1676 | tc.DeleteExpired() 1677 | } 1678 | } 1679 | 1680 | func TestGetWithExpiration(t *testing.T) { 1681 | tc := New(DefaultExpiration, 0) 1682 | 1683 | a, expiration, found := tc.GetWithExpiration("a") 1684 | if found || a != nil || !expiration.IsZero() { 1685 | t.Error("Getting A found value that shouldn't exist:", a) 1686 | } 1687 | 1688 | b, expiration, found := tc.GetWithExpiration("b") 1689 | if found || b != nil || !expiration.IsZero() { 1690 | t.Error("Getting B found value that shouldn't exist:", b) 1691 | } 1692 | 1693 | c, expiration, found := tc.GetWithExpiration("c") 1694 | if found || c != nil || !expiration.IsZero() { 1695 | t.Error("Getting C found value that shouldn't exist:", c) 1696 | } 1697 | 1698 | tc.Set("a", 1, DefaultExpiration) 1699 | tc.Set("b", "b", DefaultExpiration) 1700 | tc.Set("c", 3.5, DefaultExpiration) 1701 | tc.Set("d", 1, NoExpiration) 1702 | tc.Set("e", 1, 50*time.Millisecond) 1703 | 1704 | x, expiration, found := tc.GetWithExpiration("a") 1705 | if !found { 1706 | t.Error("a was not found while getting a2") 1707 | } 1708 | if x == nil { 1709 | t.Error("x for a is nil") 1710 | } else if a2 := x.(int); a2+2 != 3 { 1711 | t.Error("a2 (which should be 1) plus 2 does not equal 3; value:", a2) 1712 | } 1713 | if !expiration.IsZero() { 1714 | t.Error("expiration for a is not a zeroed time") 1715 | } 1716 | 1717 | x, expiration, found = tc.GetWithExpiration("b") 1718 | if !found { 1719 | t.Error("b was not found while getting b2") 1720 | } 1721 | if x == nil { 1722 | t.Error("x for b is nil") 1723 | } else if b2 := x.(string); b2+"B" != "bB" { 1724 | t.Error("b2 (which should be b) plus B does not equal bB; value:", b2) 1725 | } 1726 | if !expiration.IsZero() { 1727 | t.Error("expiration for b is not a zeroed time") 1728 | } 1729 | 1730 | x, expiration, found = tc.GetWithExpiration("c") 1731 | if !found { 1732 | t.Error("c was not found while getting c2") 1733 | } 1734 | if x == nil { 1735 | t.Error("x for c is nil") 1736 | } else if c2 := x.(float64); c2+1.2 != 4.7 { 1737 | t.Error("c2 (which should be 3.5) plus 1.2 does not equal 4.7; value:", c2) 1738 | } 1739 | if !expiration.IsZero() { 1740 | t.Error("expiration for c is not a zeroed time") 1741 | } 1742 | 1743 | x, expiration, found = tc.GetWithExpiration("d") 1744 | if !found { 1745 | t.Error("d was not found while getting d2") 1746 | } 1747 | if x == nil { 1748 | t.Error("x for d is nil") 1749 | } else if d2 := x.(int); d2+2 != 3 { 1750 | t.Error("d (which should be 1) plus 2 does not equal 3; value:", d2) 1751 | } 1752 | if !expiration.IsZero() { 1753 | t.Error("expiration for d is not a zeroed time") 1754 | } 1755 | 1756 | x, expiration, found = tc.GetWithExpiration("e") 1757 | if !found { 1758 | t.Error("e was not found while getting e2") 1759 | } 1760 | if x == nil { 1761 | t.Error("x for e is nil") 1762 | } else if e2 := x.(int); e2+2 != 3 { 1763 | t.Error("e (which should be 1) plus 2 does not equal 3; value:", e2) 1764 | } 1765 | if expiration.UnixNano() != tc.items["e"].Expiration { 1766 | t.Error("expiration for e is not the correct time") 1767 | } 1768 | if expiration.UnixNano() < time.Now().UnixNano() { 1769 | t.Error("expiration for e is in the past") 1770 | } 1771 | } 1772 | -------------------------------------------------------------------------------- /sharded.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | "math/big" 7 | insecurerand "math/rand" 8 | "os" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | // This is an experimental and unexported (for now) attempt at making a cache 14 | // with better algorithmic complexity than the standard one, namely by 15 | // preventing write locks of the entire cache when an item is added. As of the 16 | // time of writing, the overhead of selecting buckets results in cache 17 | // operations being about twice as slow as for the standard cache with small 18 | // total cache sizes, and faster for larger ones. 19 | // 20 | // See cache_test.go for a few benchmarks. 21 | 22 | type unexportedShardedCache struct { 23 | *shardedCache 24 | } 25 | 26 | type shardedCache struct { 27 | seed uint32 28 | m uint32 29 | cs []*cache 30 | janitor *shardedJanitor 31 | } 32 | 33 | // djb2 with better shuffling. 5x faster than FNV with the hash.Hash overhead. 34 | func djb33(seed uint32, k string) uint32 { 35 | var ( 36 | l = uint32(len(k)) 37 | d = 5381 + seed + l 38 | i = uint32(0) 39 | ) 40 | // Why is all this 5x faster than a for loop? 41 | if l >= 4 { 42 | for i < l-4 { 43 | d = (d * 33) ^ uint32(k[i]) 44 | d = (d * 33) ^ uint32(k[i+1]) 45 | d = (d * 33) ^ uint32(k[i+2]) 46 | d = (d * 33) ^ uint32(k[i+3]) 47 | i += 4 48 | } 49 | } 50 | switch l - i { 51 | case 1: 52 | case 2: 53 | d = (d * 33) ^ uint32(k[i]) 54 | case 3: 55 | d = (d * 33) ^ uint32(k[i]) 56 | d = (d * 33) ^ uint32(k[i+1]) 57 | case 4: 58 | d = (d * 33) ^ uint32(k[i]) 59 | d = (d * 33) ^ uint32(k[i+1]) 60 | d = (d * 33) ^ uint32(k[i+2]) 61 | } 62 | return d ^ (d >> 16) 63 | } 64 | 65 | func (sc *shardedCache) bucket(k string) *cache { 66 | return sc.cs[djb33(sc.seed, k)%sc.m] 67 | } 68 | 69 | func (sc *shardedCache) Set(k string, x interface{}, d time.Duration) { 70 | sc.bucket(k).Set(k, x, d) 71 | } 72 | 73 | func (sc *shardedCache) Add(k string, x interface{}, d time.Duration) error { 74 | return sc.bucket(k).Add(k, x, d) 75 | } 76 | 77 | func (sc *shardedCache) Replace(k string, x interface{}, d time.Duration) error { 78 | return sc.bucket(k).Replace(k, x, d) 79 | } 80 | 81 | func (sc *shardedCache) Get(k string) (interface{}, bool) { 82 | return sc.bucket(k).Get(k) 83 | } 84 | 85 | func (sc *shardedCache) Increment(k string, n int64) error { 86 | return sc.bucket(k).Increment(k, n) 87 | } 88 | 89 | func (sc *shardedCache) IncrementFloat(k string, n float64) error { 90 | return sc.bucket(k).IncrementFloat(k, n) 91 | } 92 | 93 | func (sc *shardedCache) Decrement(k string, n int64) error { 94 | return sc.bucket(k).Decrement(k, n) 95 | } 96 | 97 | func (sc *shardedCache) Delete(k string) { 98 | sc.bucket(k).Delete(k) 99 | } 100 | 101 | func (sc *shardedCache) DeleteExpired() { 102 | for _, v := range sc.cs { 103 | v.DeleteExpired() 104 | } 105 | } 106 | 107 | // Returns the items in the cache. This may include items that have expired, 108 | // but have not yet been cleaned up. If this is significant, the Expiration 109 | // fields of the items should be checked. Note that explicit synchronization 110 | // is needed to use a cache and its corresponding Items() return values at 111 | // the same time, as the maps are shared. 112 | func (sc *shardedCache) Items() []map[string]Item { 113 | res := make([]map[string]Item, len(sc.cs)) 114 | for i, v := range sc.cs { 115 | res[i] = v.Items() 116 | } 117 | return res 118 | } 119 | 120 | func (sc *shardedCache) Flush() { 121 | for _, v := range sc.cs { 122 | v.Flush() 123 | } 124 | } 125 | 126 | type shardedJanitor struct { 127 | Interval time.Duration 128 | stop chan bool 129 | } 130 | 131 | func (j *shardedJanitor) Run(sc *shardedCache) { 132 | j.stop = make(chan bool) 133 | tick := time.Tick(j.Interval) 134 | for { 135 | select { 136 | case <-tick: 137 | sc.DeleteExpired() 138 | case <-j.stop: 139 | return 140 | } 141 | } 142 | } 143 | 144 | func stopShardedJanitor(sc *unexportedShardedCache) { 145 | sc.janitor.stop <- true 146 | } 147 | 148 | func runShardedJanitor(sc *shardedCache, ci time.Duration) { 149 | j := &shardedJanitor{ 150 | Interval: ci, 151 | } 152 | sc.janitor = j 153 | go j.Run(sc) 154 | } 155 | 156 | func newShardedCache(n int, de time.Duration) *shardedCache { 157 | max := big.NewInt(0).SetUint64(uint64(math.MaxUint32)) 158 | rnd, err := rand.Int(rand.Reader, max) 159 | var seed uint32 160 | if err != nil { 161 | os.Stderr.Write([]byte("WARNING: go-cache's newShardedCache failed to read from the system CSPRNG (/dev/urandom or equivalent.) Your system's security may be compromised. Continuing with an insecure seed.\n")) 162 | seed = insecurerand.Uint32() 163 | } else { 164 | seed = uint32(rnd.Uint64()) 165 | } 166 | sc := &shardedCache{ 167 | seed: seed, 168 | m: uint32(n), 169 | cs: make([]*cache, n), 170 | } 171 | for i := 0; i < n; i++ { 172 | c := &cache{ 173 | defaultExpiration: de, 174 | items: map[string]Item{}, 175 | } 176 | sc.cs[i] = c 177 | } 178 | return sc 179 | } 180 | 181 | func unexportedNewSharded(defaultExpiration, cleanupInterval time.Duration, shards int) *unexportedShardedCache { 182 | if defaultExpiration == 0 { 183 | defaultExpiration = -1 184 | } 185 | sc := newShardedCache(shards, defaultExpiration) 186 | SC := &unexportedShardedCache{sc} 187 | if cleanupInterval > 0 { 188 | runShardedJanitor(sc, cleanupInterval) 189 | runtime.SetFinalizer(SC, stopShardedJanitor) 190 | } 191 | return SC 192 | } 193 | -------------------------------------------------------------------------------- /sharded_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "strconv" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // func TestDjb33(t *testing.T) { 11 | // } 12 | 13 | var shardedKeys = []string{ 14 | "f", 15 | "fo", 16 | "foo", 17 | "barf", 18 | "barfo", 19 | "foobar", 20 | "bazbarf", 21 | "bazbarfo", 22 | "bazbarfoo", 23 | "foobarbazq", 24 | "foobarbazqu", 25 | "foobarbazquu", 26 | "foobarbazquux", 27 | } 28 | 29 | func TestShardedCache(t *testing.T) { 30 | tc := unexportedNewSharded(DefaultExpiration, 0, 13) 31 | for _, v := range shardedKeys { 32 | tc.Set(v, "value", DefaultExpiration) 33 | } 34 | } 35 | 36 | func BenchmarkShardedCacheGetExpiring(b *testing.B) { 37 | benchmarkShardedCacheGet(b, 5*time.Minute) 38 | } 39 | 40 | func BenchmarkShardedCacheGetNotExpiring(b *testing.B) { 41 | benchmarkShardedCacheGet(b, NoExpiration) 42 | } 43 | 44 | func benchmarkShardedCacheGet(b *testing.B, exp time.Duration) { 45 | b.StopTimer() 46 | tc := unexportedNewSharded(exp, 0, 10) 47 | tc.Set("foobarba", "zquux", DefaultExpiration) 48 | b.StartTimer() 49 | for i := 0; i < b.N; i++ { 50 | tc.Get("foobarba") 51 | } 52 | } 53 | 54 | func BenchmarkShardedCacheGetManyConcurrentExpiring(b *testing.B) { 55 | benchmarkShardedCacheGetManyConcurrent(b, 5*time.Minute) 56 | } 57 | 58 | func BenchmarkShardedCacheGetManyConcurrentNotExpiring(b *testing.B) { 59 | benchmarkShardedCacheGetManyConcurrent(b, NoExpiration) 60 | } 61 | 62 | func benchmarkShardedCacheGetManyConcurrent(b *testing.B, exp time.Duration) { 63 | b.StopTimer() 64 | n := 10000 65 | tsc := unexportedNewSharded(exp, 0, 20) 66 | keys := make([]string, n) 67 | for i := 0; i < n; i++ { 68 | k := "foo" + strconv.Itoa(i) 69 | keys[i] = k 70 | tsc.Set(k, "bar", DefaultExpiration) 71 | } 72 | each := b.N / n 73 | wg := new(sync.WaitGroup) 74 | wg.Add(n) 75 | for _, v := range keys { 76 | go func(k string) { 77 | for j := 0; j < each; j++ { 78 | tsc.Get(k) 79 | } 80 | wg.Done() 81 | }(v) 82 | } 83 | b.StartTimer() 84 | wg.Wait() 85 | } 86 | --------------------------------------------------------------------------------