github.com/outcaste-io/ristretto@v0.2.3/store.go (about) 1 /* 2 * Copyright 2019 Dgraph Labs, Inc. and Contributors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package ristretto 18 19 import ( 20 "sync" 21 "time" 22 ) 23 24 // TODO: Do we need this to be a separate struct from Item? 25 type storeItem struct { 26 key uint64 27 conflict uint64 28 value interface{} 29 expiration time.Time 30 } 31 32 const numShards uint64 = 256 33 34 type updateFn func(prev, cur interface{}) bool 35 type shardedMap struct { 36 shards []*lockedMap 37 expiryMap *expirationMap 38 shouldUpdate func(prev, cur interface{}) bool 39 } 40 41 // newShardedMap is safe for concurrent usage. 42 func newShardedMap(fn updateFn) *shardedMap { 43 sm := &shardedMap{ 44 shards: make([]*lockedMap, int(numShards)), 45 expiryMap: newExpirationMap(), 46 } 47 if fn == nil { 48 fn = func(prev, cur interface{}) bool { 49 return true 50 } 51 } 52 for i := range sm.shards { 53 sm.shards[i] = newLockedMap(fn, sm.expiryMap) 54 } 55 return sm 56 } 57 58 func (sm *shardedMap) Get(key, conflict uint64) (interface{}, bool) { 59 return sm.shards[key%numShards].get(key, conflict) 60 } 61 62 func (sm *shardedMap) Expiration(key uint64) time.Time { 63 return sm.shards[key%numShards].Expiration(key) 64 } 65 66 func (sm *shardedMap) Set(i *Item) { 67 if i == nil { 68 // If item is nil make this Set a no-op. 69 return 70 } 71 72 sm.shards[i.Key%numShards].Set(i) 73 } 74 75 func (sm *shardedMap) Del(key, conflict uint64) (uint64, interface{}) { 76 return sm.shards[key%numShards].Del(key, conflict) 77 } 78 79 func (sm *shardedMap) Update(newItem *Item) (interface{}, bool) { 80 return sm.shards[newItem.Key%numShards].Update(newItem) 81 } 82 83 func (sm *shardedMap) Cleanup(policy *lfuPolicy, onEvict itemCallback) { 84 sm.expiryMap.cleanup(sm, policy, onEvict) 85 } 86 87 func (sm *shardedMap) Clear(onEvict itemCallback) { 88 for i := uint64(0); i < numShards; i++ { 89 sm.shards[i].Clear(onEvict) 90 } 91 } 92 93 type lockedMap struct { 94 sync.RWMutex 95 data map[uint64]storeItem 96 em *expirationMap 97 shouldUpdate updateFn 98 } 99 100 func newLockedMap(fn updateFn, em *expirationMap) *lockedMap { 101 return &lockedMap{ 102 data: make(map[uint64]storeItem), 103 em: em, 104 shouldUpdate: fn, 105 } 106 } 107 108 func (m *lockedMap) get(key, conflict uint64) (interface{}, bool) { 109 m.RLock() 110 item, ok := m.data[key] 111 m.RUnlock() 112 if !ok { 113 return nil, false 114 } 115 if conflict != 0 && (conflict != item.conflict) { 116 return nil, false 117 } 118 119 // Handle expired items. 120 if !item.expiration.IsZero() && time.Now().After(item.expiration) { 121 return nil, false 122 } 123 return item.value, true 124 } 125 126 func (m *lockedMap) Expiration(key uint64) time.Time { 127 m.RLock() 128 defer m.RUnlock() 129 return m.data[key].expiration 130 } 131 132 func (m *lockedMap) Set(i *Item) { 133 if i == nil { 134 // If the item is nil make this Set a no-op. 135 return 136 } 137 138 m.Lock() 139 defer m.Unlock() 140 item, ok := m.data[i.Key] 141 142 if ok { 143 // The item existed already. We need to check the conflict key and reject the 144 // update if they do not match. Only after that the expiration map is updated. 145 if i.Conflict != 0 && (i.Conflict != item.conflict) { 146 return 147 } 148 if !m.shouldUpdate(item.value, i.Value) { 149 return 150 } 151 m.em.update(i.Key, i.Conflict, item.expiration, i.Expiration) 152 } else { 153 // The value is not in the map already. There's no need to return anything. 154 // Simply add the expiration map. 155 m.em.add(i.Key, i.Conflict, i.Expiration) 156 } 157 158 m.data[i.Key] = storeItem{ 159 key: i.Key, 160 conflict: i.Conflict, 161 value: i.Value, 162 expiration: i.Expiration, 163 } 164 } 165 166 func (m *lockedMap) Del(key, conflict uint64) (uint64, interface{}) { 167 m.Lock() 168 item, ok := m.data[key] 169 if !ok { 170 m.Unlock() 171 return 0, nil 172 } 173 if conflict != 0 && (conflict != item.conflict) { 174 m.Unlock() 175 return 0, nil 176 } 177 178 if !item.expiration.IsZero() { 179 m.em.del(key, item.expiration) 180 } 181 182 delete(m.data, key) 183 m.Unlock() 184 return item.conflict, item.value 185 } 186 187 func (m *lockedMap) Update(newItem *Item) (interface{}, bool) { 188 m.Lock() 189 defer m.Unlock() 190 191 item, ok := m.data[newItem.Key] 192 if !ok { 193 return nil, false 194 } 195 if newItem.Conflict != 0 && (newItem.Conflict != item.conflict) { 196 return nil, false 197 } 198 if !m.shouldUpdate(item.value, newItem.Value) { 199 return item.value, false 200 } 201 202 m.em.update(newItem.Key, newItem.Conflict, item.expiration, newItem.Expiration) 203 m.data[newItem.Key] = storeItem{ 204 key: newItem.Key, 205 conflict: newItem.Conflict, 206 value: newItem.Value, 207 expiration: newItem.Expiration, 208 } 209 return item.value, true 210 } 211 212 func (m *lockedMap) Clear(onEvict itemCallback) { 213 m.Lock() 214 i := &Item{} 215 if onEvict != nil { 216 for _, si := range m.data { 217 i.Key = si.key 218 i.Conflict = si.conflict 219 i.Value = si.value 220 onEvict(i) 221 } 222 } 223 m.data = make(map[uint64]storeItem) 224 m.Unlock() 225 }