mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 03:31:39 +00:00
syncs: add Map.WithLock to allow mutations to the underlying map (#8101)
Some operations cannot be implemented with the prior API: * Iterating over the map and deleting keys * Iterating over the map and replacing items * Calling APIs that expect a native Go map Add a Map.WithLock method that acquires a write-lock on the map and then calls a user-provided closure with the underlying Go map. This allows users to interact with the Map as a regular Go map, but with the gaurantees that it is concurrent safe. Updates tailscale/corp#9115 Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
parent
fc28c8e7f3
commit
d209b032ab
@ -252,8 +252,10 @@ func (m *Map[K, V]) Delete(key K) {
|
|||||||
delete(m.m, key)
|
delete(m.m, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range iterates over the map in undefined order calling f for each entry.
|
// Range iterates over the map in an undefined order calling f for each entry.
|
||||||
// Iteration stops if f returns false. Map changes are blocked during iteration.
|
// Iteration stops if f returns false. Map changes are blocked during iteration.
|
||||||
|
// A read lock is held for the entire duration of the iteration.
|
||||||
|
// Use the [WithLock] method instead to mutate the map during iteration.
|
||||||
func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
@ -264,6 +266,15 @@ func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithLock calls f with the underlying map.
|
||||||
|
// Use of m2 must not escape the duration of this call.
|
||||||
|
// The write-lock is held for the entire duration of this call.
|
||||||
|
func (m *Map[K, V]) WithLock(f func(m2 map[K]V)) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
f(m.m)
|
||||||
|
}
|
||||||
|
|
||||||
// Len returns the length of the map.
|
// Len returns the length of the map.
|
||||||
func (m *Map[K, V]) Len() int {
|
func (m *Map[K, V]) Len() int {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
@ -189,19 +188,11 @@ func TestMap(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("LoadOrStore", func(t *testing.T) {
|
t.Run("LoadOrStore", func(t *testing.T) {
|
||||||
var m Map[string, string]
|
var m Map[string, string]
|
||||||
var wg sync.WaitGroup
|
var wg WaitGroup
|
||||||
wg.Add(2)
|
|
||||||
var ok1, ok2 bool
|
var ok1, ok2 bool
|
||||||
go func() {
|
wg.Go(func() { _, ok1 = m.LoadOrStore("", "") })
|
||||||
defer wg.Done()
|
wg.Go(func() { _, ok2 = m.LoadOrStore("", "") })
|
||||||
_, ok1 = m.LoadOrStore("", "")
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
_, ok2 = m.LoadOrStore("", "")
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
if ok1 == ok2 {
|
if ok1 == ok2 {
|
||||||
t.Errorf("exactly one LoadOrStore should load")
|
t.Errorf("exactly one LoadOrStore should load")
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user