Files
tasks/common/collection/concurrent_tx_map.go
Stephan BehnkeandGitHub c9e360a3fb go fix (#9337)
## What changed?

Integrate Go 1.26's new `go fix` into workflow.

NOTE that the changes caused our linter to fire; a [separate
commit](https://github.com/temporalio/temporal/pull/9337/commits/1b23f787aef370098b35829c4d17dcc06230b4c1)
addresses those.

## Why?

Ensure Go code is standardized/modernized.

## How did you test it?
- [ ] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)
2026-02-18 09:12:19 -08:00

234 lines
5.7 KiB
Go

package collection
import (
"sync"
"sync/atomic"
)
const (
// nShards represents the number of shards
// At any given point of time, there can only
// be nShards number of concurrent writers to
// the map at max
nShards = 32
)
type (
// ShardedConcurrentTxMap is an implementation of
// ConcurrentMap that internally uses multiple
// sharded maps to increase parallelism
ShardedConcurrentTxMap struct {
shards [nShards]mapShard
hashfn HashFunc
size int32
initialCap int
}
// mapIteratorImpl represents an iterator type
// for the concurrent map.
mapIteratorImpl struct {
stopCh chan struct{}
dataCh chan *MapEntry
}
// mapShard represents a single instance
// of thread safe map
mapShard struct {
sync.RWMutex
items map[any]any
}
)
// NewShardedConcurrentTxMap returns an instance of ShardedConcurrentMap
//
// ShardedConcurrentMap is a thread safe map that maintains upto nShards
// number of maps internally to allow nShards writers to be acive at the
// same time. This map *does not* use re-entrant locks, so access to the
// map during iterator can cause a dead lock.
//
// @param initialSz
//
// The initial size for the map
//
// @param hashfn
//
// The hash function to use for sharding
func NewShardedConcurrentTxMap(initialCap int, hashfn HashFunc) ConcurrentTxMap {
cmap := new(ShardedConcurrentTxMap)
cmap.hashfn = hashfn
cmap.initialCap = max(nShards, initialCap/nShards)
return cmap
}
// Get returns the value corresponding to the key, if it exist
func (cmap *ShardedConcurrentTxMap) Get(key any) (any, bool) {
shard := cmap.getShard(key)
var ok bool
var value any
shard.RLock()
if shard.items != nil {
value, ok = shard.items[key]
}
shard.RUnlock()
return value, ok
}
// Contains returns true if the key exist and false otherwise
func (cmap *ShardedConcurrentTxMap) Contains(key any) bool {
_, ok := cmap.Get(key)
return ok
}
// Put records the given key value mapping. Overwrites previous values
func (cmap *ShardedConcurrentTxMap) Put(key any, value any) {
shard := cmap.getShard(key)
shard.Lock()
cmap.lazyInitShard(shard)
_, ok := shard.items[key]
if !ok {
atomic.AddInt32(&cmap.size, 1)
}
shard.items[key] = value
shard.Unlock()
}
// PutIfNotExist records the mapping, if there is no mapping for this key already
// Returns true if the mapping was recorded, false otherwise
func (cmap *ShardedConcurrentTxMap) PutIfNotExist(key any, value any) bool {
shard := cmap.getShard(key)
var ok bool
shard.Lock()
cmap.lazyInitShard(shard)
_, ok = shard.items[key]
if !ok {
shard.items[key] = value
atomic.AddInt32(&cmap.size, 1)
}
shard.Unlock()
return !ok
}
// Remove deletes the given key from the map
func (cmap *ShardedConcurrentTxMap) Remove(key any) {
shard := cmap.getShard(key)
shard.Lock()
cmap.lazyInitShard(shard)
_, ok := shard.items[key]
if ok {
delete(shard.items, key)
atomic.AddInt32(&cmap.size, -1)
}
shard.Unlock()
}
// GetAndDo returns the value corresponding to the key, and apply fn to key value before return value
// return (value, value exist or not, error when evaluation fn)
func (cmap *ShardedConcurrentTxMap) GetAndDo(key any, fn ActionFunc) (any, bool, error) {
shard := cmap.getShard(key)
var value any
var ok bool
var err error
shard.Lock()
if shard.items != nil {
value, ok = shard.items[key]
if ok {
err = fn(key, value)
}
}
shard.Unlock()
return value, ok, err
}
// PutOrDo put the key value in the map, if key does not exists, otherwise, call fn with existing key and value
// return (value, fn evaluated or not, error when evaluation fn)
func (cmap *ShardedConcurrentTxMap) PutOrDo(key any, value any, fn ActionFunc) (any, bool, error) {
shard := cmap.getShard(key)
var err error
shard.Lock()
cmap.lazyInitShard(shard)
v, ok := shard.items[key]
if !ok {
shard.items[key] = value
v = value
atomic.AddInt32(&cmap.size, 1)
} else {
err = fn(key, v)
}
shard.Unlock()
return v, ok, err
}
// RemoveIf deletes the given key from the map if fn return true
func (cmap *ShardedConcurrentTxMap) RemoveIf(key any, fn PredicateFunc) bool {
shard := cmap.getShard(key)
var removed bool
shard.Lock()
if shard.items != nil {
value, ok := shard.items[key]
if ok && fn(key, value) {
removed = true
delete(shard.items, key)
atomic.AddInt32(&cmap.size, -1)
}
}
shard.Unlock()
return removed
}
// Close closes the iterator
func (it *mapIteratorImpl) Close() {
close(it.stopCh)
}
// Entries returns a channel of map entries
func (it *mapIteratorImpl) Entries() <-chan *MapEntry {
return it.dataCh
}
// Iter returns an iterator to the map. This map
// does not use re-entrant locks, so access or modification
// to the map during iteration can cause a dead lock.
func (cmap *ShardedConcurrentTxMap) Iter() MapIterator {
iterator := new(mapIteratorImpl)
iterator.dataCh = make(chan *MapEntry, 8)
iterator.stopCh = make(chan struct{})
go func(iterator *mapIteratorImpl) {
for i := range nShards {
cmap.shards[i].RLock()
for k, v := range cmap.shards[i].items {
entry := &MapEntry{Key: k, Value: v}
select {
case iterator.dataCh <- entry:
case <-iterator.stopCh:
cmap.shards[i].RUnlock()
close(iterator.dataCh)
return
}
}
cmap.shards[i].RUnlock()
}
close(iterator.dataCh)
}(iterator)
return iterator
}
// Len returns the number of items in the map
func (cmap *ShardedConcurrentTxMap) Len() int {
return int(atomic.LoadInt32(&cmap.size))
}
func (cmap *ShardedConcurrentTxMap) getShard(key any) *mapShard {
shardIdx := cmap.hashfn(key) % nShards
return &cmap.shards[shardIdx]
}
func (cmap *ShardedConcurrentTxMap) lazyInitShard(shard *mapShard) {
if shard.items == nil {
shard.items = make(map[any]any, cmap.initialCap)
}
}