mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-25 13:38:19 -05:00
switch to go vendoring
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Jun Kimura
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
# GCache
|
||||
|
||||

|
||||
[](https://pkg.go.dev/github.com/bluele/gcache?tab=doc)
|
||||
|
||||
Cache library for golang. It supports expirable Cache, LFU, LRU and ARC.
|
||||
|
||||
## Features
|
||||
|
||||
* Supports expirable Cache, LFU, LRU and ARC.
|
||||
|
||||
* Goroutine safe.
|
||||
|
||||
* Supports event handlers which evict, purge, and add entry. (Optional)
|
||||
|
||||
* Automatically load cache if it doesn't exists. (Optional)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
$ go get github.com/bluele/gcache
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
### Manually set a key-value pair.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bluele/gcache"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gc := gcache.New(20).
|
||||
LRU().
|
||||
Build()
|
||||
gc.Set("key", "ok")
|
||||
value, err := gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Get:", value)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Get: ok
|
||||
```
|
||||
|
||||
### Manually set a key-value pair, with an expiration time.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bluele/gcache"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gc := gcache.New(20).
|
||||
LRU().
|
||||
Build()
|
||||
gc.SetWithExpire("key", "ok", time.Second*10)
|
||||
value, _ := gc.Get("key")
|
||||
fmt.Println("Get:", value)
|
||||
|
||||
// Wait for value to expire
|
||||
time.Sleep(time.Second*10)
|
||||
|
||||
value, err = gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Get:", value)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Get: ok
|
||||
// 10 seconds later, new attempt:
|
||||
panic: ErrKeyNotFound
|
||||
```
|
||||
|
||||
|
||||
### Automatically load value
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/bluele/gcache"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gc := gcache.New(20).
|
||||
LRU().
|
||||
LoaderFunc(func(key interface{}) (interface{}, error) {
|
||||
return "ok", nil
|
||||
}).
|
||||
Build()
|
||||
value, err := gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Get:", value)
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Get: ok
|
||||
```
|
||||
|
||||
### Automatically load value with expiration
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var evictCounter, loaderCounter, purgeCounter int
|
||||
gc := gcache.New(20).
|
||||
LRU().
|
||||
LoaderExpireFunc(func(key interface{}) (interface{}, *time.Duration, error) {
|
||||
loaderCounter++
|
||||
expire := 1 * time.Second
|
||||
return "ok", &expire, nil
|
||||
}).
|
||||
EvictedFunc(func(key, value interface{}) {
|
||||
evictCounter++
|
||||
fmt.Println("evicted key:", key)
|
||||
}).
|
||||
PurgeVisitorFunc(func(key, value interface{}) {
|
||||
purgeCounter++
|
||||
fmt.Println("purged key:", key)
|
||||
}).
|
||||
Build()
|
||||
value, err := gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Get:", value)
|
||||
time.Sleep(1 * time.Second)
|
||||
value, err = gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Get:", value)
|
||||
gc.Purge()
|
||||
if loaderCounter != evictCounter+purgeCounter {
|
||||
panic("bad")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Get: ok
|
||||
evicted key: key
|
||||
Get: ok
|
||||
purged key: key
|
||||
```
|
||||
|
||||
|
||||
## Cache Algorithm
|
||||
|
||||
* Least-Frequently Used (LFU)
|
||||
|
||||
Discards the least frequently used items first.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// size: 10
|
||||
gc := gcache.New(10).
|
||||
LFU().
|
||||
Build()
|
||||
gc.Set("key", "value")
|
||||
}
|
||||
```
|
||||
|
||||
* Least Recently Used (LRU)
|
||||
|
||||
Discards the least recently used items first.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// size: 10
|
||||
gc := gcache.New(10).
|
||||
LRU().
|
||||
Build()
|
||||
gc.Set("key", "value")
|
||||
}
|
||||
```
|
||||
|
||||
* Adaptive Replacement Cache (ARC)
|
||||
|
||||
Constantly balances between LRU and LFU, to improve the combined result.
|
||||
|
||||
detail: http://en.wikipedia.org/wiki/Adaptive_replacement_cache
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// size: 10
|
||||
gc := gcache.New(10).
|
||||
ARC().
|
||||
Build()
|
||||
gc.Set("key", "value")
|
||||
}
|
||||
```
|
||||
|
||||
* SimpleCache (Default)
|
||||
|
||||
SimpleCache has no clear priority for evict cache. It depends on key-value map order.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// size: 10
|
||||
gc := gcache.New(10).Build()
|
||||
gc.Set("key", "value")
|
||||
v, err := gc.Get("key")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Loading Cache
|
||||
|
||||
If specified `LoaderFunc`, values are automatically loaded by the cache, and are stored in the cache until either evicted or manually invalidated.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
gc := gcache.New(10).
|
||||
LRU().
|
||||
LoaderFunc(func(key interface{}) (interface{}, error) {
|
||||
return "value", nil
|
||||
}).
|
||||
Build()
|
||||
v, _ := gc.Get("key")
|
||||
// output: "value"
|
||||
fmt.Println(v)
|
||||
}
|
||||
```
|
||||
|
||||
GCache coordinates cache fills such that only one load in one process of an entire replicated set of processes populates the cache, then multiplexes the loaded value to all callers.
|
||||
|
||||
## Expirable cache
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// LRU cache, size: 10, expiration: after a hour
|
||||
gc := gcache.New(10).
|
||||
LRU().
|
||||
Expiration(time.Hour).
|
||||
Build()
|
||||
}
|
||||
```
|
||||
|
||||
## Event handlers
|
||||
|
||||
### Evicted handler
|
||||
|
||||
Event handler for evict the entry.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
gc := gcache.New(2).
|
||||
EvictedFunc(func(key, value interface{}) {
|
||||
fmt.Println("evicted key:", key)
|
||||
}).
|
||||
Build()
|
||||
for i := 0; i < 3; i++ {
|
||||
gc.Set(i, i*i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
evicted key: 0
|
||||
```
|
||||
|
||||
### Added handler
|
||||
|
||||
Event handler for add the entry.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
gc := gcache.New(2).
|
||||
AddedFunc(func(key, value interface{}) {
|
||||
fmt.Println("added key:", key)
|
||||
}).
|
||||
Build()
|
||||
for i := 0; i < 3; i++ {
|
||||
gc.Set(i, i*i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
added key: 0
|
||||
added key: 1
|
||||
added key: 2
|
||||
```
|
||||
|
||||
# Author
|
||||
|
||||
**Jun Kimura**
|
||||
|
||||
* <http://github.com/bluele>
|
||||
* <junkxdev@gmail.com>
|
||||
+456
@@ -0,0 +1,456 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constantly balances between LRU and LFU, to improve the combined result.
|
||||
type ARC struct {
|
||||
baseCache
|
||||
items map[interface{}]*arcItem
|
||||
|
||||
part int
|
||||
t1 *arcList
|
||||
t2 *arcList
|
||||
b1 *arcList
|
||||
b2 *arcList
|
||||
}
|
||||
|
||||
func newARC(cb *CacheBuilder) *ARC {
|
||||
c := &ARC{}
|
||||
buildCache(&c.baseCache, cb)
|
||||
|
||||
c.init()
|
||||
c.loadGroup.cache = c
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ARC) init() {
|
||||
c.items = make(map[interface{}]*arcItem)
|
||||
c.t1 = newARCList()
|
||||
c.t2 = newARCList()
|
||||
c.b1 = newARCList()
|
||||
c.b2 = newARCList()
|
||||
}
|
||||
|
||||
func (c *ARC) replace(key interface{}) {
|
||||
if !c.isCacheFull() {
|
||||
return
|
||||
}
|
||||
var old interface{}
|
||||
if c.t1.Len() > 0 && ((c.b2.Has(key) && c.t1.Len() == c.part) || (c.t1.Len() > c.part)) {
|
||||
old = c.t1.RemoveTail()
|
||||
c.b1.PushFront(old)
|
||||
} else if c.t2.Len() > 0 {
|
||||
old = c.t2.RemoveTail()
|
||||
c.b2.PushFront(old)
|
||||
} else {
|
||||
old = c.t1.RemoveTail()
|
||||
c.b1.PushFront(old)
|
||||
}
|
||||
item, ok := c.items[old]
|
||||
if ok {
|
||||
delete(c.items, old)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ARC) Set(key, value interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_, err := c.set(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a new key-value pair with an expiration time
|
||||
func (c *ARC) SetWithExpire(key, value interface{}, expiration time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := c.clock.Now().Add(expiration)
|
||||
item.(*arcItem).expiration = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ARC) set(key, value interface{}) (interface{}, error) {
|
||||
var err error
|
||||
if c.serializeFunc != nil {
|
||||
value, err = c.serializeFunc(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
item.value = value
|
||||
} else {
|
||||
item = &arcItem{
|
||||
clock: c.clock,
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
c.items[key] = item
|
||||
}
|
||||
|
||||
if c.expiration != nil {
|
||||
t := c.clock.Now().Add(*c.expiration)
|
||||
item.expiration = &t
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if c.addedFunc != nil {
|
||||
c.addedFunc(key, value)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.t1.Has(key) || c.t2.Has(key) {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
if elt := c.b1.Lookup(key); elt != nil {
|
||||
c.setPart(minInt(c.size, c.part+maxInt(c.b2.Len()/c.b1.Len(), 1)))
|
||||
c.replace(key)
|
||||
c.b1.Remove(key, elt)
|
||||
c.t2.PushFront(key)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
if elt := c.b2.Lookup(key); elt != nil {
|
||||
c.setPart(maxInt(0, c.part-maxInt(c.b1.Len()/c.b2.Len(), 1)))
|
||||
c.replace(key)
|
||||
c.b2.Remove(key, elt)
|
||||
c.t2.PushFront(key)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
if c.isCacheFull() && c.t1.Len()+c.b1.Len() == c.size {
|
||||
if c.t1.Len() < c.size {
|
||||
c.b1.RemoveTail()
|
||||
c.replace(key)
|
||||
} else {
|
||||
pop := c.t1.RemoveTail()
|
||||
item, ok := c.items[pop]
|
||||
if ok {
|
||||
delete(c.items, pop)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
total := c.t1.Len() + c.b1.Len() + c.t2.Len() + c.b2.Len()
|
||||
if total >= c.size {
|
||||
if total == (2 * c.size) {
|
||||
if c.b2.Len() > 0 {
|
||||
c.b2.RemoveTail()
|
||||
} else {
|
||||
c.b1.RemoveTail()
|
||||
}
|
||||
}
|
||||
c.replace(key)
|
||||
}
|
||||
}
|
||||
c.t1.PushFront(key)
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// Get a value from cache pool using key if it exists. If not exists and it has LoaderFunc, it will generate the value using you have specified LoaderFunc method returns value.
|
||||
func (c *ARC) Get(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, true)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetIFPresent gets a value from cache pool using key if it exists.
|
||||
// If it dose not exists key, returns KeyNotFoundError.
|
||||
// And send a request which refresh value for specified key if cache object has LoaderFunc.
|
||||
func (c *ARC) GetIFPresent(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, false)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *ARC) get(key interface{}, onLoad bool) (interface{}, error) {
|
||||
v, err := c.getValue(key, onLoad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.deserializeFunc != nil {
|
||||
return c.deserializeFunc(key, v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *ARC) getValue(key interface{}, onLoad bool) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if elt := c.t1.Lookup(key); elt != nil {
|
||||
c.t1.Remove(key, elt)
|
||||
item := c.items[key]
|
||||
if !item.IsExpired(nil) {
|
||||
c.t2.PushFront(key)
|
||||
if !onLoad {
|
||||
c.stats.IncrHitCount()
|
||||
}
|
||||
return item.value, nil
|
||||
} else {
|
||||
delete(c.items, key)
|
||||
c.b1.PushFront(key)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if elt := c.t2.Lookup(key); elt != nil {
|
||||
item := c.items[key]
|
||||
if !item.IsExpired(nil) {
|
||||
c.t2.MoveToFront(elt)
|
||||
if !onLoad {
|
||||
c.stats.IncrHitCount()
|
||||
}
|
||||
return item.value, nil
|
||||
} else {
|
||||
delete(c.items, key)
|
||||
c.t2.Remove(key, elt)
|
||||
c.b2.PushFront(key)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !onLoad {
|
||||
c.stats.IncrMissCount()
|
||||
}
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
|
||||
func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
|
||||
if c.loaderExpireFunc == nil {
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expiration != nil {
|
||||
t := c.clock.Now().Add(*expiration)
|
||||
item.(*arcItem).expiration = &t
|
||||
}
|
||||
return v, nil
|
||||
}, isWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Has checks if key exists in cache
|
||||
func (c *ARC) Has(key interface{}) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
now := time.Now()
|
||||
return c.has(key, &now)
|
||||
}
|
||||
|
||||
func (c *ARC) has(key interface{}, now *time.Time) bool {
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !item.IsExpired(now)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *ARC) Remove(key interface{}) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.remove(key)
|
||||
}
|
||||
|
||||
func (c *ARC) remove(key interface{}) bool {
|
||||
if elt := c.t1.Lookup(key); elt != nil {
|
||||
c.t1.Remove(key, elt)
|
||||
item := c.items[key]
|
||||
delete(c.items, key)
|
||||
c.b1.PushFront(key)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(key, item.value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if elt := c.t2.Lookup(key); elt != nil {
|
||||
c.t2.Remove(key, elt)
|
||||
item := c.items[key]
|
||||
delete(c.items, key)
|
||||
c.b2.PushFront(key)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(key, item.value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetALL returns all key-value pairs in the cache.
|
||||
func (c *ARC) GetALL(checkExpired bool) map[interface{}]interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
items := make(map[interface{}]interface{}, len(c.items))
|
||||
now := time.Now()
|
||||
for k, item := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
items[k] = item.value
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
func (c *ARC) Keys(checkExpired bool) []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, 0, len(c.items))
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *ARC) Len(checkExpired bool) int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if !checkExpired {
|
||||
return len(c.items)
|
||||
}
|
||||
var length int
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if c.has(k, &now) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
// Purge is used to completely clear the cache
|
||||
func (c *ARC) Purge() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.purgeVisitorFunc != nil {
|
||||
for _, item := range c.items {
|
||||
c.purgeVisitorFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
|
||||
c.init()
|
||||
}
|
||||
|
||||
func (c *ARC) setPart(p int) {
|
||||
if c.isCacheFull() {
|
||||
c.part = p
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ARC) isCacheFull() bool {
|
||||
return (c.t1.Len() + c.t2.Len()) == c.size
|
||||
}
|
||||
|
||||
// IsExpired returns boolean value whether this item is expired or not.
|
||||
func (it *arcItem) IsExpired(now *time.Time) bool {
|
||||
if it.expiration == nil {
|
||||
return false
|
||||
}
|
||||
if now == nil {
|
||||
t := it.clock.Now()
|
||||
now = &t
|
||||
}
|
||||
return it.expiration.Before(*now)
|
||||
}
|
||||
|
||||
type arcList struct {
|
||||
l *list.List
|
||||
keys map[interface{}]*list.Element
|
||||
}
|
||||
|
||||
type arcItem struct {
|
||||
clock Clock
|
||||
key interface{}
|
||||
value interface{}
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
func newARCList() *arcList {
|
||||
return &arcList{
|
||||
l: list.New(),
|
||||
keys: make(map[interface{}]*list.Element),
|
||||
}
|
||||
}
|
||||
|
||||
func (al *arcList) Has(key interface{}) bool {
|
||||
_, ok := al.keys[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (al *arcList) Lookup(key interface{}) *list.Element {
|
||||
elt := al.keys[key]
|
||||
return elt
|
||||
}
|
||||
|
||||
func (al *arcList) MoveToFront(elt *list.Element) {
|
||||
al.l.MoveToFront(elt)
|
||||
}
|
||||
|
||||
func (al *arcList) PushFront(key interface{}) {
|
||||
if elt, ok := al.keys[key]; ok {
|
||||
al.l.MoveToFront(elt)
|
||||
return
|
||||
}
|
||||
elt := al.l.PushFront(key)
|
||||
al.keys[key] = elt
|
||||
}
|
||||
|
||||
func (al *arcList) Remove(key interface{}, elt *list.Element) {
|
||||
delete(al.keys, key)
|
||||
al.l.Remove(elt)
|
||||
}
|
||||
|
||||
func (al *arcList) RemoveTail() interface{} {
|
||||
elt := al.l.Back()
|
||||
al.l.Remove(elt)
|
||||
|
||||
key := elt.Value
|
||||
delete(al.keys, key)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func (al *arcList) Len() int {
|
||||
return al.l.Len()
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
TYPE_SIMPLE = "simple"
|
||||
TYPE_LRU = "lru"
|
||||
TYPE_LFU = "lfu"
|
||||
TYPE_ARC = "arc"
|
||||
)
|
||||
|
||||
var KeyNotFoundError = errors.New("Key not found.")
|
||||
|
||||
type Cache interface {
|
||||
Set(key, value interface{}) error
|
||||
SetWithExpire(key, value interface{}, expiration time.Duration) error
|
||||
Get(key interface{}) (interface{}, error)
|
||||
GetIFPresent(key interface{}) (interface{}, error)
|
||||
GetALL(checkExpired bool) map[interface{}]interface{}
|
||||
get(key interface{}, onLoad bool) (interface{}, error)
|
||||
Remove(key interface{}) bool
|
||||
Purge()
|
||||
Keys(checkExpired bool) []interface{}
|
||||
Len(checkExpired bool) int
|
||||
Has(key interface{}) bool
|
||||
|
||||
statsAccessor
|
||||
}
|
||||
|
||||
type baseCache struct {
|
||||
clock Clock
|
||||
size int
|
||||
loaderExpireFunc LoaderExpireFunc
|
||||
evictedFunc EvictedFunc
|
||||
purgeVisitorFunc PurgeVisitorFunc
|
||||
addedFunc AddedFunc
|
||||
deserializeFunc DeserializeFunc
|
||||
serializeFunc SerializeFunc
|
||||
expiration *time.Duration
|
||||
mu sync.RWMutex
|
||||
loadGroup Group
|
||||
*stats
|
||||
}
|
||||
|
||||
type (
|
||||
LoaderFunc func(interface{}) (interface{}, error)
|
||||
LoaderExpireFunc func(interface{}) (interface{}, *time.Duration, error)
|
||||
EvictedFunc func(interface{}, interface{})
|
||||
PurgeVisitorFunc func(interface{}, interface{})
|
||||
AddedFunc func(interface{}, interface{})
|
||||
DeserializeFunc func(interface{}, interface{}) (interface{}, error)
|
||||
SerializeFunc func(interface{}, interface{}) (interface{}, error)
|
||||
)
|
||||
|
||||
type CacheBuilder struct {
|
||||
clock Clock
|
||||
tp string
|
||||
size int
|
||||
loaderExpireFunc LoaderExpireFunc
|
||||
evictedFunc EvictedFunc
|
||||
purgeVisitorFunc PurgeVisitorFunc
|
||||
addedFunc AddedFunc
|
||||
expiration *time.Duration
|
||||
deserializeFunc DeserializeFunc
|
||||
serializeFunc SerializeFunc
|
||||
}
|
||||
|
||||
func New(size int) *CacheBuilder {
|
||||
return &CacheBuilder{
|
||||
clock: NewRealClock(),
|
||||
tp: TYPE_SIMPLE,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) Clock(clock Clock) *CacheBuilder {
|
||||
cb.clock = clock
|
||||
return cb
|
||||
}
|
||||
|
||||
// Set a loader function.
|
||||
// loaderFunc: create a new value with this function if cached value is expired.
|
||||
func (cb *CacheBuilder) LoaderFunc(loaderFunc LoaderFunc) *CacheBuilder {
|
||||
cb.loaderExpireFunc = func(k interface{}) (interface{}, *time.Duration, error) {
|
||||
v, err := loaderFunc(k)
|
||||
return v, nil, err
|
||||
}
|
||||
return cb
|
||||
}
|
||||
|
||||
// Set a loader function with expiration.
|
||||
// loaderExpireFunc: create a new value with this function if cached value is expired.
|
||||
// If nil returned instead of time.Duration from loaderExpireFunc than value will never expire.
|
||||
func (cb *CacheBuilder) LoaderExpireFunc(loaderExpireFunc LoaderExpireFunc) *CacheBuilder {
|
||||
cb.loaderExpireFunc = loaderExpireFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) EvictType(tp string) *CacheBuilder {
|
||||
cb.tp = tp
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) Simple() *CacheBuilder {
|
||||
return cb.EvictType(TYPE_SIMPLE)
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) LRU() *CacheBuilder {
|
||||
return cb.EvictType(TYPE_LRU)
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) LFU() *CacheBuilder {
|
||||
return cb.EvictType(TYPE_LFU)
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) ARC() *CacheBuilder {
|
||||
return cb.EvictType(TYPE_ARC)
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) EvictedFunc(evictedFunc EvictedFunc) *CacheBuilder {
|
||||
cb.evictedFunc = evictedFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) PurgeVisitorFunc(purgeVisitorFunc PurgeVisitorFunc) *CacheBuilder {
|
||||
cb.purgeVisitorFunc = purgeVisitorFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) AddedFunc(addedFunc AddedFunc) *CacheBuilder {
|
||||
cb.addedFunc = addedFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) DeserializeFunc(deserializeFunc DeserializeFunc) *CacheBuilder {
|
||||
cb.deserializeFunc = deserializeFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) SerializeFunc(serializeFunc SerializeFunc) *CacheBuilder {
|
||||
cb.serializeFunc = serializeFunc
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) Expiration(expiration time.Duration) *CacheBuilder {
|
||||
cb.expiration = &expiration
|
||||
return cb
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) Build() Cache {
|
||||
if cb.size <= 0 && cb.tp != TYPE_SIMPLE {
|
||||
panic("gcache: Cache size <= 0")
|
||||
}
|
||||
|
||||
return cb.build()
|
||||
}
|
||||
|
||||
func (cb *CacheBuilder) build() Cache {
|
||||
switch cb.tp {
|
||||
case TYPE_SIMPLE:
|
||||
return newSimpleCache(cb)
|
||||
case TYPE_LRU:
|
||||
return newLRUCache(cb)
|
||||
case TYPE_LFU:
|
||||
return newLFUCache(cb)
|
||||
case TYPE_ARC:
|
||||
return newARC(cb)
|
||||
default:
|
||||
panic("gcache: Unknown type " + cb.tp)
|
||||
}
|
||||
}
|
||||
|
||||
func buildCache(c *baseCache, cb *CacheBuilder) {
|
||||
c.clock = cb.clock
|
||||
c.size = cb.size
|
||||
c.loaderExpireFunc = cb.loaderExpireFunc
|
||||
c.expiration = cb.expiration
|
||||
c.addedFunc = cb.addedFunc
|
||||
c.deserializeFunc = cb.deserializeFunc
|
||||
c.serializeFunc = cb.serializeFunc
|
||||
c.evictedFunc = cb.evictedFunc
|
||||
c.purgeVisitorFunc = cb.purgeVisitorFunc
|
||||
c.stats = &stats{}
|
||||
}
|
||||
|
||||
// load a new value using by specified key.
|
||||
func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, error) (interface{}, error), isWait bool) (interface{}, bool, error) {
|
||||
v, called, err := c.loadGroup.Do(key, func() (v interface{}, e error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
e = fmt.Errorf("Loader panics: %v", r)
|
||||
}
|
||||
}()
|
||||
return cb(c.loaderExpireFunc(key))
|
||||
}, isWait)
|
||||
if err != nil {
|
||||
return nil, called, err
|
||||
}
|
||||
return v, called, nil
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type RealClock struct{}
|
||||
|
||||
func NewRealClock() Clock {
|
||||
return RealClock{}
|
||||
}
|
||||
|
||||
func (rc RealClock) Now() time.Time {
|
||||
t := time.Now()
|
||||
return t
|
||||
}
|
||||
|
||||
type FakeClock interface {
|
||||
Clock
|
||||
|
||||
Advance(d time.Duration)
|
||||
}
|
||||
|
||||
func NewFakeClock() FakeClock {
|
||||
return &fakeclock{
|
||||
// Taken from github.com/jonboulle/clockwork: use a fixture that does not fulfill Time.IsZero()
|
||||
now: time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
type fakeclock struct {
|
||||
now time.Time
|
||||
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (fc *fakeclock) Now() time.Time {
|
||||
fc.mutex.RLock()
|
||||
defer fc.mutex.RUnlock()
|
||||
t := fc.now
|
||||
return t
|
||||
}
|
||||
|
||||
func (fc *fakeclock) Advance(d time.Duration) {
|
||||
fc.mutex.Lock()
|
||||
defer fc.mutex.Unlock()
|
||||
fc.now = fc.now.Add(d)
|
||||
}
|
||||
+377
@@ -0,0 +1,377 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Discards the least frequently used items first.
|
||||
type LFUCache struct {
|
||||
baseCache
|
||||
items map[interface{}]*lfuItem
|
||||
freqList *list.List // list for freqEntry
|
||||
}
|
||||
|
||||
var _ Cache = (*LFUCache)(nil)
|
||||
|
||||
type lfuItem struct {
|
||||
clock Clock
|
||||
key interface{}
|
||||
value interface{}
|
||||
freqElement *list.Element
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
type freqEntry struct {
|
||||
freq uint
|
||||
items map[*lfuItem]struct{}
|
||||
}
|
||||
|
||||
func newLFUCache(cb *CacheBuilder) *LFUCache {
|
||||
c := &LFUCache{}
|
||||
buildCache(&c.baseCache, cb)
|
||||
|
||||
c.init()
|
||||
c.loadGroup.cache = c
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *LFUCache) init() {
|
||||
c.freqList = list.New()
|
||||
c.items = make(map[interface{}]*lfuItem, c.size)
|
||||
c.freqList.PushFront(&freqEntry{
|
||||
freq: 0,
|
||||
items: make(map[*lfuItem]struct{}),
|
||||
})
|
||||
}
|
||||
|
||||
// Set a new key-value pair
|
||||
func (c *LFUCache) Set(key, value interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_, err := c.set(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a new key-value pair with an expiration time
|
||||
func (c *LFUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := c.clock.Now().Add(expiration)
|
||||
item.(*lfuItem).expiration = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *LFUCache) set(key, value interface{}) (interface{}, error) {
|
||||
var err error
|
||||
if c.serializeFunc != nil {
|
||||
value, err = c.serializeFunc(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing item
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
item.value = value
|
||||
} else {
|
||||
// Verify size not exceeded
|
||||
if len(c.items) >= c.size {
|
||||
c.evict(1)
|
||||
}
|
||||
item = &lfuItem{
|
||||
clock: c.clock,
|
||||
key: key,
|
||||
value: value,
|
||||
freqElement: nil,
|
||||
}
|
||||
el := c.freqList.Front()
|
||||
fe := el.Value.(*freqEntry)
|
||||
fe.items[item] = struct{}{}
|
||||
|
||||
item.freqElement = el
|
||||
c.items[key] = item
|
||||
}
|
||||
|
||||
if c.expiration != nil {
|
||||
t := c.clock.Now().Add(*c.expiration)
|
||||
item.expiration = &t
|
||||
}
|
||||
|
||||
if c.addedFunc != nil {
|
||||
c.addedFunc(key, value)
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// Get a value from cache pool using key if it exists.
|
||||
// If it dose not exists key and has LoaderFunc,
|
||||
// generate a value using `LoaderFunc` method returns value.
|
||||
func (c *LFUCache) Get(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, true)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetIFPresent gets a value from cache pool using key if it exists.
|
||||
// If it dose not exists key, returns KeyNotFoundError.
|
||||
// And send a request which refresh value for specified key if cache object has LoaderFunc.
|
||||
func (c *LFUCache) GetIFPresent(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, false)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *LFUCache) get(key interface{}, onLoad bool) (interface{}, error) {
|
||||
v, err := c.getValue(key, onLoad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.deserializeFunc != nil {
|
||||
return c.deserializeFunc(key, v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *LFUCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
if !item.IsExpired(nil) {
|
||||
c.increment(item)
|
||||
v := item.value
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrHitCount()
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
c.removeItem(item)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrMissCount()
|
||||
}
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
|
||||
func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
|
||||
if c.loaderExpireFunc == nil {
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expiration != nil {
|
||||
t := c.clock.Now().Add(*expiration)
|
||||
item.(*lfuItem).expiration = &t
|
||||
}
|
||||
return v, nil
|
||||
}, isWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (c *LFUCache) increment(item *lfuItem) {
|
||||
currentFreqElement := item.freqElement
|
||||
currentFreqEntry := currentFreqElement.Value.(*freqEntry)
|
||||
nextFreq := currentFreqEntry.freq + 1
|
||||
delete(currentFreqEntry.items, item)
|
||||
|
||||
// a boolean whether reuse the empty current entry
|
||||
removable := isRemovableFreqEntry(currentFreqEntry)
|
||||
|
||||
// insert item into a valid entry
|
||||
nextFreqElement := currentFreqElement.Next()
|
||||
switch {
|
||||
case nextFreqElement == nil || nextFreqElement.Value.(*freqEntry).freq > nextFreq:
|
||||
if removable {
|
||||
currentFreqEntry.freq = nextFreq
|
||||
nextFreqElement = currentFreqElement
|
||||
} else {
|
||||
nextFreqElement = c.freqList.InsertAfter(&freqEntry{
|
||||
freq: nextFreq,
|
||||
items: make(map[*lfuItem]struct{}),
|
||||
}, currentFreqElement)
|
||||
}
|
||||
case nextFreqElement.Value.(*freqEntry).freq == nextFreq:
|
||||
if removable {
|
||||
c.freqList.Remove(currentFreqElement)
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
nextFreqElement.Value.(*freqEntry).items[item] = struct{}{}
|
||||
item.freqElement = nextFreqElement
|
||||
}
|
||||
|
||||
// evict removes the least frequence item from the cache.
|
||||
func (c *LFUCache) evict(count int) {
|
||||
entry := c.freqList.Front()
|
||||
for i := 0; i < count; {
|
||||
if entry == nil {
|
||||
return
|
||||
} else {
|
||||
for item := range entry.Value.(*freqEntry).items {
|
||||
if i >= count {
|
||||
return
|
||||
}
|
||||
c.removeItem(item)
|
||||
i++
|
||||
}
|
||||
entry = entry.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has checks if key exists in cache
|
||||
func (c *LFUCache) Has(key interface{}) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
now := time.Now()
|
||||
return c.has(key, &now)
|
||||
}
|
||||
|
||||
func (c *LFUCache) has(key interface{}, now *time.Time) bool {
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !item.IsExpired(now)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *LFUCache) Remove(key interface{}) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.remove(key)
|
||||
}
|
||||
|
||||
func (c *LFUCache) remove(key interface{}) bool {
|
||||
if item, ok := c.items[key]; ok {
|
||||
c.removeItem(item)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// removeElement is used to remove a given list element from the cache
|
||||
func (c *LFUCache) removeItem(item *lfuItem) {
|
||||
entry := item.freqElement.Value.(*freqEntry)
|
||||
delete(c.items, item.key)
|
||||
delete(entry.items, item)
|
||||
if isRemovableFreqEntry(entry) {
|
||||
c.freqList.Remove(item.freqElement)
|
||||
}
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(item.key, item.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LFUCache) keys() []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, len(c.items))
|
||||
var i = 0
|
||||
for k := range c.items {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// GetALL returns all key-value pairs in the cache.
|
||||
func (c *LFUCache) GetALL(checkExpired bool) map[interface{}]interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
items := make(map[interface{}]interface{}, len(c.items))
|
||||
now := time.Now()
|
||||
for k, item := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
items[k] = item.value
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
func (c *LFUCache) Keys(checkExpired bool) []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, 0, len(c.items))
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *LFUCache) Len(checkExpired bool) int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if !checkExpired {
|
||||
return len(c.items)
|
||||
}
|
||||
var length int
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if c.has(k, &now) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
// Completely clear the cache
|
||||
func (c *LFUCache) Purge() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.purgeVisitorFunc != nil {
|
||||
for key, item := range c.items {
|
||||
c.purgeVisitorFunc(key, item.value)
|
||||
}
|
||||
}
|
||||
|
||||
c.init()
|
||||
}
|
||||
|
||||
// IsExpired returns boolean value whether this item is expired or not.
|
||||
func (it *lfuItem) IsExpired(now *time.Time) bool {
|
||||
if it.expiration == nil {
|
||||
return false
|
||||
}
|
||||
if now == nil {
|
||||
t := it.clock.Now()
|
||||
now = &t
|
||||
}
|
||||
return it.expiration.Before(*now)
|
||||
}
|
||||
|
||||
func isRemovableFreqEntry(entry *freqEntry) bool {
|
||||
return entry.freq != 0 && len(entry.items) == 0
|
||||
}
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Discards the least recently used items first.
|
||||
type LRUCache struct {
|
||||
baseCache
|
||||
items map[interface{}]*list.Element
|
||||
evictList *list.List
|
||||
}
|
||||
|
||||
func newLRUCache(cb *CacheBuilder) *LRUCache {
|
||||
c := &LRUCache{}
|
||||
buildCache(&c.baseCache, cb)
|
||||
|
||||
c.init()
|
||||
c.loadGroup.cache = c
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *LRUCache) init() {
|
||||
c.evictList = list.New()
|
||||
c.items = make(map[interface{}]*list.Element, c.size+1)
|
||||
}
|
||||
|
||||
func (c *LRUCache) set(key, value interface{}) (interface{}, error) {
|
||||
var err error
|
||||
if c.serializeFunc != nil {
|
||||
value, err = c.serializeFunc(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing item
|
||||
var item *lruItem
|
||||
if it, ok := c.items[key]; ok {
|
||||
c.evictList.MoveToFront(it)
|
||||
item = it.Value.(*lruItem)
|
||||
item.value = value
|
||||
} else {
|
||||
// Verify size not exceeded
|
||||
if c.evictList.Len() >= c.size {
|
||||
c.evict(1)
|
||||
}
|
||||
item = &lruItem{
|
||||
clock: c.clock,
|
||||
key: key,
|
||||
value: value,
|
||||
}
|
||||
c.items[key] = c.evictList.PushFront(item)
|
||||
}
|
||||
|
||||
if c.expiration != nil {
|
||||
t := c.clock.Now().Add(*c.expiration)
|
||||
item.expiration = &t
|
||||
}
|
||||
|
||||
if c.addedFunc != nil {
|
||||
c.addedFunc(key, value)
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// set a new key-value pair
|
||||
func (c *LRUCache) Set(key, value interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_, err := c.set(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a new key-value pair with an expiration time
|
||||
func (c *LRUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := c.clock.Now().Add(expiration)
|
||||
item.(*lruItem).expiration = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get a value from cache pool using key if it exists.
|
||||
// If it dose not exists key and has LoaderFunc,
|
||||
// generate a value using `LoaderFunc` method returns value.
|
||||
func (c *LRUCache) Get(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, true)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetIFPresent gets a value from cache pool using key if it exists.
|
||||
// If it dose not exists key, returns KeyNotFoundError.
|
||||
// And send a request which refresh value for specified key if cache object has LoaderFunc.
|
||||
func (c *LRUCache) GetIFPresent(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, false)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (c *LRUCache) get(key interface{}, onLoad bool) (interface{}, error) {
|
||||
v, err := c.getValue(key, onLoad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.deserializeFunc != nil {
|
||||
return c.deserializeFunc(key, v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *LRUCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
it := item.Value.(*lruItem)
|
||||
if !it.IsExpired(nil) {
|
||||
c.evictList.MoveToFront(item)
|
||||
v := it.value
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrHitCount()
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
c.removeElement(item)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrMissCount()
|
||||
}
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
|
||||
func (c *LRUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
|
||||
if c.loaderExpireFunc == nil {
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expiration != nil {
|
||||
t := c.clock.Now().Add(*expiration)
|
||||
item.(*lruItem).expiration = &t
|
||||
}
|
||||
return v, nil
|
||||
}, isWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// evict removes the oldest item from the cache.
|
||||
func (c *LRUCache) evict(count int) {
|
||||
for i := 0; i < count; i++ {
|
||||
ent := c.evictList.Back()
|
||||
if ent == nil {
|
||||
return
|
||||
} else {
|
||||
c.removeElement(ent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has checks if key exists in cache
|
||||
func (c *LRUCache) Has(key interface{}) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
now := time.Now()
|
||||
return c.has(key, &now)
|
||||
}
|
||||
|
||||
func (c *LRUCache) has(key interface{}, now *time.Time) bool {
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !item.Value.(*lruItem).IsExpired(now)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *LRUCache) Remove(key interface{}) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.remove(key)
|
||||
}
|
||||
|
||||
func (c *LRUCache) remove(key interface{}) bool {
|
||||
if ent, ok := c.items[key]; ok {
|
||||
c.removeElement(ent)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *LRUCache) removeElement(e *list.Element) {
|
||||
c.evictList.Remove(e)
|
||||
entry := e.Value.(*lruItem)
|
||||
delete(c.items, entry.key)
|
||||
if c.evictedFunc != nil {
|
||||
entry := e.Value.(*lruItem)
|
||||
c.evictedFunc(entry.key, entry.value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LRUCache) keys() []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, len(c.items))
|
||||
var i = 0
|
||||
for k := range c.items {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// GetALL returns all key-value pairs in the cache.
|
||||
func (c *LRUCache) GetALL(checkExpired bool) map[interface{}]interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
items := make(map[interface{}]interface{}, len(c.items))
|
||||
now := time.Now()
|
||||
for k, item := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
items[k] = item.Value.(*lruItem).value
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
func (c *LRUCache) Keys(checkExpired bool) []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, 0, len(c.items))
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *LRUCache) Len(checkExpired bool) int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if !checkExpired {
|
||||
return len(c.items)
|
||||
}
|
||||
var length int
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if c.has(k, &now) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
// Completely clear the cache
|
||||
func (c *LRUCache) Purge() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.purgeVisitorFunc != nil {
|
||||
for key, item := range c.items {
|
||||
it := item.Value.(*lruItem)
|
||||
v := it.value
|
||||
c.purgeVisitorFunc(key, v)
|
||||
}
|
||||
}
|
||||
|
||||
c.init()
|
||||
}
|
||||
|
||||
type lruItem struct {
|
||||
clock Clock
|
||||
key interface{}
|
||||
value interface{}
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
// IsExpired returns boolean value whether this item is expired or not.
|
||||
func (it *lruItem) IsExpired(now *time.Time) bool {
|
||||
if it.expiration == nil {
|
||||
return false
|
||||
}
|
||||
if now == nil {
|
||||
t := it.clock.Now()
|
||||
now = &t
|
||||
}
|
||||
return it.expiration.Before(*now)
|
||||
}
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SimpleCache has no clear priority for evict cache. It depends on key-value map order.
|
||||
type SimpleCache struct {
|
||||
baseCache
|
||||
items map[interface{}]*simpleItem
|
||||
}
|
||||
|
||||
func newSimpleCache(cb *CacheBuilder) *SimpleCache {
|
||||
c := &SimpleCache{}
|
||||
buildCache(&c.baseCache, cb)
|
||||
|
||||
c.init()
|
||||
c.loadGroup.cache = c
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *SimpleCache) init() {
|
||||
if c.size <= 0 {
|
||||
c.items = make(map[interface{}]*simpleItem)
|
||||
} else {
|
||||
c.items = make(map[interface{}]*simpleItem, c.size)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a new key-value pair
|
||||
func (c *SimpleCache) Set(key, value interface{}) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
_, err := c.set(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a new key-value pair with an expiration time
|
||||
func (c *SimpleCache) SetWithExpire(key, value interface{}, expiration time.Duration) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t := c.clock.Now().Add(expiration)
|
||||
item.(*simpleItem).expiration = &t
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SimpleCache) set(key, value interface{}) (interface{}, error) {
|
||||
var err error
|
||||
if c.serializeFunc != nil {
|
||||
value, err = c.serializeFunc(key, value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existing item
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
item.value = value
|
||||
} else {
|
||||
// Verify size not exceeded
|
||||
if (len(c.items) >= c.size) && c.size > 0 {
|
||||
c.evict(1)
|
||||
}
|
||||
item = &simpleItem{
|
||||
clock: c.clock,
|
||||
value: value,
|
||||
}
|
||||
c.items[key] = item
|
||||
}
|
||||
|
||||
if c.expiration != nil {
|
||||
t := c.clock.Now().Add(*c.expiration)
|
||||
item.expiration = &t
|
||||
}
|
||||
|
||||
if c.addedFunc != nil {
|
||||
c.addedFunc(key, value)
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// Get a value from cache pool using key if it exists.
|
||||
// If it dose not exists key and has LoaderFunc,
|
||||
// generate a value using `LoaderFunc` method returns value.
|
||||
func (c *SimpleCache) Get(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, true)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetIFPresent gets a value from cache pool using key if it exists.
|
||||
// If it dose not exists key, returns KeyNotFoundError.
|
||||
// And send a request which refresh value for specified key if cache object has LoaderFunc.
|
||||
func (c *SimpleCache) GetIFPresent(key interface{}) (interface{}, error) {
|
||||
v, err := c.get(key, false)
|
||||
if err == KeyNotFoundError {
|
||||
return c.getWithLoader(key, false)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *SimpleCache) get(key interface{}, onLoad bool) (interface{}, error) {
|
||||
v, err := c.getValue(key, onLoad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.deserializeFunc != nil {
|
||||
return c.deserializeFunc(key, v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (c *SimpleCache) getValue(key interface{}, onLoad bool) (interface{}, error) {
|
||||
c.mu.Lock()
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
if !item.IsExpired(nil) {
|
||||
v := item.value
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrHitCount()
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
c.remove(key)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if !onLoad {
|
||||
c.stats.IncrMissCount()
|
||||
}
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
|
||||
func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) {
|
||||
if c.loaderExpireFunc == nil {
|
||||
return nil, KeyNotFoundError
|
||||
}
|
||||
value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
item, err := c.set(key, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expiration != nil {
|
||||
t := c.clock.Now().Add(*expiration)
|
||||
item.(*simpleItem).expiration = &t
|
||||
}
|
||||
return v, nil
|
||||
}, isWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (c *SimpleCache) evict(count int) {
|
||||
now := c.clock.Now()
|
||||
current := 0
|
||||
for key, item := range c.items {
|
||||
if current >= count {
|
||||
return
|
||||
}
|
||||
if item.expiration == nil || now.After(*item.expiration) {
|
||||
defer c.remove(key)
|
||||
current++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has checks if key exists in cache
|
||||
func (c *SimpleCache) Has(key interface{}) bool {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
now := time.Now()
|
||||
return c.has(key, &now)
|
||||
}
|
||||
|
||||
func (c *SimpleCache) has(key interface{}, now *time.Time) bool {
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return !item.IsExpired(now)
|
||||
}
|
||||
|
||||
// Remove removes the provided key from the cache.
|
||||
func (c *SimpleCache) Remove(key interface{}) bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.remove(key)
|
||||
}
|
||||
|
||||
func (c *SimpleCache) remove(key interface{}) bool {
|
||||
item, ok := c.items[key]
|
||||
if ok {
|
||||
delete(c.items, key)
|
||||
if c.evictedFunc != nil {
|
||||
c.evictedFunc(key, item.value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns a slice of the keys in the cache.
|
||||
func (c *SimpleCache) keys() []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, len(c.items))
|
||||
var i = 0
|
||||
for k := range c.items {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// GetALL returns all key-value pairs in the cache.
|
||||
func (c *SimpleCache) GetALL(checkExpired bool) map[interface{}]interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
items := make(map[interface{}]interface{}, len(c.items))
|
||||
now := time.Now()
|
||||
for k, item := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
items[k] = item.value
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
// Keys returns a slice of the keys in the cache.
|
||||
func (c *SimpleCache) Keys(checkExpired bool) []interface{} {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
keys := make([]interface{}, 0, len(c.items))
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if !checkExpired || c.has(k, &now) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Len returns the number of items in the cache.
|
||||
func (c *SimpleCache) Len(checkExpired bool) int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
if !checkExpired {
|
||||
return len(c.items)
|
||||
}
|
||||
var length int
|
||||
now := time.Now()
|
||||
for k := range c.items {
|
||||
if c.has(k, &now) {
|
||||
length++
|
||||
}
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
// Completely clear the cache
|
||||
func (c *SimpleCache) Purge() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.purgeVisitorFunc != nil {
|
||||
for key, item := range c.items {
|
||||
c.purgeVisitorFunc(key, item.value)
|
||||
}
|
||||
}
|
||||
|
||||
c.init()
|
||||
}
|
||||
|
||||
type simpleItem struct {
|
||||
clock Clock
|
||||
value interface{}
|
||||
expiration *time.Time
|
||||
}
|
||||
|
||||
// IsExpired returns boolean value whether this item is expired or not.
|
||||
func (si *simpleItem) IsExpired(now *time.Time) bool {
|
||||
if si.expiration == nil {
|
||||
return false
|
||||
}
|
||||
if now == nil {
|
||||
t := si.clock.Now()
|
||||
now = &t
|
||||
}
|
||||
return si.expiration.Before(*now)
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package gcache
|
||||
|
||||
/*
|
||||
Copyright 2012 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This module provides a duplicate function call suppression
|
||||
// mechanism.
|
||||
|
||||
import "sync"
|
||||
|
||||
// call is an in-flight or completed Do call
|
||||
type call struct {
|
||||
wg sync.WaitGroup
|
||||
val interface{}
|
||||
err error
|
||||
}
|
||||
|
||||
// Group represents a class of work and forms a namespace in which
|
||||
// units of work can be executed with duplicate suppression.
|
||||
type Group struct {
|
||||
cache Cache
|
||||
mu sync.Mutex // protects m
|
||||
m map[interface{}]*call // lazily initialized
|
||||
}
|
||||
|
||||
// Do executes and returns the results of the given function, making
|
||||
// sure that only one execution is in-flight for a given key at a
|
||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||
// original to complete and receives the same results.
|
||||
func (g *Group) Do(key interface{}, fn func() (interface{}, error), isWait bool) (interface{}, bool, error) {
|
||||
g.mu.Lock()
|
||||
v, err := g.cache.get(key, true)
|
||||
if err == nil {
|
||||
g.mu.Unlock()
|
||||
return v, false, nil
|
||||
}
|
||||
if g.m == nil {
|
||||
g.m = make(map[interface{}]*call)
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
g.mu.Unlock()
|
||||
if !isWait {
|
||||
return nil, false, KeyNotFoundError
|
||||
}
|
||||
c.wg.Wait()
|
||||
return c.val, false, c.err
|
||||
}
|
||||
c := new(call)
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.mu.Unlock()
|
||||
if !isWait {
|
||||
go g.call(c, key, fn)
|
||||
return nil, false, KeyNotFoundError
|
||||
}
|
||||
v, err = g.call(c, key, fn)
|
||||
return v, true, err
|
||||
}
|
||||
|
||||
func (g *Group) call(c *call, key interface{}, fn func() (interface{}, error)) (interface{}, error) {
|
||||
c.val, c.err = fn()
|
||||
c.wg.Done()
|
||||
|
||||
g.mu.Lock()
|
||||
delete(g.m, key)
|
||||
g.mu.Unlock()
|
||||
|
||||
return c.val, c.err
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type statsAccessor interface {
|
||||
HitCount() uint64
|
||||
MissCount() uint64
|
||||
LookupCount() uint64
|
||||
HitRate() float64
|
||||
}
|
||||
|
||||
// statistics
|
||||
type stats struct {
|
||||
hitCount uint64
|
||||
missCount uint64
|
||||
}
|
||||
|
||||
// increment hit count
|
||||
func (st *stats) IncrHitCount() uint64 {
|
||||
return atomic.AddUint64(&st.hitCount, 1)
|
||||
}
|
||||
|
||||
// increment miss count
|
||||
func (st *stats) IncrMissCount() uint64 {
|
||||
return atomic.AddUint64(&st.missCount, 1)
|
||||
}
|
||||
|
||||
// HitCount returns hit count
|
||||
func (st *stats) HitCount() uint64 {
|
||||
return atomic.LoadUint64(&st.hitCount)
|
||||
}
|
||||
|
||||
// MissCount returns miss count
|
||||
func (st *stats) MissCount() uint64 {
|
||||
return atomic.LoadUint64(&st.missCount)
|
||||
}
|
||||
|
||||
// LookupCount returns lookup count
|
||||
func (st *stats) LookupCount() uint64 {
|
||||
return st.HitCount() + st.MissCount()
|
||||
}
|
||||
|
||||
// HitRate returns rate for cache hitting
|
||||
func (st *stats) HitRate() float64 {
|
||||
hc, mc := st.HitCount(), st.MissCount()
|
||||
total := hc + mc
|
||||
if total == 0 {
|
||||
return 0.0
|
||||
}
|
||||
return float64(hc) / float64(total)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package gcache
|
||||
|
||||
func minInt(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
func maxInt(x, y int) int {
|
||||
if x > y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
Reference in New Issue
Block a user