├── go.mod ├── utils.go ├── examples └── autoloading │ └── autoloading_cache.go ├── .github └── workflows │ └── test.yml ├── clock.go ├── LICENSE ├── stats.go ├── lru_test.go ├── singleflight_test.go ├── singleflight.go ├── simple_test.go ├── stats_test.go ├── arc_test.go ├── helpers_test.go ├── lfu_test.go ├── README.md ├── cache_test.go ├── cache.go ├── simple.go ├── lru.go ├── lfu.go └── arc.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bluele/gcache 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | func minInt(x, y int) int { 4 | if x < y { 5 | return x 6 | } 7 | return y 8 | } 9 | 10 | func maxInt(x, y int) int { 11 | if x > y { 12 | return x 13 | } 14 | return y 15 | } 16 | -------------------------------------------------------------------------------- /examples/autoloading/autoloading_cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/gcache" 6 | ) 7 | 8 | func main() { 9 | gc := gcache.New(10). 10 | LFU(). 11 | LoaderFunc(func(key interface{}) (interface{}, error) { 12 | return fmt.Sprintf("%v-value", key), nil 13 | }). 14 | Build() 15 | 16 | v, err := gc.Get("key") 17 | if err != nil { 18 | panic(err) 19 | } 20 | fmt.Println(v) 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | 9 | - name: Set up Go 1.15 10 | uses: actions/setup-go@v1 11 | with: 12 | go-version: 1.15 13 | id: go 14 | 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v2 17 | 18 | - name: go test 19 | run: go test -v ./... 20 | -------------------------------------------------------------------------------- /clock.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Clock interface { 9 | Now() time.Time 10 | } 11 | 12 | type RealClock struct{} 13 | 14 | func NewRealClock() Clock { 15 | return RealClock{} 16 | } 17 | 18 | func (rc RealClock) Now() time.Time { 19 | t := time.Now() 20 | return t 21 | } 22 | 23 | type FakeClock interface { 24 | Clock 25 | 26 | Advance(d time.Duration) 27 | } 28 | 29 | func NewFakeClock() FakeClock { 30 | return &fakeclock{ 31 | // Taken from github.com/jonboulle/clockwork: use a fixture that does not fulfill Time.IsZero() 32 | now: time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC), 33 | } 34 | } 35 | 36 | type fakeclock struct { 37 | now time.Time 38 | 39 | mutex sync.RWMutex 40 | } 41 | 42 | func (fc *fakeclock) Now() time.Time { 43 | fc.mutex.RLock() 44 | defer fc.mutex.RUnlock() 45 | t := fc.now 46 | return t 47 | } 48 | 49 | func (fc *fakeclock) Advance(d time.Duration) { 50 | fc.mutex.Lock() 51 | defer fc.mutex.Unlock() 52 | fc.now = fc.now.Add(d) 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jun Kimura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /stats.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | type statsAccessor interface { 8 | HitCount() uint64 9 | MissCount() uint64 10 | LookupCount() uint64 11 | HitRate() float64 12 | } 13 | 14 | // statistics 15 | type stats struct { 16 | hitCount uint64 17 | missCount uint64 18 | } 19 | 20 | // increment hit count 21 | func (st *stats) IncrHitCount() uint64 { 22 | return atomic.AddUint64(&st.hitCount, 1) 23 | } 24 | 25 | // increment miss count 26 | func (st *stats) IncrMissCount() uint64 { 27 | return atomic.AddUint64(&st.missCount, 1) 28 | } 29 | 30 | // HitCount returns hit count 31 | func (st *stats) HitCount() uint64 { 32 | return atomic.LoadUint64(&st.hitCount) 33 | } 34 | 35 | // MissCount returns miss count 36 | func (st *stats) MissCount() uint64 { 37 | return atomic.LoadUint64(&st.missCount) 38 | } 39 | 40 | // LookupCount returns lookup count 41 | func (st *stats) LookupCount() uint64 { 42 | return st.HitCount() + st.MissCount() 43 | } 44 | 45 | // HitRate returns rate for cache hitting 46 | func (st *stats) HitRate() float64 { 47 | hc, mc := st.HitCount(), st.MissCount() 48 | total := hc + mc 49 | if total == 0 { 50 | return 0.0 51 | } 52 | return float64(hc) / float64(total) 53 | } 54 | -------------------------------------------------------------------------------- /lru_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestLRUGet(t *testing.T) { 10 | size := 1000 11 | gc := buildTestCache(t, TYPE_LRU, size) 12 | testSetCache(t, gc, size) 13 | testGetCache(t, gc, size) 14 | } 15 | 16 | func TestLoadingLRUGet(t *testing.T) { 17 | size := 1000 18 | gc := buildTestLoadingCache(t, TYPE_LRU, size, loader) 19 | testGetCache(t, gc, size) 20 | } 21 | 22 | func TestLRULength(t *testing.T) { 23 | gc := buildTestLoadingCache(t, TYPE_LRU, 1000, loader) 24 | gc.Get("test1") 25 | gc.Get("test2") 26 | length := gc.Len(true) 27 | expectedLength := 2 28 | if length != expectedLength { 29 | t.Errorf("Expected length is %v, not %v", length, expectedLength) 30 | } 31 | } 32 | 33 | func TestLRUEvictItem(t *testing.T) { 34 | cacheSize := 10 35 | numbers := 11 36 | gc := buildTestLoadingCache(t, TYPE_LRU, cacheSize, loader) 37 | 38 | for i := 0; i < numbers; i++ { 39 | _, err := gc.Get(fmt.Sprintf("Key-%d", i)) 40 | if err != nil { 41 | t.Errorf("Unexpected error: %v", err) 42 | } 43 | } 44 | } 45 | 46 | func TestLRUGetIFPresent(t *testing.T) { 47 | testGetIFPresent(t, TYPE_LRU) 48 | } 49 | 50 | func TestLRUHas(t *testing.T) { 51 | gc := buildTestLoadingCacheWithExpiration(t, TYPE_LRU, 2, 10*time.Millisecond) 52 | 53 | for i := 0; i < 10; i++ { 54 | t.Run(fmt.Sprint(i), func(t *testing.T) { 55 | gc.Get("test1") 56 | gc.Get("test2") 57 | 58 | if gc.Has("test0") { 59 | t.Fatal("should not have test0") 60 | } 61 | if !gc.Has("test1") { 62 | t.Fatal("should have test1") 63 | } 64 | if !gc.Has("test2") { 65 | t.Fatal("should have test2") 66 | } 67 | 68 | time.Sleep(20 * time.Millisecond) 69 | 70 | if gc.Has("test0") { 71 | t.Fatal("should not have test0") 72 | } 73 | if gc.Has("test1") { 74 | t.Fatal("should not have test1") 75 | } 76 | if gc.Has("test2") { 77 | t.Fatal("should not have test2") 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /singleflight_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | /* 4 | Copyright 2012 Google Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "sync" 23 | "sync/atomic" 24 | "testing" 25 | "time" 26 | ) 27 | 28 | func TestDo(t *testing.T) { 29 | var g Group 30 | g.cache = New(32).Build() 31 | v, _, err := g.Do("key", func() (interface{}, error) { 32 | return "bar", nil 33 | }, true) 34 | if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { 35 | t.Errorf("Do = %v; want %v", got, want) 36 | } 37 | if err != nil { 38 | t.Errorf("Do error = %v", err) 39 | } 40 | } 41 | 42 | func TestDoErr(t *testing.T) { 43 | var g Group 44 | g.cache = New(32).Build() 45 | someErr := errors.New("Some error") 46 | v, _, err := g.Do("key", func() (interface{}, error) { 47 | return nil, someErr 48 | }, true) 49 | if err != someErr { 50 | t.Errorf("Do error = %v; want someErr", err) 51 | } 52 | if v != nil { 53 | t.Errorf("unexpected non-nil value %#v", v) 54 | } 55 | } 56 | 57 | func TestDoDupSuppress(t *testing.T) { 58 | var g Group 59 | g.cache = New(32).Build() 60 | c := make(chan string) 61 | var calls int32 62 | fn := func() (interface{}, error) { 63 | atomic.AddInt32(&calls, 1) 64 | return <-c, nil 65 | } 66 | 67 | const n = 10 68 | var wg sync.WaitGroup 69 | for i := 0; i < n; i++ { 70 | wg.Add(1) 71 | go func() { 72 | v, _, err := g.Do("key", fn, true) 73 | if err != nil { 74 | t.Errorf("Do error: %v", err) 75 | } 76 | if v.(string) != "bar" { 77 | t.Errorf("got %q; want %q", v, "bar") 78 | } 79 | wg.Done() 80 | }() 81 | } 82 | time.Sleep(100 * time.Millisecond) // let goroutines above block 83 | c <- "bar" 84 | wg.Wait() 85 | if got := atomic.LoadInt32(&calls); got != 1 { 86 | t.Errorf("number of calls = %d; want 1", got) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /singleflight.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | /* 4 | Copyright 2012 Google Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // This module provides a duplicate function call suppression 20 | // mechanism. 21 | 22 | import "sync" 23 | 24 | // call is an in-flight or completed Do call 25 | type call struct { 26 | wg sync.WaitGroup 27 | val interface{} 28 | err error 29 | } 30 | 31 | // Group represents a class of work and forms a namespace in which 32 | // units of work can be executed with duplicate suppression. 33 | type Group struct { 34 | cache Cache 35 | mu sync.Mutex // protects m 36 | m map[interface{}]*call // lazily initialized 37 | } 38 | 39 | // Do executes and returns the results of the given function, making 40 | // sure that only one execution is in-flight for a given key at a 41 | // time. If a duplicate comes in, the duplicate caller waits for the 42 | // original to complete and receives the same results. 43 | func (g *Group) Do(key interface{}, fn func() (interface{}, error), isWait bool) (interface{}, bool, error) { 44 | g.mu.Lock() 45 | v, err := g.cache.get(key, true) 46 | if err == nil { 47 | g.mu.Unlock() 48 | return v, false, nil 49 | } 50 | if g.m == nil { 51 | g.m = make(map[interface{}]*call) 52 | } 53 | if c, ok := g.m[key]; ok { 54 | g.mu.Unlock() 55 | if !isWait { 56 | return nil, false, KeyNotFoundError 57 | } 58 | c.wg.Wait() 59 | return c.val, false, c.err 60 | } 61 | c := new(call) 62 | c.wg.Add(1) 63 | g.m[key] = c 64 | g.mu.Unlock() 65 | if !isWait { 66 | go g.call(c, key, fn) 67 | return nil, false, KeyNotFoundError 68 | } 69 | v, err = g.call(c, key, fn) 70 | return v, true, err 71 | } 72 | 73 | func (g *Group) call(c *call, key interface{}, fn func() (interface{}, error)) (interface{}, error) { 74 | c.val, c.err = fn() 75 | c.wg.Done() 76 | 77 | g.mu.Lock() 78 | delete(g.m, key) 79 | g.mu.Unlock() 80 | 81 | return c.val, c.err 82 | } 83 | -------------------------------------------------------------------------------- /simple_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestSimpleGet(t *testing.T) { 10 | size := 1000 11 | gc := buildTestCache(t, TYPE_SIMPLE, size) 12 | testSetCache(t, gc, size) 13 | testGetCache(t, gc, size) 14 | } 15 | 16 | func TestLoadingSimpleGet(t *testing.T) { 17 | size := 1000 18 | numbers := 1000 19 | testGetCache(t, buildTestLoadingCache(t, TYPE_SIMPLE, size, loader), numbers) 20 | } 21 | 22 | func TestSimpleLength(t *testing.T) { 23 | gc := buildTestLoadingCache(t, TYPE_SIMPLE, 1000, loader) 24 | gc.Get("test1") 25 | gc.Get("test2") 26 | length := gc.Len(true) 27 | expectedLength := 2 28 | if length != expectedLength { 29 | t.Errorf("Expected length is %v, not %v", length, expectedLength) 30 | } 31 | } 32 | 33 | func TestSimpleEvictItem(t *testing.T) { 34 | cacheSize := 10 35 | numbers := 11 36 | gc := buildTestLoadingCache(t, TYPE_SIMPLE, cacheSize, loader) 37 | 38 | for i := 0; i < numbers; i++ { 39 | _, err := gc.Get(fmt.Sprintf("Key-%d", i)) 40 | if err != nil { 41 | t.Errorf("Unexpected error: %v", err) 42 | } 43 | } 44 | } 45 | 46 | func TestSimpleUnboundedNoEviction(t *testing.T) { 47 | numbers := 1000 48 | size_tracker := 0 49 | gcu := buildTestLoadingCache(t, TYPE_SIMPLE, 0, loader) 50 | 51 | for i := 0; i < numbers; i++ { 52 | current_size := gcu.Len(true) 53 | if current_size != size_tracker { 54 | t.Errorf("Excepted cache size is %v not %v", current_size, size_tracker) 55 | } 56 | 57 | _, err := gcu.Get(fmt.Sprintf("Key-%d", i)) 58 | if err != nil { 59 | t.Errorf("Unexpected error: %v", err) 60 | } 61 | 62 | size_tracker++ 63 | } 64 | } 65 | 66 | func TestSimpleGetIFPresent(t *testing.T) { 67 | testGetIFPresent(t, TYPE_SIMPLE) 68 | } 69 | 70 | func TestSimpleHas(t *testing.T) { 71 | gc := buildTestLoadingCacheWithExpiration(t, TYPE_SIMPLE, 2, 10*time.Millisecond) 72 | 73 | for i := 0; i < 10; i++ { 74 | t.Run(fmt.Sprint(i), func(t *testing.T) { 75 | gc.Get("test1") 76 | gc.Get("test2") 77 | 78 | if gc.Has("test0") { 79 | t.Fatal("should not have test0") 80 | } 81 | if !gc.Has("test1") { 82 | t.Fatal("should have test1") 83 | } 84 | if !gc.Has("test2") { 85 | t.Fatal("should have test2") 86 | } 87 | 88 | time.Sleep(20 * time.Millisecond) 89 | 90 | if gc.Has("test0") { 91 | t.Fatal("should not have test0") 92 | } 93 | if gc.Has("test1") { 94 | t.Fatal("should not have test1") 95 | } 96 | if gc.Has("test2") { 97 | t.Fatal("should not have test2") 98 | } 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /stats_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStats(t *testing.T) { 8 | var cases = []struct { 9 | hit int 10 | miss int 11 | rate float64 12 | }{ 13 | {3, 1, 0.75}, 14 | {0, 1, 0.0}, 15 | {3, 0, 1.0}, 16 | {0, 0, 0.0}, 17 | } 18 | 19 | for _, cs := range cases { 20 | st := &stats{} 21 | for i := 0; i < cs.hit; i++ { 22 | st.IncrHitCount() 23 | } 24 | for i := 0; i < cs.miss; i++ { 25 | st.IncrMissCount() 26 | } 27 | if rate := st.HitRate(); rate != cs.rate { 28 | t.Errorf("%v != %v", rate, cs.rate) 29 | } 30 | } 31 | } 32 | 33 | func getter(key interface{}) (interface{}, error) { 34 | return key, nil 35 | } 36 | 37 | func TestCacheStats(t *testing.T) { 38 | var cases = []struct { 39 | builder func() Cache 40 | rate float64 41 | }{ 42 | { 43 | builder: func() Cache { 44 | cc := New(32).Simple().Build() 45 | cc.Set(0, 0) 46 | cc.Get(0) 47 | cc.Get(1) 48 | return cc 49 | }, 50 | rate: 0.5, 51 | }, 52 | { 53 | builder: func() Cache { 54 | cc := New(32).LRU().Build() 55 | cc.Set(0, 0) 56 | cc.Get(0) 57 | cc.Get(1) 58 | return cc 59 | }, 60 | rate: 0.5, 61 | }, 62 | { 63 | builder: func() Cache { 64 | cc := New(32).LFU().Build() 65 | cc.Set(0, 0) 66 | cc.Get(0) 67 | cc.Get(1) 68 | return cc 69 | }, 70 | rate: 0.5, 71 | }, 72 | { 73 | builder: func() Cache { 74 | cc := New(32).ARC().Build() 75 | cc.Set(0, 0) 76 | cc.Get(0) 77 | cc.Get(1) 78 | return cc 79 | }, 80 | rate: 0.5, 81 | }, 82 | { 83 | builder: func() Cache { 84 | cc := New(32). 85 | Simple(). 86 | LoaderFunc(getter). 87 | Build() 88 | cc.Set(0, 0) 89 | cc.Get(0) 90 | cc.Get(1) 91 | return cc 92 | }, 93 | rate: 0.5, 94 | }, 95 | { 96 | builder: func() Cache { 97 | cc := New(32). 98 | LRU(). 99 | LoaderFunc(getter). 100 | Build() 101 | cc.Set(0, 0) 102 | cc.Get(0) 103 | cc.Get(1) 104 | return cc 105 | }, 106 | rate: 0.5, 107 | }, 108 | { 109 | builder: func() Cache { 110 | cc := New(32). 111 | LFU(). 112 | LoaderFunc(getter). 113 | Build() 114 | cc.Set(0, 0) 115 | cc.Get(0) 116 | cc.Get(1) 117 | return cc 118 | }, 119 | rate: 0.5, 120 | }, 121 | { 122 | builder: func() Cache { 123 | cc := New(32). 124 | ARC(). 125 | LoaderFunc(getter). 126 | Build() 127 | cc.Set(0, 0) 128 | cc.Get(0) 129 | cc.Get(1) 130 | return cc 131 | }, 132 | rate: 0.5, 133 | }, 134 | } 135 | 136 | for i, cs := range cases { 137 | cc := cs.builder() 138 | if rate := cc.HitRate(); rate != cs.rate { 139 | t.Errorf("case-%v: %v != %v", i, rate, cs.rate) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /arc_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestARCGet(t *testing.T) { 10 | size := 1000 11 | gc := buildTestCache(t, TYPE_ARC, size) 12 | testSetCache(t, gc, size) 13 | testGetCache(t, gc, size) 14 | } 15 | 16 | func TestLoadingARCGet(t *testing.T) { 17 | size := 1000 18 | numbers := 1000 19 | testGetCache(t, buildTestLoadingCache(t, TYPE_ARC, size, loader), numbers) 20 | } 21 | 22 | func TestARCLength(t *testing.T) { 23 | gc := buildTestLoadingCacheWithExpiration(t, TYPE_ARC, 2, time.Millisecond) 24 | gc.Get("test1") 25 | gc.Get("test2") 26 | gc.Get("test3") 27 | length := gc.Len(true) 28 | expectedLength := 2 29 | if length != expectedLength { 30 | t.Errorf("Expected length is %v, not %v", expectedLength, length) 31 | } 32 | time.Sleep(time.Millisecond) 33 | gc.Get("test4") 34 | length = gc.Len(true) 35 | expectedLength = 1 36 | if length != expectedLength { 37 | t.Errorf("Expected length is %v, not %v", expectedLength, length) 38 | } 39 | } 40 | 41 | func TestARCEvictItem(t *testing.T) { 42 | cacheSize := 10 43 | numbers := cacheSize + 1 44 | gc := buildTestLoadingCache(t, TYPE_ARC, cacheSize, loader) 45 | 46 | for i := 0; i < numbers; i++ { 47 | _, err := gc.Get(fmt.Sprintf("Key-%d", i)) 48 | if err != nil { 49 | t.Errorf("Unexpected error: %v", err) 50 | } 51 | } 52 | } 53 | 54 | func TestARCPurgeCache(t *testing.T) { 55 | cacheSize := 10 56 | purgeCount := 0 57 | gc := New(cacheSize). 58 | ARC(). 59 | LoaderFunc(loader). 60 | PurgeVisitorFunc(func(k, v interface{}) { 61 | purgeCount++ 62 | }). 63 | Build() 64 | 65 | for i := 0; i < cacheSize; i++ { 66 | _, err := gc.Get(fmt.Sprintf("Key-%d", i)) 67 | if err != nil { 68 | t.Errorf("Unexpected error: %v", err) 69 | } 70 | } 71 | 72 | gc.Purge() 73 | 74 | if purgeCount != cacheSize { 75 | t.Errorf("failed to purge everything") 76 | } 77 | } 78 | 79 | func TestARCGetIFPresent(t *testing.T) { 80 | testGetIFPresent(t, TYPE_ARC) 81 | } 82 | 83 | func TestARCHas(t *testing.T) { 84 | gc := buildTestLoadingCacheWithExpiration(t, TYPE_ARC, 2, 10*time.Millisecond) 85 | 86 | for i := 0; i < 10; i++ { 87 | t.Run(fmt.Sprint(i), func(t *testing.T) { 88 | gc.Get("test1") 89 | gc.Get("test2") 90 | 91 | if gc.Has("test0") { 92 | t.Fatal("should not have test0") 93 | } 94 | if !gc.Has("test1") { 95 | t.Fatal("should have test1") 96 | } 97 | if !gc.Has("test2") { 98 | t.Fatal("should have test2") 99 | } 100 | 101 | time.Sleep(20 * time.Millisecond) 102 | 103 | if gc.Has("test0") { 104 | t.Fatal("should not have test0") 105 | } 106 | if gc.Has("test1") { 107 | t.Fatal("should not have test1") 108 | } 109 | if gc.Has("test2") { 110 | t.Fatal("should not have test2") 111 | } 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /helpers_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func loader(key interface{}) (interface{}, error) { 10 | return fmt.Sprintf("valueFor%s", key), nil 11 | } 12 | 13 | func testSetCache(t *testing.T, gc Cache, numbers int) { 14 | for i := 0; i < numbers; i++ { 15 | key := fmt.Sprintf("Key-%d", i) 16 | value, err := loader(key) 17 | if err != nil { 18 | t.Error(err) 19 | return 20 | } 21 | gc.Set(key, value) 22 | } 23 | } 24 | 25 | func testGetCache(t *testing.T, gc Cache, numbers int) { 26 | for i := 0; i < numbers; i++ { 27 | key := fmt.Sprintf("Key-%d", i) 28 | v, err := gc.Get(key) 29 | if err != nil { 30 | t.Errorf("Unexpected error: %v", err) 31 | } 32 | expectedV, _ := loader(key) 33 | if v != expectedV { 34 | t.Errorf("Expected value is %v, not %v", expectedV, v) 35 | } 36 | } 37 | } 38 | 39 | func testGetIFPresent(t *testing.T, evT string) { 40 | cache := 41 | New(8). 42 | EvictType(evT). 43 | LoaderFunc( 44 | func(key interface{}) (interface{}, error) { 45 | return "value", nil 46 | }). 47 | Build() 48 | 49 | v, err := cache.GetIFPresent("key") 50 | if err != KeyNotFoundError { 51 | t.Errorf("err should not be %v", err) 52 | } 53 | 54 | time.Sleep(2 * time.Millisecond) 55 | 56 | v, err = cache.GetIFPresent("key") 57 | if err != nil { 58 | t.Errorf("err should not be %v", err) 59 | } 60 | if v != "value" { 61 | t.Errorf("v should not be %v", v) 62 | } 63 | } 64 | 65 | func setItemsByRange(t *testing.T, c Cache, start, end int) { 66 | for i := start; i < end; i++ { 67 | if err := c.Set(i, i); err != nil { 68 | t.Error(err) 69 | } 70 | } 71 | } 72 | 73 | func keysToMap(keys []interface{}) map[interface{}]struct{} { 74 | m := make(map[interface{}]struct{}, len(keys)) 75 | for _, k := range keys { 76 | m[k] = struct{}{} 77 | } 78 | return m 79 | } 80 | 81 | func checkItemsByRange(t *testing.T, keys []interface{}, m map[interface{}]interface{}, size, start, end int) { 82 | if len(keys) != size { 83 | t.Fatalf("%v != %v", len(keys), size) 84 | } else if len(m) != size { 85 | t.Fatalf("%v != %v", len(m), size) 86 | } 87 | km := keysToMap(keys) 88 | for i := start; i < end; i++ { 89 | if _, ok := km[i]; !ok { 90 | t.Errorf("keys should contain %v", i) 91 | } 92 | v, ok := m[i] 93 | if !ok { 94 | t.Errorf("m should contain %v", i) 95 | continue 96 | } 97 | if v.(int) != i { 98 | t.Errorf("%v != %v", v, i) 99 | continue 100 | } 101 | } 102 | } 103 | 104 | func testExpiredItems(t *testing.T, evT string) { 105 | size := 8 106 | cache := 107 | New(size). 108 | Expiration(time.Millisecond). 109 | EvictType(evT). 110 | Build() 111 | 112 | setItemsByRange(t, cache, 0, size) 113 | checkItemsByRange(t, cache.Keys(true), cache.GetALL(true), cache.Len(true), 0, size) 114 | 115 | time.Sleep(time.Millisecond) 116 | 117 | checkItemsByRange(t, cache.Keys(false), cache.GetALL(false), cache.Len(false), 0, size) 118 | 119 | if l := cache.Len(true); l != 0 { 120 | t.Fatalf("GetALL should returns no items, but got length %v", l) 121 | } 122 | 123 | cache.Set(1, 1) 124 | m := cache.GetALL(true) 125 | if len(m) != 1 { 126 | t.Fatalf("%v != %v", len(m), 1) 127 | } else if l := cache.Len(true); l != 1 { 128 | t.Fatalf("%v != %v", l, 1) 129 | } 130 | if m[1] != 1 { 131 | t.Fatalf("%v != %v", m[1], 1) 132 | } 133 | } 134 | 135 | func getSimpleEvictedFunc(t *testing.T) func(interface{}, interface{}) { 136 | return func(key, value interface{}) { 137 | t.Logf("Key=%v Value=%v will be evicted.\n", key, value) 138 | } 139 | } 140 | 141 | func buildTestCache(t *testing.T, tp string, size int) Cache { 142 | return New(size). 143 | EvictType(tp). 144 | EvictedFunc(getSimpleEvictedFunc(t)). 145 | Build() 146 | } 147 | 148 | func buildTestLoadingCache(t *testing.T, tp string, size int, loader LoaderFunc) Cache { 149 | return New(size). 150 | EvictType(tp). 151 | LoaderFunc(loader). 152 | EvictedFunc(getSimpleEvictedFunc(t)). 153 | Build() 154 | } 155 | 156 | func buildTestLoadingCacheWithExpiration(t *testing.T, tp string, size int, ep time.Duration) Cache { 157 | return New(size). 158 | EvictType(tp). 159 | Expiration(ep). 160 | LoaderFunc(loader). 161 | EvictedFunc(getSimpleEvictedFunc(t)). 162 | Build() 163 | } 164 | -------------------------------------------------------------------------------- /lfu_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestLFUGet(t *testing.T) { 10 | size := 1000 11 | numbers := 1000 12 | 13 | gc := buildTestLoadingCache(t, TYPE_LFU, size, loader) 14 | testSetCache(t, gc, numbers) 15 | testGetCache(t, gc, numbers) 16 | } 17 | 18 | func TestLoadingLFUGet(t *testing.T) { 19 | size := 1000 20 | numbers := 1000 21 | 22 | gc := buildTestLoadingCache(t, TYPE_LFU, size, loader) 23 | testGetCache(t, gc, numbers) 24 | } 25 | 26 | func TestLFULength(t *testing.T) { 27 | gc := buildTestLoadingCache(t, TYPE_LFU, 1000, loader) 28 | gc.Get("test1") 29 | gc.Get("test2") 30 | length := gc.Len(true) 31 | expectedLength := 2 32 | if length != expectedLength { 33 | t.Errorf("Expected length is %v, not %v", length, expectedLength) 34 | } 35 | } 36 | 37 | func TestLFUEvictItem(t *testing.T) { 38 | cacheSize := 10 39 | numbers := 11 40 | gc := buildTestLoadingCache(t, TYPE_LFU, cacheSize, loader) 41 | 42 | for i := 0; i < numbers; i++ { 43 | _, err := gc.Get(fmt.Sprintf("Key-%d", i)) 44 | if err != nil { 45 | t.Errorf("Unexpected error: %v", err) 46 | } 47 | } 48 | } 49 | 50 | func TestLFUGetIFPresent(t *testing.T) { 51 | testGetIFPresent(t, TYPE_LFU) 52 | } 53 | 54 | func TestLFUHas(t *testing.T) { 55 | gc := buildTestLoadingCacheWithExpiration(t, TYPE_LFU, 2, 10*time.Millisecond) 56 | 57 | for i := 0; i < 10; i++ { 58 | t.Run(fmt.Sprint(i), func(t *testing.T) { 59 | gc.Get("test1") 60 | gc.Get("test2") 61 | 62 | if gc.Has("test0") { 63 | t.Fatal("should not have test0") 64 | } 65 | if !gc.Has("test1") { 66 | t.Fatal("should have test1") 67 | } 68 | if !gc.Has("test2") { 69 | t.Fatal("should have test2") 70 | } 71 | 72 | time.Sleep(20 * time.Millisecond) 73 | 74 | if gc.Has("test0") { 75 | t.Fatal("should not have test0") 76 | } 77 | if gc.Has("test1") { 78 | t.Fatal("should not have test1") 79 | } 80 | if gc.Has("test2") { 81 | t.Fatal("should not have test2") 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func TestLFUFreqListOrder(t *testing.T) { 88 | gc := buildTestCache(t, TYPE_LFU, 5) 89 | for i := 4; i >= 0; i-- { 90 | gc.Set(i, i) 91 | for j := 0; j <= i; j++ { 92 | gc.Get(i) 93 | } 94 | } 95 | if l := gc.(*LFUCache).freqList.Len(); l != 6 { 96 | t.Fatalf("%v != 6", l) 97 | } 98 | var i uint 99 | for e := gc.(*LFUCache).freqList.Front(); e != nil; e = e.Next() { 100 | if e.Value.(*freqEntry).freq != i { 101 | t.Fatalf("%v != %v", e.Value.(*freqEntry).freq, i) 102 | } 103 | i++ 104 | } 105 | gc.Remove(1) 106 | 107 | if l := gc.(*LFUCache).freqList.Len(); l != 5 { 108 | t.Fatalf("%v != 5", l) 109 | } 110 | gc.Set(1, 1) 111 | if l := gc.(*LFUCache).freqList.Len(); l != 5 { 112 | t.Fatalf("%v != 5", l) 113 | } 114 | gc.Get(1) 115 | if l := gc.(*LFUCache).freqList.Len(); l != 5 { 116 | t.Fatalf("%v != 5", l) 117 | } 118 | gc.Get(1) 119 | if l := gc.(*LFUCache).freqList.Len(); l != 6 { 120 | t.Fatalf("%v != 6", l) 121 | } 122 | } 123 | 124 | func TestLFUFreqListLength(t *testing.T) { 125 | k0, v0 := "k0", "v0" 126 | k1, v1 := "k1", "v1" 127 | 128 | { 129 | gc := buildTestCache(t, TYPE_LFU, 5) 130 | if l := gc.(*LFUCache).freqList.Len(); l != 1 { 131 | t.Fatalf("%v != 1", l) 132 | } 133 | } 134 | { 135 | gc := buildTestCache(t, TYPE_LFU, 5) 136 | gc.Set(k0, v0) 137 | for i := 0; i < 5; i++ { 138 | gc.Get(k0) 139 | } 140 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 141 | t.Fatalf("%v != 2", l) 142 | } 143 | } 144 | 145 | { 146 | gc := buildTestCache(t, TYPE_LFU, 5) 147 | gc.Set(k0, v0) 148 | gc.Set(k1, v1) 149 | for i := 0; i < 5; i++ { 150 | gc.Get(k0) 151 | gc.Get(k1) 152 | } 153 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 154 | t.Fatalf("%v != 2", l) 155 | } 156 | } 157 | 158 | { 159 | gc := buildTestCache(t, TYPE_LFU, 5) 160 | gc.Set(k0, v0) 161 | gc.Set(k1, v1) 162 | for i := 0; i < 5; i++ { 163 | gc.Get(k0) 164 | } 165 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 166 | t.Fatalf("%v != 2", l) 167 | } 168 | for i := 0; i < 5; i++ { 169 | gc.Get(k1) 170 | } 171 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 172 | t.Fatalf("%v != 2", l) 173 | } 174 | } 175 | 176 | { 177 | gc := buildTestCache(t, TYPE_LFU, 5) 178 | gc.Set(k0, v0) 179 | gc.Get(k0) 180 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 181 | t.Fatalf("%v != 2", l) 182 | } 183 | gc.Remove(k0) 184 | if l := gc.(*LFUCache).freqList.Len(); l != 1 { 185 | t.Fatalf("%v != 1", l) 186 | } 187 | gc.Set(k0, v0) 188 | if l := gc.(*LFUCache).freqList.Len(); l != 1 { 189 | t.Fatalf("%v != 1", l) 190 | } 191 | gc.Get(k0) 192 | if l := gc.(*LFUCache).freqList.Len(); l != 2 { 193 | t.Fatalf("%v != 2", l) 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GCache 2 | 3 | ![Test](https://github.com/bluele/gcache/workflows/Test/badge.svg) 4 | [![GoDoc](https://godoc.org/github.com/bluele/gcache?status.svg)](https://pkg.go.dev/github.com/bluele/gcache?tab=doc) 5 | 6 | Cache library for golang. It supports expirable Cache, LFU, LRU and ARC. 7 | 8 | ## Features 9 | 10 | * Supports expirable Cache, LFU, LRU and ARC. 11 | 12 | * Goroutine safe. 13 | 14 | * Supports event handlers which evict, purge, and add entry. (Optional) 15 | 16 | * Automatically load cache if it doesn't exists. (Optional) 17 | 18 | ## Install 19 | 20 | ``` 21 | $ go get github.com/bluele/gcache 22 | ``` 23 | 24 | ## Example 25 | 26 | ### Manually set a key-value pair. 27 | 28 | ```go 29 | package main 30 | 31 | import ( 32 | "github.com/bluele/gcache" 33 | "fmt" 34 | ) 35 | 36 | func main() { 37 | gc := gcache.New(20). 38 | LRU(). 39 | Build() 40 | gc.Set("key", "ok") 41 | value, err := gc.Get("key") 42 | if err != nil { 43 | panic(err) 44 | } 45 | fmt.Println("Get:", value) 46 | } 47 | ``` 48 | 49 | ``` 50 | Get: ok 51 | ``` 52 | 53 | ### Manually set a key-value pair, with an expiration time. 54 | 55 | ```go 56 | package main 57 | 58 | import ( 59 | "github.com/bluele/gcache" 60 | "fmt" 61 | "time" 62 | ) 63 | 64 | func main() { 65 | gc := gcache.New(20). 66 | LRU(). 67 | Build() 68 | gc.SetWithExpire("key", "ok", time.Second*10) 69 | value, _ := gc.Get("key") 70 | fmt.Println("Get:", value) 71 | 72 | // Wait for value to expire 73 | time.Sleep(time.Second*10) 74 | 75 | value, err := gc.Get("key") 76 | if err != nil { 77 | panic(err) 78 | } 79 | fmt.Println("Get:", value) 80 | } 81 | ``` 82 | 83 | ``` 84 | Get: ok 85 | // 10 seconds later, new attempt: 86 | panic: ErrKeyNotFound 87 | ``` 88 | 89 | 90 | ### Automatically load value 91 | 92 | ```go 93 | package main 94 | 95 | import ( 96 | "github.com/bluele/gcache" 97 | "fmt" 98 | ) 99 | 100 | func main() { 101 | gc := gcache.New(20). 102 | LRU(). 103 | LoaderFunc(func(key interface{}) (interface{}, error) { 104 | return "ok", nil 105 | }). 106 | Build() 107 | value, err := gc.Get("key") 108 | if err != nil { 109 | panic(err) 110 | } 111 | fmt.Println("Get:", value) 112 | } 113 | ``` 114 | 115 | ``` 116 | Get: ok 117 | ``` 118 | 119 | ### Automatically load value with expiration 120 | 121 | ```go 122 | package main 123 | 124 | import ( 125 | "fmt" 126 | "time" 127 | 128 | "github.com/bluele/gcache" 129 | ) 130 | 131 | func main() { 132 | var evictCounter, loaderCounter, purgeCounter int 133 | gc := gcache.New(20). 134 | LRU(). 135 | LoaderExpireFunc(func(key interface{}) (interface{}, *time.Duration, error) { 136 | loaderCounter++ 137 | expire := 1 * time.Second 138 | return "ok", &expire, nil 139 | }). 140 | EvictedFunc(func(key, value interface{}) { 141 | evictCounter++ 142 | fmt.Println("evicted key:", key) 143 | }). 144 | PurgeVisitorFunc(func(key, value interface{}) { 145 | purgeCounter++ 146 | fmt.Println("purged key:", key) 147 | }). 148 | Build() 149 | value, err := gc.Get("key") 150 | if err != nil { 151 | panic(err) 152 | } 153 | fmt.Println("Get:", value) 154 | time.Sleep(1 * time.Second) 155 | value, err = gc.Get("key") 156 | if err != nil { 157 | panic(err) 158 | } 159 | fmt.Println("Get:", value) 160 | gc.Purge() 161 | if loaderCounter != evictCounter+purgeCounter { 162 | panic("bad") 163 | } 164 | } 165 | ``` 166 | 167 | ``` 168 | Get: ok 169 | evicted key: key 170 | Get: ok 171 | purged key: key 172 | ``` 173 | 174 | 175 | ## Cache Algorithm 176 | 177 | * Least-Frequently Used (LFU) 178 | 179 | Discards the least frequently used items first. 180 | 181 | ```go 182 | func main() { 183 | // size: 10 184 | gc := gcache.New(10). 185 | LFU(). 186 | Build() 187 | gc.Set("key", "value") 188 | } 189 | ``` 190 | 191 | * Least Recently Used (LRU) 192 | 193 | Discards the least recently used items first. 194 | 195 | ```go 196 | func main() { 197 | // size: 10 198 | gc := gcache.New(10). 199 | LRU(). 200 | Build() 201 | gc.Set("key", "value") 202 | } 203 | ``` 204 | 205 | * Adaptive Replacement Cache (ARC) 206 | 207 | Constantly balances between LRU and LFU, to improve the combined result. 208 | 209 | detail: http://en.wikipedia.org/wiki/Adaptive_replacement_cache 210 | 211 | ```go 212 | func main() { 213 | // size: 10 214 | gc := gcache.New(10). 215 | ARC(). 216 | Build() 217 | gc.Set("key", "value") 218 | } 219 | ``` 220 | 221 | * SimpleCache (Default) 222 | 223 | SimpleCache has no clear priority for evict cache. It depends on key-value map order. 224 | 225 | ```go 226 | func main() { 227 | // size: 10 228 | gc := gcache.New(10).Build() 229 | gc.Set("key", "value") 230 | v, err := gc.Get("key") 231 | if err != nil { 232 | panic(err) 233 | } 234 | } 235 | ``` 236 | 237 | ## Loading Cache 238 | 239 | If specified `LoaderFunc`, values are automatically loaded by the cache, and are stored in the cache until either evicted or manually invalidated. 240 | 241 | ```go 242 | func main() { 243 | gc := gcache.New(10). 244 | LRU(). 245 | LoaderFunc(func(key interface{}) (interface{}, error) { 246 | return "value", nil 247 | }). 248 | Build() 249 | v, _ := gc.Get("key") 250 | // output: "value" 251 | fmt.Println(v) 252 | } 253 | ``` 254 | 255 | 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. 256 | 257 | ## Expirable cache 258 | 259 | ```go 260 | func main() { 261 | // LRU cache, size: 10, expiration: after a hour 262 | gc := gcache.New(10). 263 | LRU(). 264 | Expiration(time.Hour). 265 | Build() 266 | } 267 | ``` 268 | 269 | ## Event handlers 270 | 271 | ### Evicted handler 272 | 273 | Event handler for evict the entry. 274 | 275 | ```go 276 | func main() { 277 | gc := gcache.New(2). 278 | EvictedFunc(func(key, value interface{}) { 279 | fmt.Println("evicted key:", key) 280 | }). 281 | Build() 282 | for i := 0; i < 3; i++ { 283 | gc.Set(i, i*i) 284 | } 285 | } 286 | ``` 287 | 288 | ``` 289 | evicted key: 0 290 | ``` 291 | 292 | ### Added handler 293 | 294 | Event handler for add the entry. 295 | 296 | ```go 297 | func main() { 298 | gc := gcache.New(2). 299 | AddedFunc(func(key, value interface{}) { 300 | fmt.Println("added key:", key) 301 | }). 302 | Build() 303 | for i := 0; i < 3; i++ { 304 | gc.Set(i, i*i) 305 | } 306 | } 307 | ``` 308 | 309 | ``` 310 | added key: 0 311 | added key: 1 312 | added key: 2 313 | ``` 314 | 315 | # Author 316 | 317 | **Jun Kimura** 318 | 319 | * 320 | * 321 | -------------------------------------------------------------------------------- /cache_test.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "sync" 7 | "sync/atomic" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestLoaderFunc(t *testing.T) { 13 | size := 2 14 | var testCaches = []*CacheBuilder{ 15 | New(size).Simple(), 16 | New(size).LRU(), 17 | New(size).LFU(), 18 | New(size).ARC(), 19 | } 20 | for _, builder := range testCaches { 21 | var testCounter int64 22 | counter := 1000 23 | cache := builder. 24 | LoaderFunc(func(key interface{}) (interface{}, error) { 25 | time.Sleep(10 * time.Millisecond) 26 | return atomic.AddInt64(&testCounter, 1), nil 27 | }). 28 | EvictedFunc(func(key, value interface{}) { 29 | panic(key) 30 | }).Build() 31 | 32 | var wg sync.WaitGroup 33 | for i := 0; i < counter; i++ { 34 | wg.Add(1) 35 | go func() { 36 | defer wg.Done() 37 | _, err := cache.Get(0) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | }() 42 | } 43 | wg.Wait() 44 | 45 | if testCounter != 1 { 46 | t.Errorf("testCounter != %v", testCounter) 47 | } 48 | } 49 | } 50 | 51 | func TestLoaderExpireFuncWithoutExpire(t *testing.T) { 52 | size := 2 53 | var testCaches = []*CacheBuilder{ 54 | New(size).Simple(), 55 | New(size).LRU(), 56 | New(size).LFU(), 57 | New(size).ARC(), 58 | } 59 | for _, builder := range testCaches { 60 | var testCounter int64 61 | counter := 1000 62 | cache := builder. 63 | LoaderExpireFunc(func(key interface{}) (interface{}, *time.Duration, error) { 64 | return atomic.AddInt64(&testCounter, 1), nil, nil 65 | }). 66 | EvictedFunc(func(key, value interface{}) { 67 | panic(key) 68 | }).Build() 69 | 70 | var wg sync.WaitGroup 71 | for i := 0; i < counter; i++ { 72 | wg.Add(1) 73 | go func() { 74 | defer wg.Done() 75 | _, err := cache.Get(0) 76 | if err != nil { 77 | t.Error(err) 78 | } 79 | }() 80 | } 81 | 82 | wg.Wait() 83 | 84 | if testCounter != 1 { 85 | t.Errorf("testCounter != %v", testCounter) 86 | } 87 | } 88 | } 89 | 90 | func TestLoaderExpireFuncWithExpire(t *testing.T) { 91 | size := 2 92 | var testCaches = []*CacheBuilder{ 93 | New(size).Simple(), 94 | New(size).LRU(), 95 | New(size).LFU(), 96 | New(size).ARC(), 97 | } 98 | for _, builder := range testCaches { 99 | var testCounter int64 100 | counter := 1000 101 | expire := 200 * time.Millisecond 102 | cache := builder. 103 | LoaderExpireFunc(func(key interface{}) (interface{}, *time.Duration, error) { 104 | return atomic.AddInt64(&testCounter, 1), &expire, nil 105 | }). 106 | Build() 107 | 108 | var wg sync.WaitGroup 109 | for i := 0; i < counter; i++ { 110 | wg.Add(1) 111 | go func() { 112 | defer wg.Done() 113 | _, err := cache.Get(0) 114 | if err != nil { 115 | t.Error(err) 116 | } 117 | }() 118 | } 119 | time.Sleep(expire) // Waiting for key expiration 120 | for i := 0; i < counter; i++ { 121 | wg.Add(1) 122 | go func() { 123 | defer wg.Done() 124 | _, err := cache.Get(0) 125 | if err != nil { 126 | t.Error(err) 127 | } 128 | }() 129 | } 130 | 131 | wg.Wait() 132 | 133 | if testCounter != 2 { 134 | t.Errorf("testCounter != %v", testCounter) 135 | } 136 | } 137 | } 138 | 139 | func TestLoaderPurgeVisitorFunc(t *testing.T) { 140 | size := 7 141 | tests := []struct { 142 | name string 143 | cacheBuilder *CacheBuilder 144 | }{ 145 | { 146 | name: "simple", 147 | cacheBuilder: New(size).Simple(), 148 | }, 149 | { 150 | name: "lru", 151 | cacheBuilder: New(size).LRU(), 152 | }, 153 | { 154 | name: "lfu", 155 | cacheBuilder: New(size).LFU(), 156 | }, 157 | { 158 | name: "arc", 159 | cacheBuilder: New(size).ARC(), 160 | }, 161 | } 162 | 163 | for _, test := range tests { 164 | var purgeCounter, evictCounter, loaderCounter int64 165 | counter := 1000 166 | cache := test.cacheBuilder. 167 | LoaderFunc(func(key interface{}) (interface{}, error) { 168 | return atomic.AddInt64(&loaderCounter, 1), nil 169 | }). 170 | EvictedFunc(func(key, value interface{}) { 171 | atomic.AddInt64(&evictCounter, 1) 172 | }). 173 | PurgeVisitorFunc(func(k, v interface{}) { 174 | atomic.AddInt64(&purgeCounter, 1) 175 | }). 176 | Build() 177 | 178 | var wg sync.WaitGroup 179 | for i := 0; i < counter; i++ { 180 | i := i 181 | wg.Add(1) 182 | go func() { 183 | defer wg.Done() 184 | _, err := cache.Get(i) 185 | if err != nil { 186 | t.Error(err) 187 | } 188 | }() 189 | } 190 | 191 | wg.Wait() 192 | 193 | if loaderCounter != int64(counter) { 194 | t.Errorf("%s: loaderCounter != %v", test.name, loaderCounter) 195 | } 196 | 197 | cache.Purge() 198 | 199 | if evictCounter+purgeCounter != loaderCounter { 200 | t.Logf("%s: evictCounter: %d", test.name, evictCounter) 201 | t.Logf("%s: purgeCounter: %d", test.name, purgeCounter) 202 | t.Logf("%s: loaderCounter: %d", test.name, loaderCounter) 203 | t.Errorf("%s: load != evict+purge", test.name) 204 | } 205 | } 206 | } 207 | 208 | func TestDeserializeFunc(t *testing.T) { 209 | var cases = []struct { 210 | tp string 211 | }{ 212 | {TYPE_SIMPLE}, 213 | {TYPE_LRU}, 214 | {TYPE_LFU}, 215 | {TYPE_ARC}, 216 | } 217 | 218 | for _, cs := range cases { 219 | key1, value1 := "key1", "value1" 220 | key2, value2 := "key2", "value2" 221 | cc := New(32). 222 | EvictType(cs.tp). 223 | LoaderFunc(func(k interface{}) (interface{}, error) { 224 | return value1, nil 225 | }). 226 | DeserializeFunc(func(k, v interface{}) (interface{}, error) { 227 | dec := gob.NewDecoder(bytes.NewBuffer(v.([]byte))) 228 | var str string 229 | err := dec.Decode(&str) 230 | if err != nil { 231 | return nil, err 232 | } 233 | return str, nil 234 | }). 235 | SerializeFunc(func(k, v interface{}) (interface{}, error) { 236 | buf := new(bytes.Buffer) 237 | enc := gob.NewEncoder(buf) 238 | err := enc.Encode(v) 239 | return buf.Bytes(), err 240 | }). 241 | Build() 242 | v, err := cc.Get(key1) 243 | if err != nil { 244 | t.Fatal(err) 245 | } 246 | if v != value1 { 247 | t.Errorf("%v != %v", v, value1) 248 | } 249 | v, err = cc.Get(key1) 250 | if err != nil { 251 | t.Fatal(err) 252 | } 253 | if v != value1 { 254 | t.Errorf("%v != %v", v, value1) 255 | } 256 | if err := cc.Set(key2, value2); err != nil { 257 | t.Error(err) 258 | } 259 | v, err = cc.Get(key2) 260 | if err != nil { 261 | t.Error(err) 262 | } 263 | if v != value2 { 264 | t.Errorf("%v != %v", v, value2) 265 | } 266 | } 267 | } 268 | 269 | func TestExpiredItems(t *testing.T) { 270 | var tps = []string{ 271 | TYPE_SIMPLE, 272 | TYPE_LRU, 273 | TYPE_LFU, 274 | TYPE_ARC, 275 | } 276 | for _, tp := range tps { 277 | t.Run(tp, func(t *testing.T) { 278 | testExpiredItems(t, tp) 279 | }) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | const ( 11 | TYPE_SIMPLE = "simple" 12 | TYPE_LRU = "lru" 13 | TYPE_LFU = "lfu" 14 | TYPE_ARC = "arc" 15 | ) 16 | 17 | var KeyNotFoundError = errors.New("Key not found.") 18 | 19 | type Cache interface { 20 | // Set inserts or updates the specified key-value pair. 21 | Set(key, value interface{}) error 22 | // SetWithExpire inserts or updates the specified key-value pair with an expiration time. 23 | SetWithExpire(key, value interface{}, expiration time.Duration) error 24 | // Get returns the value for the specified key if it is present in the cache. 25 | // If the key is not present in the cache and the cache has LoaderFunc, 26 | // invoke the `LoaderFunc` function and inserts the key-value pair in the cache. 27 | // If the key is not present in the cache and the cache does not have a LoaderFunc, 28 | // return KeyNotFoundError. 29 | Get(key interface{}) (interface{}, error) 30 | // GetIFPresent returns the value for the specified key if it is present in the cache. 31 | // Return KeyNotFoundError if the key is not present. 32 | GetIFPresent(key interface{}) (interface{}, error) 33 | // GetAll returns a map containing all key-value pairs in the cache. 34 | GetALL(checkExpired bool) map[interface{}]interface{} 35 | get(key interface{}, onLoad bool) (interface{}, error) 36 | // Remove removes the specified key from the cache if the key is present. 37 | // Returns true if the key was present and the key has been deleted. 38 | Remove(key interface{}) bool 39 | // Purge removes all key-value pairs from the cache. 40 | Purge() 41 | // Keys returns a slice containing all keys in the cache. 42 | Keys(checkExpired bool) []interface{} 43 | // Len returns the number of items in the cache. 44 | Len(checkExpired bool) int 45 | // Has returns true if the key exists in the cache. 46 | Has(key interface{}) bool 47 | 48 | statsAccessor 49 | } 50 | 51 | type baseCache struct { 52 | clock Clock 53 | size int 54 | loaderExpireFunc LoaderExpireFunc 55 | evictedFunc EvictedFunc 56 | purgeVisitorFunc PurgeVisitorFunc 57 | addedFunc AddedFunc 58 | deserializeFunc DeserializeFunc 59 | serializeFunc SerializeFunc 60 | expiration *time.Duration 61 | mu sync.RWMutex 62 | loadGroup Group 63 | *stats 64 | } 65 | 66 | type ( 67 | LoaderFunc func(interface{}) (interface{}, error) 68 | LoaderExpireFunc func(interface{}) (interface{}, *time.Duration, error) 69 | EvictedFunc func(interface{}, interface{}) 70 | PurgeVisitorFunc func(interface{}, interface{}) 71 | AddedFunc func(interface{}, interface{}) 72 | DeserializeFunc func(interface{}, interface{}) (interface{}, error) 73 | SerializeFunc func(interface{}, interface{}) (interface{}, error) 74 | ) 75 | 76 | type CacheBuilder struct { 77 | clock Clock 78 | tp string 79 | size int 80 | loaderExpireFunc LoaderExpireFunc 81 | evictedFunc EvictedFunc 82 | purgeVisitorFunc PurgeVisitorFunc 83 | addedFunc AddedFunc 84 | expiration *time.Duration 85 | deserializeFunc DeserializeFunc 86 | serializeFunc SerializeFunc 87 | } 88 | 89 | func New(size int) *CacheBuilder { 90 | return &CacheBuilder{ 91 | clock: NewRealClock(), 92 | tp: TYPE_SIMPLE, 93 | size: size, 94 | } 95 | } 96 | 97 | func (cb *CacheBuilder) Clock(clock Clock) *CacheBuilder { 98 | cb.clock = clock 99 | return cb 100 | } 101 | 102 | // Set a loader function. 103 | // loaderFunc: create a new value with this function if cached value is expired. 104 | func (cb *CacheBuilder) LoaderFunc(loaderFunc LoaderFunc) *CacheBuilder { 105 | cb.loaderExpireFunc = func(k interface{}) (interface{}, *time.Duration, error) { 106 | v, err := loaderFunc(k) 107 | return v, nil, err 108 | } 109 | return cb 110 | } 111 | 112 | // Set a loader function with expiration. 113 | // loaderExpireFunc: create a new value with this function if cached value is expired. 114 | // If nil returned instead of time.Duration from loaderExpireFunc than value will never expire. 115 | func (cb *CacheBuilder) LoaderExpireFunc(loaderExpireFunc LoaderExpireFunc) *CacheBuilder { 116 | cb.loaderExpireFunc = loaderExpireFunc 117 | return cb 118 | } 119 | 120 | func (cb *CacheBuilder) EvictType(tp string) *CacheBuilder { 121 | cb.tp = tp 122 | return cb 123 | } 124 | 125 | func (cb *CacheBuilder) Simple() *CacheBuilder { 126 | return cb.EvictType(TYPE_SIMPLE) 127 | } 128 | 129 | func (cb *CacheBuilder) LRU() *CacheBuilder { 130 | return cb.EvictType(TYPE_LRU) 131 | } 132 | 133 | func (cb *CacheBuilder) LFU() *CacheBuilder { 134 | return cb.EvictType(TYPE_LFU) 135 | } 136 | 137 | func (cb *CacheBuilder) ARC() *CacheBuilder { 138 | return cb.EvictType(TYPE_ARC) 139 | } 140 | 141 | func (cb *CacheBuilder) EvictedFunc(evictedFunc EvictedFunc) *CacheBuilder { 142 | cb.evictedFunc = evictedFunc 143 | return cb 144 | } 145 | 146 | func (cb *CacheBuilder) PurgeVisitorFunc(purgeVisitorFunc PurgeVisitorFunc) *CacheBuilder { 147 | cb.purgeVisitorFunc = purgeVisitorFunc 148 | return cb 149 | } 150 | 151 | func (cb *CacheBuilder) AddedFunc(addedFunc AddedFunc) *CacheBuilder { 152 | cb.addedFunc = addedFunc 153 | return cb 154 | } 155 | 156 | func (cb *CacheBuilder) DeserializeFunc(deserializeFunc DeserializeFunc) *CacheBuilder { 157 | cb.deserializeFunc = deserializeFunc 158 | return cb 159 | } 160 | 161 | func (cb *CacheBuilder) SerializeFunc(serializeFunc SerializeFunc) *CacheBuilder { 162 | cb.serializeFunc = serializeFunc 163 | return cb 164 | } 165 | 166 | func (cb *CacheBuilder) Expiration(expiration time.Duration) *CacheBuilder { 167 | cb.expiration = &expiration 168 | return cb 169 | } 170 | 171 | func (cb *CacheBuilder) Build() Cache { 172 | if cb.size <= 0 && cb.tp != TYPE_SIMPLE { 173 | panic("gcache: Cache size <= 0") 174 | } 175 | 176 | return cb.build() 177 | } 178 | 179 | func (cb *CacheBuilder) build() Cache { 180 | switch cb.tp { 181 | case TYPE_SIMPLE: 182 | return newSimpleCache(cb) 183 | case TYPE_LRU: 184 | return newLRUCache(cb) 185 | case TYPE_LFU: 186 | return newLFUCache(cb) 187 | case TYPE_ARC: 188 | return newARC(cb) 189 | default: 190 | panic("gcache: Unknown type " + cb.tp) 191 | } 192 | } 193 | 194 | func buildCache(c *baseCache, cb *CacheBuilder) { 195 | c.clock = cb.clock 196 | c.size = cb.size 197 | c.loaderExpireFunc = cb.loaderExpireFunc 198 | c.expiration = cb.expiration 199 | c.addedFunc = cb.addedFunc 200 | c.deserializeFunc = cb.deserializeFunc 201 | c.serializeFunc = cb.serializeFunc 202 | c.evictedFunc = cb.evictedFunc 203 | c.purgeVisitorFunc = cb.purgeVisitorFunc 204 | c.stats = &stats{} 205 | } 206 | 207 | // load a new value using by specified key. 208 | func (c *baseCache) load(key interface{}, cb func(interface{}, *time.Duration, error) (interface{}, error), isWait bool) (interface{}, bool, error) { 209 | v, called, err := c.loadGroup.Do(key, func() (v interface{}, e error) { 210 | defer func() { 211 | if r := recover(); r != nil { 212 | e = fmt.Errorf("Loader panics: %v", r) 213 | } 214 | }() 215 | return cb(c.loaderExpireFunc(key)) 216 | }, isWait) 217 | if err != nil { 218 | return nil, called, err 219 | } 220 | return v, called, nil 221 | } 222 | -------------------------------------------------------------------------------- /simple.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // SimpleCache has no clear priority for evict cache. It depends on key-value map order. 8 | type SimpleCache struct { 9 | baseCache 10 | items map[interface{}]*simpleItem 11 | } 12 | 13 | func newSimpleCache(cb *CacheBuilder) *SimpleCache { 14 | c := &SimpleCache{} 15 | buildCache(&c.baseCache, cb) 16 | 17 | c.init() 18 | c.loadGroup.cache = c 19 | return c 20 | } 21 | 22 | func (c *SimpleCache) init() { 23 | if c.size <= 0 { 24 | c.items = make(map[interface{}]*simpleItem) 25 | } else { 26 | c.items = make(map[interface{}]*simpleItem, c.size) 27 | } 28 | } 29 | 30 | // Set a new key-value pair 31 | func (c *SimpleCache) Set(key, value interface{}) error { 32 | c.mu.Lock() 33 | defer c.mu.Unlock() 34 | _, err := c.set(key, value) 35 | return err 36 | } 37 | 38 | // Set a new key-value pair with an expiration time 39 | func (c *SimpleCache) SetWithExpire(key, value interface{}, expiration time.Duration) error { 40 | c.mu.Lock() 41 | defer c.mu.Unlock() 42 | item, err := c.set(key, value) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | t := c.clock.Now().Add(expiration) 48 | item.(*simpleItem).expiration = &t 49 | return nil 50 | } 51 | 52 | func (c *SimpleCache) set(key, value interface{}) (interface{}, error) { 53 | var err error 54 | if c.serializeFunc != nil { 55 | value, err = c.serializeFunc(key, value) 56 | if err != nil { 57 | return nil, err 58 | } 59 | } 60 | 61 | // Check for existing item 62 | item, ok := c.items[key] 63 | if ok { 64 | item.value = value 65 | } else { 66 | // Verify size not exceeded 67 | if (len(c.items) >= c.size) && c.size > 0 { 68 | c.evict(1) 69 | } 70 | item = &simpleItem{ 71 | clock: c.clock, 72 | value: value, 73 | } 74 | c.items[key] = item 75 | } 76 | 77 | if c.expiration != nil { 78 | t := c.clock.Now().Add(*c.expiration) 79 | item.expiration = &t 80 | } 81 | 82 | if c.addedFunc != nil { 83 | c.addedFunc(key, value) 84 | } 85 | 86 | return item, nil 87 | } 88 | 89 | // Get a value from cache pool using key if it exists. 90 | // If it does not exists key and has LoaderFunc, 91 | // generate a value using `LoaderFunc` method returns value. 92 | func (c *SimpleCache) Get(key interface{}) (interface{}, error) { 93 | v, err := c.get(key, false) 94 | if err == KeyNotFoundError { 95 | return c.getWithLoader(key, true) 96 | } 97 | return v, err 98 | } 99 | 100 | // GetIFPresent gets a value from cache pool using key if it exists. 101 | // If it does not exists key, returns KeyNotFoundError. 102 | // And send a request which refresh value for specified key if cache object has LoaderFunc. 103 | func (c *SimpleCache) GetIFPresent(key interface{}) (interface{}, error) { 104 | v, err := c.get(key, false) 105 | if err == KeyNotFoundError { 106 | return c.getWithLoader(key, false) 107 | } 108 | return v, nil 109 | } 110 | 111 | func (c *SimpleCache) get(key interface{}, onLoad bool) (interface{}, error) { 112 | v, err := c.getValue(key, onLoad) 113 | if err != nil { 114 | return nil, err 115 | } 116 | if c.deserializeFunc != nil { 117 | return c.deserializeFunc(key, v) 118 | } 119 | return v, nil 120 | } 121 | 122 | func (c *SimpleCache) getValue(key interface{}, onLoad bool) (interface{}, error) { 123 | c.mu.Lock() 124 | item, ok := c.items[key] 125 | if ok { 126 | if !item.IsExpired(nil) { 127 | v := item.value 128 | c.mu.Unlock() 129 | if !onLoad { 130 | c.stats.IncrHitCount() 131 | } 132 | return v, nil 133 | } 134 | c.remove(key) 135 | } 136 | c.mu.Unlock() 137 | if !onLoad { 138 | c.stats.IncrMissCount() 139 | } 140 | return nil, KeyNotFoundError 141 | } 142 | 143 | func (c *SimpleCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { 144 | if c.loaderExpireFunc == nil { 145 | return nil, KeyNotFoundError 146 | } 147 | value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { 148 | if e != nil { 149 | return nil, e 150 | } 151 | c.mu.Lock() 152 | defer c.mu.Unlock() 153 | item, err := c.set(key, v) 154 | if err != nil { 155 | return nil, err 156 | } 157 | if expiration != nil { 158 | t := c.clock.Now().Add(*expiration) 159 | item.(*simpleItem).expiration = &t 160 | } 161 | return v, nil 162 | }, isWait) 163 | if err != nil { 164 | return nil, err 165 | } 166 | return value, nil 167 | } 168 | 169 | func (c *SimpleCache) evict(count int) { 170 | now := c.clock.Now() 171 | current := 0 172 | for key, item := range c.items { 173 | if current >= count { 174 | return 175 | } 176 | if item.expiration == nil || now.After(*item.expiration) { 177 | defer c.remove(key) 178 | current++ 179 | } 180 | } 181 | } 182 | 183 | // Has checks if key exists in cache 184 | func (c *SimpleCache) Has(key interface{}) bool { 185 | c.mu.RLock() 186 | defer c.mu.RUnlock() 187 | now := time.Now() 188 | return c.has(key, &now) 189 | } 190 | 191 | func (c *SimpleCache) has(key interface{}, now *time.Time) bool { 192 | item, ok := c.items[key] 193 | if !ok { 194 | return false 195 | } 196 | return !item.IsExpired(now) 197 | } 198 | 199 | // Remove removes the provided key from the cache. 200 | func (c *SimpleCache) Remove(key interface{}) bool { 201 | c.mu.Lock() 202 | defer c.mu.Unlock() 203 | 204 | return c.remove(key) 205 | } 206 | 207 | func (c *SimpleCache) remove(key interface{}) bool { 208 | item, ok := c.items[key] 209 | if ok { 210 | delete(c.items, key) 211 | if c.evictedFunc != nil { 212 | c.evictedFunc(key, item.value) 213 | } 214 | return true 215 | } 216 | return false 217 | } 218 | 219 | // Returns a slice of the keys in the cache. 220 | func (c *SimpleCache) keys() []interface{} { 221 | c.mu.RLock() 222 | defer c.mu.RUnlock() 223 | keys := make([]interface{}, len(c.items)) 224 | var i = 0 225 | for k := range c.items { 226 | keys[i] = k 227 | i++ 228 | } 229 | return keys 230 | } 231 | 232 | // GetALL returns all key-value pairs in the cache. 233 | func (c *SimpleCache) GetALL(checkExpired bool) map[interface{}]interface{} { 234 | c.mu.RLock() 235 | defer c.mu.RUnlock() 236 | items := make(map[interface{}]interface{}, len(c.items)) 237 | now := time.Now() 238 | for k, item := range c.items { 239 | if !checkExpired || c.has(k, &now) { 240 | items[k] = item.value 241 | } 242 | } 243 | return items 244 | } 245 | 246 | // Keys returns a slice of the keys in the cache. 247 | func (c *SimpleCache) Keys(checkExpired bool) []interface{} { 248 | c.mu.RLock() 249 | defer c.mu.RUnlock() 250 | keys := make([]interface{}, 0, len(c.items)) 251 | now := time.Now() 252 | for k := range c.items { 253 | if !checkExpired || c.has(k, &now) { 254 | keys = append(keys, k) 255 | } 256 | } 257 | return keys 258 | } 259 | 260 | // Len returns the number of items in the cache. 261 | func (c *SimpleCache) Len(checkExpired bool) int { 262 | c.mu.RLock() 263 | defer c.mu.RUnlock() 264 | if !checkExpired { 265 | return len(c.items) 266 | } 267 | var length int 268 | now := time.Now() 269 | for k := range c.items { 270 | if c.has(k, &now) { 271 | length++ 272 | } 273 | } 274 | return length 275 | } 276 | 277 | // Completely clear the cache 278 | func (c *SimpleCache) Purge() { 279 | c.mu.Lock() 280 | defer c.mu.Unlock() 281 | 282 | if c.purgeVisitorFunc != nil { 283 | for key, item := range c.items { 284 | c.purgeVisitorFunc(key, item.value) 285 | } 286 | } 287 | 288 | c.init() 289 | } 290 | 291 | type simpleItem struct { 292 | clock Clock 293 | value interface{} 294 | expiration *time.Time 295 | } 296 | 297 | // IsExpired returns boolean value whether this item is expired or not. 298 | func (si *simpleItem) IsExpired(now *time.Time) bool { 299 | if si.expiration == nil { 300 | return false 301 | } 302 | if now == nil { 303 | t := si.clock.Now() 304 | now = &t 305 | } 306 | return si.expiration.Before(*now) 307 | } 308 | -------------------------------------------------------------------------------- /lru.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | // Discards the least recently used items first. 9 | type LRUCache struct { 10 | baseCache 11 | items map[interface{}]*list.Element 12 | evictList *list.List 13 | } 14 | 15 | func newLRUCache(cb *CacheBuilder) *LRUCache { 16 | c := &LRUCache{} 17 | buildCache(&c.baseCache, cb) 18 | 19 | c.init() 20 | c.loadGroup.cache = c 21 | return c 22 | } 23 | 24 | func (c *LRUCache) init() { 25 | c.evictList = list.New() 26 | c.items = make(map[interface{}]*list.Element, c.size+1) 27 | } 28 | 29 | func (c *LRUCache) set(key, value interface{}) (interface{}, error) { 30 | var err error 31 | if c.serializeFunc != nil { 32 | value, err = c.serializeFunc(key, value) 33 | if err != nil { 34 | return nil, err 35 | } 36 | } 37 | 38 | // Check for existing item 39 | var item *lruItem 40 | if it, ok := c.items[key]; ok { 41 | c.evictList.MoveToFront(it) 42 | item = it.Value.(*lruItem) 43 | item.value = value 44 | } else { 45 | // Verify size not exceeded 46 | if c.evictList.Len() >= c.size { 47 | c.evict(1) 48 | } 49 | item = &lruItem{ 50 | clock: c.clock, 51 | key: key, 52 | value: value, 53 | } 54 | c.items[key] = c.evictList.PushFront(item) 55 | } 56 | 57 | if c.expiration != nil { 58 | t := c.clock.Now().Add(*c.expiration) 59 | item.expiration = &t 60 | } 61 | 62 | if c.addedFunc != nil { 63 | c.addedFunc(key, value) 64 | } 65 | 66 | return item, nil 67 | } 68 | 69 | // set a new key-value pair 70 | func (c *LRUCache) Set(key, value interface{}) error { 71 | c.mu.Lock() 72 | defer c.mu.Unlock() 73 | _, err := c.set(key, value) 74 | return err 75 | } 76 | 77 | // Set a new key-value pair with an expiration time 78 | func (c *LRUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error { 79 | c.mu.Lock() 80 | defer c.mu.Unlock() 81 | item, err := c.set(key, value) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | t := c.clock.Now().Add(expiration) 87 | item.(*lruItem).expiration = &t 88 | return nil 89 | } 90 | 91 | // Get a value from cache pool using key if it exists. 92 | // If it does not exists key and has LoaderFunc, 93 | // generate a value using `LoaderFunc` method returns value. 94 | func (c *LRUCache) Get(key interface{}) (interface{}, error) { 95 | v, err := c.get(key, false) 96 | if err == KeyNotFoundError { 97 | return c.getWithLoader(key, true) 98 | } 99 | return v, err 100 | } 101 | 102 | // GetIFPresent gets a value from cache pool using key if it exists. 103 | // If it does not exists key, returns KeyNotFoundError. 104 | // And send a request which refresh value for specified key if cache object has LoaderFunc. 105 | func (c *LRUCache) GetIFPresent(key interface{}) (interface{}, error) { 106 | v, err := c.get(key, false) 107 | if err == KeyNotFoundError { 108 | return c.getWithLoader(key, false) 109 | } 110 | return v, err 111 | } 112 | 113 | func (c *LRUCache) get(key interface{}, onLoad bool) (interface{}, error) { 114 | v, err := c.getValue(key, onLoad) 115 | if err != nil { 116 | return nil, err 117 | } 118 | if c.deserializeFunc != nil { 119 | return c.deserializeFunc(key, v) 120 | } 121 | return v, nil 122 | } 123 | 124 | func (c *LRUCache) getValue(key interface{}, onLoad bool) (interface{}, error) { 125 | c.mu.Lock() 126 | item, ok := c.items[key] 127 | if ok { 128 | it := item.Value.(*lruItem) 129 | if !it.IsExpired(nil) { 130 | c.evictList.MoveToFront(item) 131 | v := it.value 132 | c.mu.Unlock() 133 | if !onLoad { 134 | c.stats.IncrHitCount() 135 | } 136 | return v, nil 137 | } 138 | c.removeElement(item) 139 | } 140 | c.mu.Unlock() 141 | if !onLoad { 142 | c.stats.IncrMissCount() 143 | } 144 | return nil, KeyNotFoundError 145 | } 146 | 147 | func (c *LRUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { 148 | if c.loaderExpireFunc == nil { 149 | return nil, KeyNotFoundError 150 | } 151 | value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { 152 | if e != nil { 153 | return nil, e 154 | } 155 | c.mu.Lock() 156 | defer c.mu.Unlock() 157 | item, err := c.set(key, v) 158 | if err != nil { 159 | return nil, err 160 | } 161 | if expiration != nil { 162 | t := c.clock.Now().Add(*expiration) 163 | item.(*lruItem).expiration = &t 164 | } 165 | return v, nil 166 | }, isWait) 167 | if err != nil { 168 | return nil, err 169 | } 170 | return value, nil 171 | } 172 | 173 | // evict removes the oldest item from the cache. 174 | func (c *LRUCache) evict(count int) { 175 | for i := 0; i < count; i++ { 176 | ent := c.evictList.Back() 177 | if ent == nil { 178 | return 179 | } else { 180 | c.removeElement(ent) 181 | } 182 | } 183 | } 184 | 185 | // Has checks if key exists in cache 186 | func (c *LRUCache) Has(key interface{}) bool { 187 | c.mu.RLock() 188 | defer c.mu.RUnlock() 189 | now := time.Now() 190 | return c.has(key, &now) 191 | } 192 | 193 | func (c *LRUCache) has(key interface{}, now *time.Time) bool { 194 | item, ok := c.items[key] 195 | if !ok { 196 | return false 197 | } 198 | return !item.Value.(*lruItem).IsExpired(now) 199 | } 200 | 201 | // Remove removes the provided key from the cache. 202 | func (c *LRUCache) Remove(key interface{}) bool { 203 | c.mu.Lock() 204 | defer c.mu.Unlock() 205 | 206 | return c.remove(key) 207 | } 208 | 209 | func (c *LRUCache) remove(key interface{}) bool { 210 | if ent, ok := c.items[key]; ok { 211 | c.removeElement(ent) 212 | return true 213 | } 214 | return false 215 | } 216 | 217 | func (c *LRUCache) removeElement(e *list.Element) { 218 | c.evictList.Remove(e) 219 | entry := e.Value.(*lruItem) 220 | delete(c.items, entry.key) 221 | if c.evictedFunc != nil { 222 | entry := e.Value.(*lruItem) 223 | c.evictedFunc(entry.key, entry.value) 224 | } 225 | } 226 | 227 | func (c *LRUCache) keys() []interface{} { 228 | c.mu.RLock() 229 | defer c.mu.RUnlock() 230 | keys := make([]interface{}, len(c.items)) 231 | var i = 0 232 | for k := range c.items { 233 | keys[i] = k 234 | i++ 235 | } 236 | return keys 237 | } 238 | 239 | // GetALL returns all key-value pairs in the cache. 240 | func (c *LRUCache) GetALL(checkExpired bool) map[interface{}]interface{} { 241 | c.mu.RLock() 242 | defer c.mu.RUnlock() 243 | items := make(map[interface{}]interface{}, len(c.items)) 244 | now := time.Now() 245 | for k, item := range c.items { 246 | if !checkExpired || c.has(k, &now) { 247 | items[k] = item.Value.(*lruItem).value 248 | } 249 | } 250 | return items 251 | } 252 | 253 | // Keys returns a slice of the keys in the cache. 254 | func (c *LRUCache) Keys(checkExpired bool) []interface{} { 255 | c.mu.RLock() 256 | defer c.mu.RUnlock() 257 | keys := make([]interface{}, 0, len(c.items)) 258 | now := time.Now() 259 | for k := range c.items { 260 | if !checkExpired || c.has(k, &now) { 261 | keys = append(keys, k) 262 | } 263 | } 264 | return keys 265 | } 266 | 267 | // Len returns the number of items in the cache. 268 | func (c *LRUCache) Len(checkExpired bool) int { 269 | c.mu.RLock() 270 | defer c.mu.RUnlock() 271 | if !checkExpired { 272 | return len(c.items) 273 | } 274 | var length int 275 | now := time.Now() 276 | for k := range c.items { 277 | if c.has(k, &now) { 278 | length++ 279 | } 280 | } 281 | return length 282 | } 283 | 284 | // Completely clear the cache 285 | func (c *LRUCache) Purge() { 286 | c.mu.Lock() 287 | defer c.mu.Unlock() 288 | 289 | if c.purgeVisitorFunc != nil { 290 | for key, item := range c.items { 291 | it := item.Value.(*lruItem) 292 | v := it.value 293 | c.purgeVisitorFunc(key, v) 294 | } 295 | } 296 | 297 | c.init() 298 | } 299 | 300 | type lruItem struct { 301 | clock Clock 302 | key interface{} 303 | value interface{} 304 | expiration *time.Time 305 | } 306 | 307 | // IsExpired returns boolean value whether this item is expired or not. 308 | func (it *lruItem) IsExpired(now *time.Time) bool { 309 | if it.expiration == nil { 310 | return false 311 | } 312 | if now == nil { 313 | t := it.clock.Now() 314 | now = &t 315 | } 316 | return it.expiration.Before(*now) 317 | } 318 | -------------------------------------------------------------------------------- /lfu.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | // Discards the least frequently used items first. 9 | type LFUCache struct { 10 | baseCache 11 | items map[interface{}]*lfuItem 12 | freqList *list.List // list for freqEntry 13 | } 14 | 15 | var _ Cache = (*LFUCache)(nil) 16 | 17 | type lfuItem struct { 18 | clock Clock 19 | key interface{} 20 | value interface{} 21 | freqElement *list.Element 22 | expiration *time.Time 23 | } 24 | 25 | type freqEntry struct { 26 | freq uint 27 | items map[*lfuItem]struct{} 28 | } 29 | 30 | func newLFUCache(cb *CacheBuilder) *LFUCache { 31 | c := &LFUCache{} 32 | buildCache(&c.baseCache, cb) 33 | 34 | c.init() 35 | c.loadGroup.cache = c 36 | return c 37 | } 38 | 39 | func (c *LFUCache) init() { 40 | c.freqList = list.New() 41 | c.items = make(map[interface{}]*lfuItem, c.size) 42 | c.freqList.PushFront(&freqEntry{ 43 | freq: 0, 44 | items: make(map[*lfuItem]struct{}), 45 | }) 46 | } 47 | 48 | // Set a new key-value pair 49 | func (c *LFUCache) Set(key, value interface{}) error { 50 | c.mu.Lock() 51 | defer c.mu.Unlock() 52 | _, err := c.set(key, value) 53 | return err 54 | } 55 | 56 | // Set a new key-value pair with an expiration time 57 | func (c *LFUCache) SetWithExpire(key, value interface{}, expiration time.Duration) error { 58 | c.mu.Lock() 59 | defer c.mu.Unlock() 60 | item, err := c.set(key, value) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | t := c.clock.Now().Add(expiration) 66 | item.(*lfuItem).expiration = &t 67 | return nil 68 | } 69 | 70 | func (c *LFUCache) set(key, value interface{}) (interface{}, error) { 71 | var err error 72 | if c.serializeFunc != nil { 73 | value, err = c.serializeFunc(key, value) 74 | if err != nil { 75 | return nil, err 76 | } 77 | } 78 | 79 | // Check for existing item 80 | item, ok := c.items[key] 81 | if ok { 82 | item.value = value 83 | } else { 84 | // Verify size not exceeded 85 | if len(c.items) >= c.size { 86 | c.evict(1) 87 | } 88 | item = &lfuItem{ 89 | clock: c.clock, 90 | key: key, 91 | value: value, 92 | freqElement: nil, 93 | } 94 | el := c.freqList.Front() 95 | fe := el.Value.(*freqEntry) 96 | fe.items[item] = struct{}{} 97 | 98 | item.freqElement = el 99 | c.items[key] = item 100 | } 101 | 102 | if c.expiration != nil { 103 | t := c.clock.Now().Add(*c.expiration) 104 | item.expiration = &t 105 | } 106 | 107 | if c.addedFunc != nil { 108 | c.addedFunc(key, value) 109 | } 110 | 111 | return item, nil 112 | } 113 | 114 | // Get a value from cache pool using key if it exists. 115 | // If it does not exists key and has LoaderFunc, 116 | // generate a value using `LoaderFunc` method returns value. 117 | func (c *LFUCache) Get(key interface{}) (interface{}, error) { 118 | v, err := c.get(key, false) 119 | if err == KeyNotFoundError { 120 | return c.getWithLoader(key, true) 121 | } 122 | return v, err 123 | } 124 | 125 | // GetIFPresent gets a value from cache pool using key if it exists. 126 | // If it does not exists key, returns KeyNotFoundError. 127 | // And send a request which refresh value for specified key if cache object has LoaderFunc. 128 | func (c *LFUCache) GetIFPresent(key interface{}) (interface{}, error) { 129 | v, err := c.get(key, false) 130 | if err == KeyNotFoundError { 131 | return c.getWithLoader(key, false) 132 | } 133 | return v, err 134 | } 135 | 136 | func (c *LFUCache) get(key interface{}, onLoad bool) (interface{}, error) { 137 | v, err := c.getValue(key, onLoad) 138 | if err != nil { 139 | return nil, err 140 | } 141 | if c.deserializeFunc != nil { 142 | return c.deserializeFunc(key, v) 143 | } 144 | return v, nil 145 | } 146 | 147 | func (c *LFUCache) getValue(key interface{}, onLoad bool) (interface{}, error) { 148 | c.mu.Lock() 149 | item, ok := c.items[key] 150 | if ok { 151 | if !item.IsExpired(nil) { 152 | c.increment(item) 153 | v := item.value 154 | c.mu.Unlock() 155 | if !onLoad { 156 | c.stats.IncrHitCount() 157 | } 158 | return v, nil 159 | } 160 | c.removeItem(item) 161 | } 162 | c.mu.Unlock() 163 | if !onLoad { 164 | c.stats.IncrMissCount() 165 | } 166 | return nil, KeyNotFoundError 167 | } 168 | 169 | func (c *LFUCache) getWithLoader(key interface{}, isWait bool) (interface{}, error) { 170 | if c.loaderExpireFunc == nil { 171 | return nil, KeyNotFoundError 172 | } 173 | value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { 174 | if e != nil { 175 | return nil, e 176 | } 177 | c.mu.Lock() 178 | defer c.mu.Unlock() 179 | item, err := c.set(key, v) 180 | if err != nil { 181 | return nil, err 182 | } 183 | if expiration != nil { 184 | t := c.clock.Now().Add(*expiration) 185 | item.(*lfuItem).expiration = &t 186 | } 187 | return v, nil 188 | }, isWait) 189 | if err != nil { 190 | return nil, err 191 | } 192 | return value, nil 193 | } 194 | 195 | func (c *LFUCache) increment(item *lfuItem) { 196 | currentFreqElement := item.freqElement 197 | currentFreqEntry := currentFreqElement.Value.(*freqEntry) 198 | nextFreq := currentFreqEntry.freq + 1 199 | delete(currentFreqEntry.items, item) 200 | 201 | // a boolean whether reuse the empty current entry 202 | removable := isRemovableFreqEntry(currentFreqEntry) 203 | 204 | // insert item into a valid entry 205 | nextFreqElement := currentFreqElement.Next() 206 | switch { 207 | case nextFreqElement == nil || nextFreqElement.Value.(*freqEntry).freq > nextFreq: 208 | if removable { 209 | currentFreqEntry.freq = nextFreq 210 | nextFreqElement = currentFreqElement 211 | } else { 212 | nextFreqElement = c.freqList.InsertAfter(&freqEntry{ 213 | freq: nextFreq, 214 | items: make(map[*lfuItem]struct{}), 215 | }, currentFreqElement) 216 | } 217 | case nextFreqElement.Value.(*freqEntry).freq == nextFreq: 218 | if removable { 219 | c.freqList.Remove(currentFreqElement) 220 | } 221 | default: 222 | panic("unreachable") 223 | } 224 | nextFreqElement.Value.(*freqEntry).items[item] = struct{}{} 225 | item.freqElement = nextFreqElement 226 | } 227 | 228 | // evict removes the least frequence item from the cache. 229 | func (c *LFUCache) evict(count int) { 230 | entry := c.freqList.Front() 231 | for i := 0; i < count; { 232 | if entry == nil { 233 | return 234 | } else { 235 | for item := range entry.Value.(*freqEntry).items { 236 | if i >= count { 237 | return 238 | } 239 | c.removeItem(item) 240 | i++ 241 | } 242 | entry = entry.Next() 243 | } 244 | } 245 | } 246 | 247 | // Has checks if key exists in cache 248 | func (c *LFUCache) Has(key interface{}) bool { 249 | c.mu.RLock() 250 | defer c.mu.RUnlock() 251 | now := time.Now() 252 | return c.has(key, &now) 253 | } 254 | 255 | func (c *LFUCache) has(key interface{}, now *time.Time) bool { 256 | item, ok := c.items[key] 257 | if !ok { 258 | return false 259 | } 260 | return !item.IsExpired(now) 261 | } 262 | 263 | // Remove removes the provided key from the cache. 264 | func (c *LFUCache) Remove(key interface{}) bool { 265 | c.mu.Lock() 266 | defer c.mu.Unlock() 267 | 268 | return c.remove(key) 269 | } 270 | 271 | func (c *LFUCache) remove(key interface{}) bool { 272 | if item, ok := c.items[key]; ok { 273 | c.removeItem(item) 274 | return true 275 | } 276 | return false 277 | } 278 | 279 | // removeElement is used to remove a given list element from the cache 280 | func (c *LFUCache) removeItem(item *lfuItem) { 281 | entry := item.freqElement.Value.(*freqEntry) 282 | delete(c.items, item.key) 283 | delete(entry.items, item) 284 | if isRemovableFreqEntry(entry) { 285 | c.freqList.Remove(item.freqElement) 286 | } 287 | if c.evictedFunc != nil { 288 | c.evictedFunc(item.key, item.value) 289 | } 290 | } 291 | 292 | func (c *LFUCache) keys() []interface{} { 293 | c.mu.RLock() 294 | defer c.mu.RUnlock() 295 | keys := make([]interface{}, len(c.items)) 296 | var i = 0 297 | for k := range c.items { 298 | keys[i] = k 299 | i++ 300 | } 301 | return keys 302 | } 303 | 304 | // GetALL returns all key-value pairs in the cache. 305 | func (c *LFUCache) GetALL(checkExpired bool) map[interface{}]interface{} { 306 | c.mu.RLock() 307 | defer c.mu.RUnlock() 308 | items := make(map[interface{}]interface{}, len(c.items)) 309 | now := time.Now() 310 | for k, item := range c.items { 311 | if !checkExpired || c.has(k, &now) { 312 | items[k] = item.value 313 | } 314 | } 315 | return items 316 | } 317 | 318 | // Keys returns a slice of the keys in the cache. 319 | func (c *LFUCache) Keys(checkExpired bool) []interface{} { 320 | c.mu.RLock() 321 | defer c.mu.RUnlock() 322 | keys := make([]interface{}, 0, len(c.items)) 323 | now := time.Now() 324 | for k := range c.items { 325 | if !checkExpired || c.has(k, &now) { 326 | keys = append(keys, k) 327 | } 328 | } 329 | return keys 330 | } 331 | 332 | // Len returns the number of items in the cache. 333 | func (c *LFUCache) Len(checkExpired bool) int { 334 | c.mu.RLock() 335 | defer c.mu.RUnlock() 336 | if !checkExpired { 337 | return len(c.items) 338 | } 339 | var length int 340 | now := time.Now() 341 | for k := range c.items { 342 | if c.has(k, &now) { 343 | length++ 344 | } 345 | } 346 | return length 347 | } 348 | 349 | // Completely clear the cache 350 | func (c *LFUCache) Purge() { 351 | c.mu.Lock() 352 | defer c.mu.Unlock() 353 | 354 | if c.purgeVisitorFunc != nil { 355 | for key, item := range c.items { 356 | c.purgeVisitorFunc(key, item.value) 357 | } 358 | } 359 | 360 | c.init() 361 | } 362 | 363 | // IsExpired returns boolean value whether this item is expired or not. 364 | func (it *lfuItem) IsExpired(now *time.Time) bool { 365 | if it.expiration == nil { 366 | return false 367 | } 368 | if now == nil { 369 | t := it.clock.Now() 370 | now = &t 371 | } 372 | return it.expiration.Before(*now) 373 | } 374 | 375 | func isRemovableFreqEntry(entry *freqEntry) bool { 376 | return entry.freq != 0 && len(entry.items) == 0 377 | } 378 | -------------------------------------------------------------------------------- /arc.go: -------------------------------------------------------------------------------- 1 | package gcache 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | // Constantly balances between LRU and LFU, to improve the combined result. 9 | type ARC struct { 10 | baseCache 11 | items map[interface{}]*arcItem 12 | 13 | part int 14 | t1 *arcList 15 | t2 *arcList 16 | b1 *arcList 17 | b2 *arcList 18 | } 19 | 20 | func newARC(cb *CacheBuilder) *ARC { 21 | c := &ARC{} 22 | buildCache(&c.baseCache, cb) 23 | 24 | c.init() 25 | c.loadGroup.cache = c 26 | return c 27 | } 28 | 29 | func (c *ARC) init() { 30 | c.items = make(map[interface{}]*arcItem) 31 | c.t1 = newARCList() 32 | c.t2 = newARCList() 33 | c.b1 = newARCList() 34 | c.b2 = newARCList() 35 | } 36 | 37 | func (c *ARC) replace(key interface{}) { 38 | if !c.isCacheFull() { 39 | return 40 | } 41 | var old interface{} 42 | if c.t1.Len() > 0 && ((c.b2.Has(key) && c.t1.Len() == c.part) || (c.t1.Len() > c.part)) { 43 | old = c.t1.RemoveTail() 44 | c.b1.PushFront(old) 45 | } else if c.t2.Len() > 0 { 46 | old = c.t2.RemoveTail() 47 | c.b2.PushFront(old) 48 | } else { 49 | old = c.t1.RemoveTail() 50 | c.b1.PushFront(old) 51 | } 52 | item, ok := c.items[old] 53 | if ok { 54 | delete(c.items, old) 55 | if c.evictedFunc != nil { 56 | c.evictedFunc(item.key, item.value) 57 | } 58 | } 59 | } 60 | 61 | func (c *ARC) Set(key, value interface{}) error { 62 | c.mu.Lock() 63 | defer c.mu.Unlock() 64 | _, err := c.set(key, value) 65 | return err 66 | } 67 | 68 | // Set a new key-value pair with an expiration time 69 | func (c *ARC) SetWithExpire(key, value interface{}, expiration time.Duration) error { 70 | c.mu.Lock() 71 | defer c.mu.Unlock() 72 | item, err := c.set(key, value) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | t := c.clock.Now().Add(expiration) 78 | item.(*arcItem).expiration = &t 79 | return nil 80 | } 81 | 82 | func (c *ARC) set(key, value interface{}) (interface{}, error) { 83 | var err error 84 | if c.serializeFunc != nil { 85 | value, err = c.serializeFunc(key, value) 86 | if err != nil { 87 | return nil, err 88 | } 89 | } 90 | 91 | item, ok := c.items[key] 92 | if ok { 93 | item.value = value 94 | } else { 95 | item = &arcItem{ 96 | clock: c.clock, 97 | key: key, 98 | value: value, 99 | } 100 | c.items[key] = item 101 | } 102 | 103 | if c.expiration != nil { 104 | t := c.clock.Now().Add(*c.expiration) 105 | item.expiration = &t 106 | } 107 | 108 | defer func() { 109 | if c.addedFunc != nil { 110 | c.addedFunc(key, value) 111 | } 112 | }() 113 | 114 | if c.t1.Has(key) || c.t2.Has(key) { 115 | return item, nil 116 | } 117 | 118 | if elt := c.b1.Lookup(key); elt != nil { 119 | c.setPart(minInt(c.size, c.part+maxInt(c.b2.Len()/c.b1.Len(), 1))) 120 | c.replace(key) 121 | c.b1.Remove(key, elt) 122 | c.t2.PushFront(key) 123 | return item, nil 124 | } 125 | 126 | if elt := c.b2.Lookup(key); elt != nil { 127 | c.setPart(maxInt(0, c.part-maxInt(c.b1.Len()/c.b2.Len(), 1))) 128 | c.replace(key) 129 | c.b2.Remove(key, elt) 130 | c.t2.PushFront(key) 131 | return item, nil 132 | } 133 | 134 | if c.isCacheFull() && c.t1.Len()+c.b1.Len() == c.size { 135 | if c.t1.Len() < c.size { 136 | c.b1.RemoveTail() 137 | c.replace(key) 138 | } else { 139 | pop := c.t1.RemoveTail() 140 | item, ok := c.items[pop] 141 | if ok { 142 | delete(c.items, pop) 143 | if c.evictedFunc != nil { 144 | c.evictedFunc(item.key, item.value) 145 | } 146 | } 147 | } 148 | } else { 149 | total := c.t1.Len() + c.b1.Len() + c.t2.Len() + c.b2.Len() 150 | if total >= c.size { 151 | if total == (2 * c.size) { 152 | if c.b2.Len() > 0 { 153 | c.b2.RemoveTail() 154 | } else { 155 | c.b1.RemoveTail() 156 | } 157 | } 158 | c.replace(key) 159 | } 160 | } 161 | c.t1.PushFront(key) 162 | return item, nil 163 | } 164 | 165 | // 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. 166 | func (c *ARC) Get(key interface{}) (interface{}, error) { 167 | v, err := c.get(key, false) 168 | if err == KeyNotFoundError { 169 | return c.getWithLoader(key, true) 170 | } 171 | return v, err 172 | } 173 | 174 | // GetIFPresent gets a value from cache pool using key if it exists. 175 | // If it does not exists key, returns KeyNotFoundError. 176 | // And send a request which refresh value for specified key if cache object has LoaderFunc. 177 | func (c *ARC) GetIFPresent(key interface{}) (interface{}, error) { 178 | v, err := c.get(key, false) 179 | if err == KeyNotFoundError { 180 | return c.getWithLoader(key, false) 181 | } 182 | return v, err 183 | } 184 | 185 | func (c *ARC) get(key interface{}, onLoad bool) (interface{}, error) { 186 | v, err := c.getValue(key, onLoad) 187 | if err != nil { 188 | return nil, err 189 | } 190 | if c.deserializeFunc != nil { 191 | return c.deserializeFunc(key, v) 192 | } 193 | return v, nil 194 | } 195 | 196 | func (c *ARC) getValue(key interface{}, onLoad bool) (interface{}, error) { 197 | c.mu.Lock() 198 | defer c.mu.Unlock() 199 | if elt := c.t1.Lookup(key); elt != nil { 200 | c.t1.Remove(key, elt) 201 | item := c.items[key] 202 | if !item.IsExpired(nil) { 203 | c.t2.PushFront(key) 204 | if !onLoad { 205 | c.stats.IncrHitCount() 206 | } 207 | return item.value, nil 208 | } else { 209 | delete(c.items, key) 210 | c.b1.PushFront(key) 211 | if c.evictedFunc != nil { 212 | c.evictedFunc(item.key, item.value) 213 | } 214 | } 215 | } 216 | if elt := c.t2.Lookup(key); elt != nil { 217 | item := c.items[key] 218 | if !item.IsExpired(nil) { 219 | c.t2.MoveToFront(elt) 220 | if !onLoad { 221 | c.stats.IncrHitCount() 222 | } 223 | return item.value, nil 224 | } else { 225 | delete(c.items, key) 226 | c.t2.Remove(key, elt) 227 | c.b2.PushFront(key) 228 | if c.evictedFunc != nil { 229 | c.evictedFunc(item.key, item.value) 230 | } 231 | } 232 | } 233 | 234 | if !onLoad { 235 | c.stats.IncrMissCount() 236 | } 237 | return nil, KeyNotFoundError 238 | } 239 | 240 | func (c *ARC) getWithLoader(key interface{}, isWait bool) (interface{}, error) { 241 | if c.loaderExpireFunc == nil { 242 | return nil, KeyNotFoundError 243 | } 244 | value, _, err := c.load(key, func(v interface{}, expiration *time.Duration, e error) (interface{}, error) { 245 | if e != nil { 246 | return nil, e 247 | } 248 | c.mu.Lock() 249 | defer c.mu.Unlock() 250 | item, err := c.set(key, v) 251 | if err != nil { 252 | return nil, err 253 | } 254 | if expiration != nil { 255 | t := c.clock.Now().Add(*expiration) 256 | item.(*arcItem).expiration = &t 257 | } 258 | return v, nil 259 | }, isWait) 260 | if err != nil { 261 | return nil, err 262 | } 263 | return value, nil 264 | } 265 | 266 | // Has checks if key exists in cache 267 | func (c *ARC) Has(key interface{}) bool { 268 | c.mu.RLock() 269 | defer c.mu.RUnlock() 270 | now := time.Now() 271 | return c.has(key, &now) 272 | } 273 | 274 | func (c *ARC) has(key interface{}, now *time.Time) bool { 275 | item, ok := c.items[key] 276 | if !ok { 277 | return false 278 | } 279 | return !item.IsExpired(now) 280 | } 281 | 282 | // Remove removes the provided key from the cache. 283 | func (c *ARC) Remove(key interface{}) bool { 284 | c.mu.Lock() 285 | defer c.mu.Unlock() 286 | 287 | return c.remove(key) 288 | } 289 | 290 | func (c *ARC) remove(key interface{}) bool { 291 | if elt := c.t1.Lookup(key); elt != nil { 292 | c.t1.Remove(key, elt) 293 | item := c.items[key] 294 | delete(c.items, key) 295 | c.b1.PushFront(key) 296 | if c.evictedFunc != nil { 297 | c.evictedFunc(key, item.value) 298 | } 299 | return true 300 | } 301 | 302 | if elt := c.t2.Lookup(key); elt != nil { 303 | c.t2.Remove(key, elt) 304 | item := c.items[key] 305 | delete(c.items, key) 306 | c.b2.PushFront(key) 307 | if c.evictedFunc != nil { 308 | c.evictedFunc(key, item.value) 309 | } 310 | return true 311 | } 312 | 313 | return false 314 | } 315 | 316 | // GetALL returns all key-value pairs in the cache. 317 | func (c *ARC) GetALL(checkExpired bool) map[interface{}]interface{} { 318 | c.mu.RLock() 319 | defer c.mu.RUnlock() 320 | items := make(map[interface{}]interface{}, len(c.items)) 321 | now := time.Now() 322 | for k, item := range c.items { 323 | if !checkExpired || c.has(k, &now) { 324 | items[k] = item.value 325 | } 326 | } 327 | return items 328 | } 329 | 330 | // Keys returns a slice of the keys in the cache. 331 | func (c *ARC) Keys(checkExpired bool) []interface{} { 332 | c.mu.RLock() 333 | defer c.mu.RUnlock() 334 | keys := make([]interface{}, 0, len(c.items)) 335 | now := time.Now() 336 | for k := range c.items { 337 | if !checkExpired || c.has(k, &now) { 338 | keys = append(keys, k) 339 | } 340 | } 341 | return keys 342 | } 343 | 344 | // Len returns the number of items in the cache. 345 | func (c *ARC) Len(checkExpired bool) int { 346 | c.mu.RLock() 347 | defer c.mu.RUnlock() 348 | if !checkExpired { 349 | return len(c.items) 350 | } 351 | var length int 352 | now := time.Now() 353 | for k := range c.items { 354 | if c.has(k, &now) { 355 | length++ 356 | } 357 | } 358 | return length 359 | } 360 | 361 | // Purge is used to completely clear the cache 362 | func (c *ARC) Purge() { 363 | c.mu.Lock() 364 | defer c.mu.Unlock() 365 | 366 | if c.purgeVisitorFunc != nil { 367 | for _, item := range c.items { 368 | c.purgeVisitorFunc(item.key, item.value) 369 | } 370 | } 371 | 372 | c.init() 373 | } 374 | 375 | func (c *ARC) setPart(p int) { 376 | if c.isCacheFull() { 377 | c.part = p 378 | } 379 | } 380 | 381 | func (c *ARC) isCacheFull() bool { 382 | return (c.t1.Len() + c.t2.Len()) == c.size 383 | } 384 | 385 | // IsExpired returns boolean value whether this item is expired or not. 386 | func (it *arcItem) IsExpired(now *time.Time) bool { 387 | if it.expiration == nil { 388 | return false 389 | } 390 | if now == nil { 391 | t := it.clock.Now() 392 | now = &t 393 | } 394 | return it.expiration.Before(*now) 395 | } 396 | 397 | type arcList struct { 398 | l *list.List 399 | keys map[interface{}]*list.Element 400 | } 401 | 402 | type arcItem struct { 403 | clock Clock 404 | key interface{} 405 | value interface{} 406 | expiration *time.Time 407 | } 408 | 409 | func newARCList() *arcList { 410 | return &arcList{ 411 | l: list.New(), 412 | keys: make(map[interface{}]*list.Element), 413 | } 414 | } 415 | 416 | func (al *arcList) Has(key interface{}) bool { 417 | _, ok := al.keys[key] 418 | return ok 419 | } 420 | 421 | func (al *arcList) Lookup(key interface{}) *list.Element { 422 | elt := al.keys[key] 423 | return elt 424 | } 425 | 426 | func (al *arcList) MoveToFront(elt *list.Element) { 427 | al.l.MoveToFront(elt) 428 | } 429 | 430 | func (al *arcList) PushFront(key interface{}) { 431 | if elt, ok := al.keys[key]; ok { 432 | al.l.MoveToFront(elt) 433 | return 434 | } 435 | elt := al.l.PushFront(key) 436 | al.keys[key] = elt 437 | } 438 | 439 | func (al *arcList) Remove(key interface{}, elt *list.Element) { 440 | delete(al.keys, key) 441 | al.l.Remove(elt) 442 | } 443 | 444 | func (al *arcList) RemoveTail() interface{} { 445 | elt := al.l.Back() 446 | al.l.Remove(elt) 447 | 448 | key := elt.Value 449 | delete(al.keys, key) 450 | 451 | return key 452 | } 453 | 454 | func (al *arcList) Len() int { 455 | return al.l.Len() 456 | } 457 | --------------------------------------------------------------------------------