-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcache.go
121 lines (105 loc) · 2.33 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package mbbolt
import (
"log"
"sync"
"sync/atomic"
"go.oneofone.dev/genh"
"go.oneofone.dev/oerrs"
)
const ErrDeleteKey = oerrs.String("delete")
func CacheOf[T any](db *DB, bucket string, loadAll bool) *Cache[T] {
if err := db.Update(func(tx *Tx) error {
_, err := tx.CreateBucketIfNotExists(bucket)
return err
}); err != nil { // this should never ever ever happen
log.Panicf("%s (%s): %v", db.Path(), bucket, err)
}
c := &Cache[T]{
db: TypedDB[T]{db},
bucket: bucket,
}
if loadAll {
c.Sync()
}
return c
}
type Cache[T any] struct {
hits atomic.Int64
misses atomic.Int64
m genh.LMap[string, T]
db TypedDB[T]
bucket string
NoBatch bool
loadOnce sync.Once
}
func (c *Cache[T]) Sync() {
if err := c.db.ForEach(c.bucket, func(key string, v T) error {
c.m.Set(key, v)
return nil
}); err != nil {
log.Printf("mbbolt: %s (%s): %v", c.db.Path(), c.bucket, err)
}
}
// Use clone if T is a pointer or contains slices/maps/pointers that will be modified.
func (c *Cache[T]) Get(key string) (v T, err error) {
found := true
v = c.m.MustGet(key, func() T {
found = false
if v, err = c.db.Get(c.bucket, key); err == nil {
c.m.Set(key, v)
}
return v
})
if !found {
c.misses.Add(1)
} else {
c.hits.Add(1)
}
v = genh.Clone(v, false)
return
}
func (c *Cache[T]) Put(key string, v T) (err error) {
return c.Update(func(tx *Tx) (_ string, _ T, err error) {
err = tx.PutValue(c.bucket, key, v)
return key, v, err
})
}
func (c *Cache[T]) Delete(key string) (err error) {
return c.Update(func(tx *Tx) (_ string, v T, err error) {
tx.Delete(c.bucket, key)
return key, v, ErrDeleteKey
})
}
func (c *Cache[T]) ForEach(fn func(k string, v T) error) (err error) {
c.loadOnce.Do(c.Sync)
c.m.ForEach(func(k string, v T) bool {
err = fn(k, v)
return err == nil
})
return
}
func (c *Cache[T]) Update(fn func(tx *Tx) (key string, v T, err error)) (err error) {
var (
key string
v T
)
ufn := func(tx *Tx) error {
if key, v, err = fn(tx); err == nil {
if err = tx.PutValue(c.bucket, key, v); err == nil {
c.m.Set(key, genh.Clone(v, false))
}
}
if err == ErrDeleteKey {
c.m.Delete(key)
err = nil
}
return err
}
if c.NoBatch {
return c.db.Update(ufn)
}
return c.db.Batch(ufn)
}
func (c *Cache[T]) Stats() (hits, misses int64) {
return c.hits.Load(), c.misses.Load()
}