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  }