store.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. package ristretto
  17. import (
  18. "sync"
  19. "time"
  20. )
  21. // TODO: Do we need this to be a separate struct from Item?
  22. type storeItem struct {
  23. key uint64
  24. conflict uint64
  25. value interface{}
  26. expiration time.Time
  27. }
  28. // store is the interface fulfilled by all hash map implementations in this
  29. // file. Some hash map implementations are better suited for certain data
  30. // distributions than others, so this allows us to abstract that out for use
  31. // in Ristretto.
  32. //
  33. // Every store is safe for concurrent usage.
  34. type store interface {
  35. // Get returns the value associated with the key parameter.
  36. Get(uint64, uint64) (interface{}, bool)
  37. // Expiration returns the expiration time for this key.
  38. Expiration(uint64) time.Time
  39. // Set adds the key-value pair to the Map or updates the value if it's
  40. // already present. The key-value pair is passed as a pointer to an
  41. // item object.
  42. Set(*Item)
  43. // Del deletes the key-value pair from the Map.
  44. Del(uint64, uint64) (uint64, interface{})
  45. // Update attempts to update the key with a new value and returns true if
  46. // successful.
  47. Update(*Item) (interface{}, bool)
  48. // Cleanup removes items that have an expired TTL.
  49. Cleanup(policy policy, onEvict itemCallback)
  50. // Clear clears all contents of the store.
  51. Clear(onEvict itemCallback)
  52. }
  53. // newStore returns the default store implementation.
  54. func newStore() store {
  55. return newShardedMap()
  56. }
  57. const numShards uint64 = 256
  58. type shardedMap struct {
  59. shards []*lockedMap
  60. expiryMap *expirationMap
  61. }
  62. func newShardedMap() *shardedMap {
  63. sm := &shardedMap{
  64. shards: make([]*lockedMap, int(numShards)),
  65. expiryMap: newExpirationMap(),
  66. }
  67. for i := range sm.shards {
  68. sm.shards[i] = newLockedMap(sm.expiryMap)
  69. }
  70. return sm
  71. }
  72. func (sm *shardedMap) Get(key, conflict uint64) (interface{}, bool) {
  73. return sm.shards[key%numShards].get(key, conflict)
  74. }
  75. func (sm *shardedMap) Expiration(key uint64) time.Time {
  76. return sm.shards[key%numShards].Expiration(key)
  77. }
  78. func (sm *shardedMap) Set(i *Item) {
  79. if i == nil {
  80. // If item is nil make this Set a no-op.
  81. return
  82. }
  83. sm.shards[i.Key%numShards].Set(i)
  84. }
  85. func (sm *shardedMap) Del(key, conflict uint64) (uint64, interface{}) {
  86. return sm.shards[key%numShards].Del(key, conflict)
  87. }
  88. func (sm *shardedMap) Update(newItem *Item) (interface{}, bool) {
  89. return sm.shards[newItem.Key%numShards].Update(newItem)
  90. }
  91. func (sm *shardedMap) Cleanup(policy policy, onEvict itemCallback) {
  92. sm.expiryMap.cleanup(sm, policy, onEvict)
  93. }
  94. func (sm *shardedMap) Clear(onEvict itemCallback) {
  95. for i := uint64(0); i < numShards; i++ {
  96. sm.shards[i].Clear(onEvict)
  97. }
  98. }
  99. type lockedMap struct {
  100. sync.RWMutex
  101. data map[uint64]storeItem
  102. em *expirationMap
  103. }
  104. func newLockedMap(em *expirationMap) *lockedMap {
  105. return &lockedMap{
  106. data: make(map[uint64]storeItem),
  107. em: em,
  108. }
  109. }
  110. func (m *lockedMap) get(key, conflict uint64) (interface{}, bool) {
  111. m.RLock()
  112. item, ok := m.data[key]
  113. m.RUnlock()
  114. if !ok {
  115. return nil, false
  116. }
  117. if conflict != 0 && (conflict != item.conflict) {
  118. return nil, false
  119. }
  120. // Handle expired items.
  121. if !item.expiration.IsZero() && time.Now().After(item.expiration) {
  122. return nil, false
  123. }
  124. return item.value, true
  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. func (m *lockedMap) Set(i *Item) {
  132. if i == nil {
  133. // If the item is nil make this Set a no-op.
  134. return
  135. }
  136. m.Lock()
  137. defer m.Unlock()
  138. item, ok := m.data[i.Key]
  139. if ok {
  140. // The item existed already. We need to check the conflict key and reject the
  141. // update if they do not match. Only after that the expiration map is updated.
  142. if i.Conflict != 0 && (i.Conflict != item.conflict) {
  143. return
  144. }
  145. m.em.update(i.Key, i.Conflict, item.expiration, i.Expiration)
  146. } else {
  147. // The value is not in the map already. There's no need to return anything.
  148. // Simply add the expiration map.
  149. m.em.add(i.Key, i.Conflict, i.Expiration)
  150. }
  151. m.data[i.Key] = storeItem{
  152. key: i.Key,
  153. conflict: i.Conflict,
  154. value: i.Value,
  155. expiration: i.Expiration,
  156. }
  157. }
  158. func (m *lockedMap) Del(key, conflict uint64) (uint64, interface{}) {
  159. m.Lock()
  160. item, ok := m.data[key]
  161. if !ok {
  162. m.Unlock()
  163. return 0, nil
  164. }
  165. if conflict != 0 && (conflict != item.conflict) {
  166. m.Unlock()
  167. return 0, nil
  168. }
  169. if !item.expiration.IsZero() {
  170. m.em.del(key, item.expiration)
  171. }
  172. delete(m.data, key)
  173. m.Unlock()
  174. return item.conflict, item.value
  175. }
  176. func (m *lockedMap) Update(newItem *Item) (interface{}, bool) {
  177. m.Lock()
  178. item, ok := m.data[newItem.Key]
  179. if !ok {
  180. m.Unlock()
  181. return nil, false
  182. }
  183. if newItem.Conflict != 0 && (newItem.Conflict != item.conflict) {
  184. m.Unlock()
  185. return nil, false
  186. }
  187. m.em.update(newItem.Key, newItem.Conflict, item.expiration, newItem.Expiration)
  188. m.data[newItem.Key] = storeItem{
  189. key: newItem.Key,
  190. conflict: newItem.Conflict,
  191. value: newItem.Value,
  192. expiration: newItem.Expiration,
  193. }
  194. m.Unlock()
  195. return item.value, true
  196. }
  197. func (m *lockedMap) Clear(onEvict itemCallback) {
  198. m.Lock()
  199. i := &Item{}
  200. if onEvict != nil {
  201. for _, si := range m.data {
  202. i.Key = si.key
  203. i.Conflict = si.conflict
  204. i.Value = si.value
  205. onEvict(i)
  206. }
  207. }
  208. m.data = make(map[uint64]storeItem)
  209. m.Unlock()
  210. }