]> Cypherpunks repositories - gostls13.git/commitdiff
sync: add Map.Clear
authorHiro <laciferin@gmail.com>
Wed, 3 Jan 2024 00:10:58 +0000 (00:10 +0000)
committerGopher Robot <gobot@golang.org>
Thu, 1 Feb 2024 15:34:22 +0000 (15:34 +0000)
Fixes #61696

Change-Id: I0a31afd3bc433fc84280d56f2798bda10da61eba
GitHub-Last-Rev: 17bedc864f1685178a42b59f7083677a6124f831
GitHub-Pull-Request: golang/go#61702
Reviewed-on: https://go-review.googlesource.com/c/go/+/515015
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: qiulaidongfeng <2645477756@qq.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

api/next/61696.txt [new file with mode: 0644]
src/sync/map.go
src/sync/map_bench_test.go
src/sync/map_reference_test.go
src/sync/map_test.go

diff --git a/api/next/61696.txt b/api/next/61696.txt
new file mode 100644 (file)
index 0000000..8adaf3d
--- /dev/null
@@ -0,0 +1 @@
+pkg sync, method (*Map) Clear() #61696
index 7a9eebdce39d541cd6caa14464dd6de8517b785a..1f26cdd8bb56cd2b8a9491f41919c175a0ce1fb6 100644 (file)
@@ -155,6 +155,26 @@ func (m *Map) Store(key, value any) {
        _, _ = m.Swap(key, value)
 }
 
+// Clear deletes all the keys.
+func (m *Map) Clear() {
+       read := m.loadReadOnly()
+       if len(read.m) == 0 && !read.amended {
+               // Avoid allocating a new readOnly when the map is already clear.
+               return
+       }
+
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       read = m.loadReadOnly()
+       if len(read.m) > 0 || read.amended {
+               m.read.Store(&readOnly{})
+       }
+
+       clear(m.dirty)
+       m.misses = 0 // Don't immediately promote the newly-cleared dirty map on the next operation
+}
+
 // tryCompareAndSwap compare the entry with the given old value and swaps
 // it with a new value if the entry is equal to the old value, and the entry
 // has not been expunged.
index eebec3bacfccc27ac05a15dee3509d127044a84e..fb9eb25432d4b8fb6fc2c7521f0d01763e7102f5 100644 (file)
@@ -533,3 +533,15 @@ func BenchmarkCompareAndDeleteMostlyMisses(b *testing.B) {
                },
        })
 }
+
+func BenchmarkClear(b *testing.B) {
+       benchMap(b, bench{
+               perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
+                       for ; pb.Next(); i++ {
+                               k, v := i%256, i%256
+                               m.Clear()
+                               m.Store(k, v)
+                       }
+               },
+       })
+}
index aa5ebf352f98f0ec68f5e2a2929d59ff729c41f7..283da0f3a95fde3f7234240618d30e188290a462 100644 (file)
@@ -13,7 +13,7 @@ import (
 
 // mapInterface is the interface Map implements.
 type mapInterface interface {
-       Load(any) (any, bool)
+       Load(key any) (value any, ok bool)
        Store(key, value any)
        LoadOrStore(key, value any) (actual any, loaded bool)
        LoadAndDelete(key any) (value any, loaded bool)
@@ -22,6 +22,7 @@ type mapInterface interface {
        CompareAndSwap(key, old, new any) (swapped bool)
        CompareAndDelete(key, old any) (deleted bool)
        Range(func(key, value any) (shouldContinue bool))
+       Clear()
 }
 
 var (
@@ -144,6 +145,13 @@ func (m *RWMutexMap) Range(f func(key, value any) (shouldContinue bool)) {
        }
 }
 
+func (m *RWMutexMap) Clear() {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       clear(m.dirty)
+}
+
 // DeepCopyMap is an implementation of mapInterface using a Mutex and
 // atomic.Value.  It makes deep copies of the map on every write to avoid
 // acquiring the Mutex in Load.
@@ -269,3 +277,10 @@ func (m *DeepCopyMap) dirty() map[any]any {
        }
        return dirty
 }
+
+func (m *DeepCopyMap) Clear() {
+       m.mu.Lock()
+       defer m.mu.Unlock()
+
+       m.clean.Store((map[any]any)(nil))
+}
index 316f87bacc31589ee0d3d6cc9c52506e19115bf6..e1d0380765abc9a2f9ba270a5c6cfdf64ad7dc4f 100644 (file)
@@ -26,6 +26,7 @@ const (
        opSwap             = mapOp("Swap")
        opCompareAndSwap   = mapOp("CompareAndSwap")
        opCompareAndDelete = mapOp("CompareAndDelete")
+       opClear            = mapOp("Clear")
 )
 
 var mapOps = [...]mapOp{
@@ -37,6 +38,7 @@ var mapOps = [...]mapOp{
        opSwap,
        opCompareAndSwap,
        opCompareAndDelete,
+       opClear,
 }
 
 // mapCall is a quick.Generator for calls on mapInterface.
@@ -74,6 +76,9 @@ func (c mapCall) apply(m mapInterface) (any, bool) {
                        }
                }
                return nil, false
+       case opClear:
+               m.Clear()
+               return nil, false
        default:
                panic("invalid mapOp")
        }
@@ -294,3 +299,61 @@ func TestMapRangeNoAllocations(t *testing.T) { // Issue 62404
                t.Errorf("AllocsPerRun of m.Range = %v; want 0", allocs)
        }
 }
+
+// TestConcurrentClear tests concurrent behavior of sync.Map properties to ensure no data races.
+// Checks for proper synchronization between Clear, Store, Load operations.
+func TestConcurrentClear(t *testing.T) {
+       var m sync.Map
+
+       wg := sync.WaitGroup{}
+       wg.Add(30) // 10 goroutines for writing, 10 goroutines for reading, 10 goroutines for waiting
+
+       // Writing data to the map concurrently
+       for i := 0; i < 10; i++ {
+               go func(k, v int) {
+                       defer wg.Done()
+                       m.Store(k, v)
+               }(i, i*10)
+       }
+
+       // Reading data from the map concurrently
+       for i := 0; i < 10; i++ {
+               go func(k int) {
+                       defer wg.Done()
+                       if value, ok := m.Load(k); ok {
+                               t.Logf("Key: %v, Value: %v\n", k, value)
+                       } else {
+                               t.Logf("Key: %v not found\n", k)
+                       }
+               }(i)
+       }
+
+       // Clearing data from the map concurrently
+       for i := 0; i < 10; i++ {
+               go func() {
+                       defer wg.Done()
+                       m.Clear()
+               }()
+       }
+
+       wg.Wait()
+
+       m.Clear()
+
+       m.Range(func(k, v any) bool {
+               t.Errorf("after Clear, Map contains (%v, %v); expected to be empty", k, v)
+
+               return true
+       })
+}
+
+func TestMapClearNoAllocations(t *testing.T) {
+       testenv.SkipIfOptimizationOff(t)
+       var m sync.Map
+       allocs := testing.AllocsPerRun(10, func() {
+               m.Clear()
+       })
+       if allocs > 0 {
+               t.Errorf("AllocsPerRun of m.Clear = %v; want 0", allocs)
+       }
+}