Files
tasks/common/collection/syncmap.go
Tim Deeb-SwihartandGitHub 0f17459a46 Fix matching data race and add a canonical concurrent map implementation (#5506)
## What changed?

`go.temporal.io/server/common/concurrent.Map` is now the canonical
"locking map" implementation for the server (unless there's another one
I'm aware of).

## Why?

We recently found a data race in matching's lockableResultMap where the
wrong lock type was taken during the `pop` operation, causing panics at
run time. It's an easy mistake to make when you're writing multiple
implementations like this, so I made a common one we can reuse.

## How did you test it?

Added new tests and manually looked at all 40 lines of code to ensure
the correct locks were taken.

## Potential risks
None

## Documentation
No

## Is hotfix candidate?
Maybe?
2024-03-08 22:07:18 +00:00

71 lines
2.1 KiB
Go

// The MIT License
//
// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package collection
import "sync"
// SyncMap implements a simple mutex-wrapped map. We've had bugs where we took the wrong lock
// when reimplementing this pattern, so it's worth having a single canonical implementation.
type SyncMap[K comparable, V any] struct {
*sync.RWMutex
contents map[K]V
}
func NewSyncMap[K comparable, V any]() SyncMap[K, V] {
return SyncMap[K, V]{
RWMutex: &sync.RWMutex{},
contents: make(map[K]V),
}
}
func (m *SyncMap[K, V]) Get(key K) (value V, ok bool) {
m.RLock()
defer m.RUnlock()
value, ok = m.contents[key]
return
}
func (m *SyncMap[K, V]) Set(key K, value V) {
m.Lock()
defer m.Unlock()
m.contents[key] = value
}
func (m *SyncMap[K, V]) Delete(key K) {
m.Lock()
defer m.Unlock()
delete(m.contents, key)
}
func (m *SyncMap[K, V]) Pop(key K) (value V, ok bool) {
m.Lock()
defer m.Unlock()
value, ok = m.contents[key]
if ok {
delete(m.contents, key)
}
return value, ok
}