github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/store/cache/store.go (about) 1 package cache 2 3 import ( 4 "bytes" 5 "container/list" 6 "sort" 7 "sync" 8 9 dbm "github.com/gnolang/gno/tm2/pkg/db" 10 "github.com/gnolang/gno/tm2/pkg/std" 11 12 "github.com/gnolang/gno/tm2/pkg/store/types" 13 ) 14 15 // If value is nil but deleted is false, it means the parent doesn't have the 16 // key. (No need to delete upon Write()) 17 type cValue struct { 18 value []byte 19 deleted bool 20 dirty bool 21 } 22 23 // cacheStore wraps an in-memory cache around an underlying types.Store. 24 type cacheStore struct { 25 mtx sync.Mutex 26 cache map[string]*cValue 27 unsortedCache map[string]struct{} 28 sortedCache *list.List // always ascending sorted 29 parent types.Store 30 } 31 32 var _ types.Store = (*cacheStore)(nil) 33 34 func New(parent types.Store) *cacheStore { 35 return &cacheStore{ 36 cache: make(map[string]*cValue), 37 unsortedCache: make(map[string]struct{}), 38 sortedCache: list.New(), 39 parent: parent, 40 } 41 } 42 43 // Implements types.Store. 44 func (store *cacheStore) Get(key []byte) (value []byte) { 45 store.mtx.Lock() 46 defer store.mtx.Unlock() 47 types.AssertValidKey(key) 48 49 cacheValue, ok := store.cache[string(key)] 50 if !ok { 51 value = store.parent.Get(key) 52 store.setCacheValue(key, value, false, false) 53 } else { 54 value = cacheValue.value 55 } 56 57 return value 58 } 59 60 // Implements types.Store. 61 func (store *cacheStore) Set(key []byte, value []byte) { 62 store.mtx.Lock() 63 defer store.mtx.Unlock() 64 types.AssertValidKey(key) 65 types.AssertValidValue(value) 66 67 store.setCacheValue(key, value, false, true) 68 } 69 70 // Implements types.Store. 71 func (store *cacheStore) Has(key []byte) bool { 72 value := store.Get(key) 73 return value != nil 74 } 75 76 // Implements types.Store. 77 func (store *cacheStore) Delete(key []byte) { 78 store.mtx.Lock() 79 defer store.mtx.Unlock() 80 types.AssertValidKey(key) 81 82 store.setCacheValue(key, nil, true, true) 83 } 84 85 // Implements types.Store. 86 func (store *cacheStore) Write() { 87 store.mtx.Lock() 88 defer store.mtx.Unlock() 89 90 // We need a copy of all of the keys. 91 // Not the best, but probably not a bottleneck depending. 92 keys := make([]string, 0, len(store.cache)) 93 for key, dbValue := range store.cache { 94 if dbValue.dirty { 95 keys = append(keys, key) 96 } 97 } 98 99 sort.Strings(keys) 100 101 // TODO: Consider allowing usage of Batch, which would allow the write to 102 // at least happen atomically. 103 for _, key := range keys { 104 cacheValue := store.cache[key] 105 if cacheValue.deleted { 106 store.parent.Delete([]byte(key)) 107 } else if cacheValue.value == nil { 108 // Skip, it already doesn't exist in parent. 109 } else { 110 store.parent.Set([]byte(key), cacheValue.value) 111 } 112 } 113 114 // Clear the cache 115 store.cache = make(map[string]*cValue) 116 store.unsortedCache = make(map[string]struct{}) 117 store.sortedCache = list.New() 118 } 119 120 // ---------------------------------------- 121 // To cache-wrap this Store further. 122 123 // Implements Store. 124 func (store *cacheStore) CacheWrap() types.Store { 125 return New(store) 126 } 127 128 // ---------------------------------------- 129 // Iteration 130 131 // Implements types.Store. 132 func (store *cacheStore) Iterator(start, end []byte) types.Iterator { 133 return store.iterator(start, end, true) 134 } 135 136 // Implements types.Store. 137 func (store *cacheStore) ReverseIterator(start, end []byte) types.Iterator { 138 return store.iterator(start, end, false) 139 } 140 141 func (store *cacheStore) iterator(start, end []byte, ascending bool) types.Iterator { 142 store.mtx.Lock() 143 defer store.mtx.Unlock() 144 145 var parent, cache types.Iterator 146 147 if ascending { 148 parent = store.parent.Iterator(start, end) 149 } else { 150 parent = store.parent.ReverseIterator(start, end) 151 } 152 153 store.dirtyItems(start, end) 154 cache = newMemIterator(start, end, store.sortedCache, ascending) 155 156 return newCacheMergeIterator(parent, cache, ascending) 157 } 158 159 // Constructs a slice of dirty items, to use w/ memIterator. 160 func (store *cacheStore) dirtyItems(start, end []byte) { 161 unsorted := make([]*std.KVPair, 0) 162 163 for key := range store.unsortedCache { 164 cacheValue := store.cache[key] 165 if dbm.IsKeyInDomain([]byte(key), start, end) { 166 unsorted = append(unsorted, &std.KVPair{Key: []byte(key), Value: cacheValue.value}) 167 delete(store.unsortedCache, key) 168 } 169 } 170 171 sort.Slice(unsorted, func(i, j int) bool { 172 return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0 173 }) 174 175 // #nosec G602 176 for e := store.sortedCache.Front(); e != nil && len(unsorted) != 0; { 177 uitem := unsorted[0] 178 sitem := e.Value.(*std.KVPair) 179 comp := bytes.Compare(uitem.Key, sitem.Key) 180 switch comp { 181 case -1: 182 unsorted = unsorted[1:] 183 store.sortedCache.InsertBefore(uitem, e) 184 case 1: 185 e = e.Next() 186 case 0: 187 unsorted = unsorted[1:] 188 e.Value = uitem 189 e = e.Next() 190 } 191 } 192 193 for _, kvp := range unsorted { 194 store.sortedCache.PushBack(kvp) 195 } 196 } 197 198 // ---------------------------------------- 199 // etc 200 201 // Only entrypoint to mutate store.cache. 202 func (store *cacheStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { 203 store.cache[string(key)] = &cValue{ 204 value: value, 205 deleted: deleted, 206 dirty: dirty, 207 } 208 if dirty { 209 store.unsortedCache[string(key)] = struct{}{} 210 } 211 }