store.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. * SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package ristretto
  6. import (
  7. "sync"
  8. "time"
  9. )
  10. type updateFn[V any] func(cur, prev V) bool
  11. // TODO: Do we need this to be a separate struct from Item?
  12. type storeItem[V any] struct {
  13. key uint64
  14. conflict uint64
  15. value V
  16. expiration time.Time
  17. }
  18. // store is the interface fulfilled by all hash map implementations in this
  19. // file. Some hash map implementations are better suited for certain data
  20. // distributions than others, so this allows us to abstract that out for use
  21. // in Ristretto.
  22. //
  23. // Every store is safe for concurrent usage.
  24. type store[V any] interface {
  25. // Get returns the value associated with the key parameter.
  26. Get(uint64, uint64) (V, bool)
  27. // Expiration returns the expiration time for this key.
  28. Expiration(uint64) time.Time
  29. // Set adds the key-value pair to the Map or updates the value if it's
  30. // already present. The key-value pair is passed as a pointer to an
  31. // item object.
  32. Set(*Item[V])
  33. // Del deletes the key-value pair from the Map.
  34. Del(uint64, uint64) (uint64, V)
  35. // Update attempts to update the key with a new value and returns true if
  36. // successful.
  37. Update(*Item[V]) (V, bool)
  38. // Cleanup removes items that have an expired TTL.
  39. Cleanup(policy *defaultPolicy[V], onEvict func(item *Item[V]))
  40. // Clear clears all contents of the store.
  41. Clear(onEvict func(item *Item[V]))
  42. SetShouldUpdateFn(f updateFn[V])
  43. // IterValues iterates the values of the Map, passing them to the callback.
  44. // It guarantees that any value in the Map will be visited only once.
  45. // The set of values visited by IterValues is non-deterministic.
  46. IterValues(cb func(v V) (stop bool))
  47. }
  48. // newStore returns the default store implementation.
  49. func newStore[V any]() store[V] {
  50. return newShardedMap[V]()
  51. }
  52. const numShards uint64 = 256
  53. type shardedMap[V any] struct {
  54. shards []*lockedMap[V]
  55. expiryMap *expirationMap[V]
  56. }
  57. func newShardedMap[V any]() *shardedMap[V] {
  58. sm := &shardedMap[V]{
  59. shards: make([]*lockedMap[V], int(numShards)),
  60. expiryMap: newExpirationMap[V](),
  61. }
  62. for i := range sm.shards {
  63. sm.shards[i] = newLockedMap[V](sm.expiryMap)
  64. }
  65. return sm
  66. }
  67. func (m *shardedMap[V]) SetShouldUpdateFn(f updateFn[V]) {
  68. for i := range m.shards {
  69. m.shards[i].setShouldUpdateFn(f)
  70. }
  71. }
  72. // IterValues iterates the values of the Map, passing them to the callback.
  73. // It guarantees that any value in the Map will be visited only once.
  74. // The set of values visited by IterValues is non-deterministic.
  75. func (sm *shardedMap[V]) IterValues(cb func(v V) (stop bool)) {
  76. for _, shard := range sm.shards {
  77. stopped := func() bool {
  78. shard.RLock()
  79. defer shard.RUnlock()
  80. for _, item := range shard.data {
  81. if !item.expiration.IsZero() && time.Now().After(item.expiration) {
  82. continue
  83. }
  84. if stop := cb(item.value); stop {
  85. return true
  86. }
  87. }
  88. return false
  89. }()
  90. if stopped {
  91. break
  92. }
  93. }
  94. }
  95. func (sm *shardedMap[V]) Get(key, conflict uint64) (V, bool) {
  96. return sm.shards[key%numShards].get(key, conflict)
  97. }
  98. func (sm *shardedMap[V]) Expiration(key uint64) time.Time {
  99. return sm.shards[key%numShards].Expiration(key)
  100. }
  101. func (sm *shardedMap[V]) Set(i *Item[V]) {
  102. if i == nil {
  103. // If item is nil make this Set a no-op.
  104. return
  105. }
  106. sm.shards[i.Key%numShards].Set(i)
  107. }
  108. func (sm *shardedMap[V]) Del(key, conflict uint64) (uint64, V) {
  109. return sm.shards[key%numShards].Del(key, conflict)
  110. }
  111. func (sm *shardedMap[V]) Update(newItem *Item[V]) (V, bool) {
  112. return sm.shards[newItem.Key%numShards].Update(newItem)
  113. }
  114. func (sm *shardedMap[V]) Cleanup(policy *defaultPolicy[V], onEvict func(item *Item[V])) {
  115. sm.expiryMap.cleanup(sm, policy, onEvict)
  116. }
  117. func (sm *shardedMap[V]) Clear(onEvict func(item *Item[V])) {
  118. for i := uint64(0); i < numShards; i++ {
  119. sm.shards[i].Clear(onEvict)
  120. }
  121. sm.expiryMap.clear()
  122. }
  123. type lockedMap[V any] struct {
  124. sync.RWMutex
  125. data map[uint64]storeItem[V]
  126. em *expirationMap[V]
  127. shouldUpdate updateFn[V]
  128. }
  129. func newLockedMap[V any](em *expirationMap[V]) *lockedMap[V] {
  130. return &lockedMap[V]{
  131. data: make(map[uint64]storeItem[V]),
  132. em: em,
  133. shouldUpdate: func(cur, prev V) bool {
  134. return true
  135. },
  136. }
  137. }
  138. func (m *lockedMap[V]) setShouldUpdateFn(f updateFn[V]) {
  139. m.shouldUpdate = f
  140. }
  141. func (m *lockedMap[V]) get(key, conflict uint64) (V, bool) {
  142. m.RLock()
  143. item, ok := m.data[key]
  144. m.RUnlock()
  145. if !ok {
  146. return zeroValue[V](), false
  147. }
  148. if conflict != 0 && (conflict != item.conflict) {
  149. return zeroValue[V](), false
  150. }
  151. // Handle expired items.
  152. if !item.expiration.IsZero() && time.Now().After(item.expiration) {
  153. return zeroValue[V](), false
  154. }
  155. return item.value, true
  156. }
  157. func (m *lockedMap[V]) Expiration(key uint64) time.Time {
  158. m.RLock()
  159. defer m.RUnlock()
  160. return m.data[key].expiration
  161. }
  162. func (m *lockedMap[V]) Set(i *Item[V]) {
  163. if i == nil {
  164. // If the item is nil make this Set a no-op.
  165. return
  166. }
  167. m.Lock()
  168. defer m.Unlock()
  169. item, ok := m.data[i.Key]
  170. if ok {
  171. // The item existed already. We need to check the conflict key and reject the
  172. // update if they do not match. Only after that the expiration map is updated.
  173. if i.Conflict != 0 && (i.Conflict != item.conflict) {
  174. return
  175. }
  176. if m.shouldUpdate != nil && !m.shouldUpdate(i.Value, item.value) {
  177. return
  178. }
  179. m.em.update(i.Key, i.Conflict, item.expiration, i.Expiration)
  180. } else {
  181. // The value is not in the map already. There's no need to return anything.
  182. // Simply add the expiration map.
  183. m.em.add(i.Key, i.Conflict, i.Expiration)
  184. }
  185. m.data[i.Key] = storeItem[V]{
  186. key: i.Key,
  187. conflict: i.Conflict,
  188. value: i.Value,
  189. expiration: i.Expiration,
  190. }
  191. }
  192. func (m *lockedMap[V]) Del(key, conflict uint64) (uint64, V) {
  193. m.Lock()
  194. defer m.Unlock()
  195. item, ok := m.data[key]
  196. if !ok {
  197. return 0, zeroValue[V]()
  198. }
  199. if conflict != 0 && (conflict != item.conflict) {
  200. return 0, zeroValue[V]()
  201. }
  202. if !item.expiration.IsZero() {
  203. m.em.del(key, item.expiration)
  204. }
  205. delete(m.data, key)
  206. return item.conflict, item.value
  207. }
  208. func (m *lockedMap[V]) Update(newItem *Item[V]) (V, bool) {
  209. m.Lock()
  210. defer m.Unlock()
  211. item, ok := m.data[newItem.Key]
  212. if !ok {
  213. return zeroValue[V](), false
  214. }
  215. if newItem.Conflict != 0 && (newItem.Conflict != item.conflict) {
  216. return zeroValue[V](), false
  217. }
  218. if m.shouldUpdate != nil && !m.shouldUpdate(newItem.Value, item.value) {
  219. return item.value, false
  220. }
  221. m.em.update(newItem.Key, newItem.Conflict, item.expiration, newItem.Expiration)
  222. m.data[newItem.Key] = storeItem[V]{
  223. key: newItem.Key,
  224. conflict: newItem.Conflict,
  225. value: newItem.Value,
  226. expiration: newItem.Expiration,
  227. }
  228. return item.value, true
  229. }
  230. func (m *lockedMap[V]) Clear(onEvict func(item *Item[V])) {
  231. m.Lock()
  232. defer m.Unlock()
  233. i := &Item[V]{}
  234. if onEvict != nil {
  235. for _, si := range m.data {
  236. i.Key = si.key
  237. i.Conflict = si.conflict
  238. i.Value = si.value
  239. onEvict(i)
  240. }
  241. }
  242. m.data = make(map[uint64]storeItem[V])
  243. }