go.temporal.io/server@v1.23.0/common/collection/concurrent_tx_map.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package collection 26 27 import ( 28 "sync" 29 "sync/atomic" 30 ) 31 32 const ( 33 // nShards represents the number of shards 34 // At any given point of time, there can only 35 // be nShards number of concurrent writers to 36 // the map at max 37 nShards = 32 38 ) 39 40 type ( 41 42 // ShardedConcurrentTxMap is an implementation of 43 // ConcurrentMap that internally uses multiple 44 // sharded maps to increase parallelism 45 ShardedConcurrentTxMap struct { 46 shards [nShards]mapShard 47 hashfn HashFunc 48 size int32 49 initialCap int 50 } 51 52 // mapIteratorImpl represents an iterator type 53 // for the concurrent map. 54 mapIteratorImpl struct { 55 stopCh chan struct{} 56 dataCh chan *MapEntry 57 } 58 59 // mapShard represents a single instance 60 // of thread safe map 61 mapShard struct { 62 sync.RWMutex 63 items map[interface{}]interface{} 64 } 65 ) 66 67 // NewShardedConcurrentTxMap returns an instance of ShardedConcurrentMap 68 // 69 // ShardedConcurrentMap is a thread safe map that maintains upto nShards 70 // number of maps internally to allow nShards writers to be acive at the 71 // same time. This map *does not* use re-entrant locks, so access to the 72 // map during iterator can cause a dead lock. 73 // 74 // @param initialSz 75 // 76 // The initial size for the map 77 // 78 // @param hashfn 79 // 80 // The hash function to use for sharding 81 func NewShardedConcurrentTxMap(initialCap int, hashfn HashFunc) ConcurrentTxMap { 82 cmap := new(ShardedConcurrentTxMap) 83 cmap.hashfn = hashfn 84 cmap.initialCap = max(nShards, initialCap/nShards) 85 return cmap 86 } 87 88 // Get returns the value corresponding to the key, if it exist 89 func (cmap *ShardedConcurrentTxMap) Get(key interface{}) (interface{}, bool) { 90 shard := cmap.getShard(key) 91 var ok bool 92 var value interface{} 93 shard.RLock() 94 if shard.items != nil { 95 value, ok = shard.items[key] 96 } 97 shard.RUnlock() 98 return value, ok 99 } 100 101 // Contains returns true if the key exist and false otherwise 102 func (cmap *ShardedConcurrentTxMap) Contains(key interface{}) bool { 103 _, ok := cmap.Get(key) 104 return ok 105 } 106 107 // Put records the given key value mapping. Overwrites previous values 108 func (cmap *ShardedConcurrentTxMap) Put(key interface{}, value interface{}) { 109 shard := cmap.getShard(key) 110 shard.Lock() 111 cmap.lazyInitShard(shard) 112 _, ok := shard.items[key] 113 if !ok { 114 atomic.AddInt32(&cmap.size, 1) 115 } 116 shard.items[key] = value 117 shard.Unlock() 118 } 119 120 // PutIfNotExist records the mapping, if there is no mapping for this key already 121 // Returns true if the mapping was recorded, false otherwise 122 func (cmap *ShardedConcurrentTxMap) PutIfNotExist(key interface{}, value interface{}) bool { 123 shard := cmap.getShard(key) 124 var ok bool 125 shard.Lock() 126 cmap.lazyInitShard(shard) 127 _, ok = shard.items[key] 128 if !ok { 129 shard.items[key] = value 130 atomic.AddInt32(&cmap.size, 1) 131 } 132 shard.Unlock() 133 return !ok 134 } 135 136 // Remove deletes the given key from the map 137 func (cmap *ShardedConcurrentTxMap) Remove(key interface{}) { 138 shard := cmap.getShard(key) 139 shard.Lock() 140 cmap.lazyInitShard(shard) 141 _, ok := shard.items[key] 142 if ok { 143 delete(shard.items, key) 144 atomic.AddInt32(&cmap.size, -1) 145 } 146 shard.Unlock() 147 } 148 149 // GetAndDo returns the value corresponding to the key, and apply fn to key value before return value 150 // return (value, value exist or not, error when evaluation fn) 151 func (cmap *ShardedConcurrentTxMap) GetAndDo(key interface{}, fn ActionFunc) (interface{}, bool, error) { 152 shard := cmap.getShard(key) 153 var value interface{} 154 var ok bool 155 var err error 156 shard.Lock() 157 if shard.items != nil { 158 value, ok = shard.items[key] 159 if ok { 160 err = fn(key, value) 161 } 162 } 163 shard.Unlock() 164 return value, ok, err 165 } 166 167 // PutOrDo put the key value in the map, if key does not exists, otherwise, call fn with existing key and value 168 // return (value, fn evaluated or not, error when evaluation fn) 169 func (cmap *ShardedConcurrentTxMap) PutOrDo(key interface{}, value interface{}, fn ActionFunc) (interface{}, bool, error) { 170 shard := cmap.getShard(key) 171 var err error 172 shard.Lock() 173 cmap.lazyInitShard(shard) 174 v, ok := shard.items[key] 175 if !ok { 176 shard.items[key] = value 177 v = value 178 atomic.AddInt32(&cmap.size, 1) 179 } else { 180 err = fn(key, v) 181 } 182 shard.Unlock() 183 return v, ok, err 184 } 185 186 // RemoveIf deletes the given key from the map if fn return true 187 func (cmap *ShardedConcurrentTxMap) RemoveIf(key interface{}, fn PredicateFunc) bool { 188 shard := cmap.getShard(key) 189 var removed bool 190 shard.Lock() 191 if shard.items != nil { 192 value, ok := shard.items[key] 193 if ok && fn(key, value) { 194 removed = true 195 delete(shard.items, key) 196 atomic.AddInt32(&cmap.size, -1) 197 } 198 } 199 shard.Unlock() 200 return removed 201 } 202 203 // Close closes the iterator 204 func (it *mapIteratorImpl) Close() { 205 close(it.stopCh) 206 } 207 208 // Entries returns a channel of map entries 209 func (it *mapIteratorImpl) Entries() <-chan *MapEntry { 210 return it.dataCh 211 } 212 213 // Iter returns an iterator to the map. This map 214 // does not use re-entrant locks, so access or modification 215 // to the map during iteration can cause a dead lock. 216 func (cmap *ShardedConcurrentTxMap) Iter() MapIterator { 217 218 iterator := new(mapIteratorImpl) 219 iterator.dataCh = make(chan *MapEntry, 8) 220 iterator.stopCh = make(chan struct{}) 221 222 go func(iterator *mapIteratorImpl) { 223 for i := 0; i < nShards; i++ { 224 cmap.shards[i].RLock() 225 for k, v := range cmap.shards[i].items { 226 entry := &MapEntry{Key: k, Value: v} 227 select { 228 case iterator.dataCh <- entry: 229 case <-iterator.stopCh: 230 cmap.shards[i].RUnlock() 231 close(iterator.dataCh) 232 return 233 } 234 } 235 cmap.shards[i].RUnlock() 236 } 237 close(iterator.dataCh) 238 }(iterator) 239 240 return iterator 241 } 242 243 // Len returns the number of items in the map 244 func (cmap *ShardedConcurrentTxMap) Len() int { 245 return int(atomic.LoadInt32(&cmap.size)) 246 } 247 248 func (cmap *ShardedConcurrentTxMap) getShard(key interface{}) *mapShard { 249 shardIdx := cmap.hashfn(key) % nShards 250 return &cmap.shards[shardIdx] 251 } 252 253 func (cmap *ShardedConcurrentTxMap) lazyInitShard(shard *mapShard) { 254 if shard.items == nil { 255 shard.items = make(map[interface{}]interface{}, cmap.initialCap) 256 } 257 }