syncs: add ShardedMap.Mutate
To let callers do atomic/CAS-like operations. Updates tailscale/corp#7355 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
ab310a7f60
commit
cafd9a2bec
|
@ -59,9 +59,36 @@ func (m *ShardedMap[K, V]) Get(key K) (value V) {
|
|||
return
|
||||
}
|
||||
|
||||
// Mutate atomically mutates m[k] by calling mutator.
|
||||
//
|
||||
// The mutator function is called with the old value (or its zero value) and
|
||||
// whether it existed in the map and it returns the new value and whether it
|
||||
// should be set in the map (true) or deleted from the map (false).
|
||||
//
|
||||
// It returns the change in size of the map as a result of the mutation, one of
|
||||
// -1 (delete), 0 (change), or 1 (addition).
|
||||
func (m *ShardedMap[K, V]) Mutate(key K, mutator func(oldValue V, oldValueExisted bool) (newValue V, keep bool)) (sizeDelta int) {
|
||||
shard := m.shard(key)
|
||||
shard.mu.Lock()
|
||||
defer shard.mu.Unlock()
|
||||
oldV, oldOK := shard.m[key]
|
||||
newV, newOK := mutator(oldV, oldOK)
|
||||
if newOK {
|
||||
shard.m[key] = newV
|
||||
if oldOK {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
delete(shard.m, key)
|
||||
if oldOK {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Set sets m[key] = value.
|
||||
//
|
||||
// It reports whether the map grew in size (that is, whether key was not already
|
||||
// present in m).
|
||||
func (m *ShardedMap[K, V]) Set(key K, value V) (grew bool) {
|
||||
shard := m.shard(key)
|
||||
|
|
|
@ -41,4 +41,41 @@ func TestShardedMap(t *testing.T) {
|
|||
if g, w := m.Len(), 0; g != w {
|
||||
t.Errorf("got Len %v; want %v", g, w)
|
||||
}
|
||||
|
||||
// Mutation adding an entry.
|
||||
if v := m.Mutate(1, func(was string, ok bool) (string, bool) {
|
||||
if ok {
|
||||
t.Fatal("was okay")
|
||||
}
|
||||
return "ONE", true
|
||||
}); v != 1 {
|
||||
t.Errorf("Mutate = %v; want 1", v)
|
||||
}
|
||||
if g, w := m.Get(1), "ONE"; g != w {
|
||||
t.Errorf("got %q; want %q", g, w)
|
||||
}
|
||||
// Mutation changing an entry.
|
||||
if v := m.Mutate(1, func(was string, ok bool) (string, bool) {
|
||||
if !ok {
|
||||
t.Fatal("wasn't okay")
|
||||
}
|
||||
return was + "-" + was, true
|
||||
}); v != 0 {
|
||||
t.Errorf("Mutate = %v; want 0", v)
|
||||
}
|
||||
if g, w := m.Get(1), "ONE-ONE"; g != w {
|
||||
t.Errorf("got %q; want %q", g, w)
|
||||
}
|
||||
// Mutation removing an entry.
|
||||
if v := m.Mutate(1, func(was string, ok bool) (string, bool) {
|
||||
if !ok {
|
||||
t.Fatal("wasn't okay")
|
||||
}
|
||||
return "", false
|
||||
}); v != -1 {
|
||||
t.Errorf("Mutate = %v; want -1", v)
|
||||
}
|
||||
if g, w := m.Get(1), ""; g != w {
|
||||
t.Errorf("got %q; want %q", g, w)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue