mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
internal/sync: add test from issue 70970
This test checks a use-case of sync.Map that's expected to be more common in Go 1.24 and beyond, as a concurrent weak cache. The test will also fail if CompareAndSwap is not properly atomic with CompareAndDelete, which is what #70970 is actually about. We should have more explicit tests checking mutual atomicity of operations, but for now this is OK, and still useful. For #70970. Change-Id: I6db508660691586a8af9ad511c9a96432d333343 Reviewed-on: https://go-review.googlesource.com/c/go/+/640737 Reviewed-by: David Chase <drchase@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
7a2e88e911
commit
3f002abb60
1 changed files with 59 additions and 0 deletions
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"weak"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHashTrieMap(t *testing.T) {
|
func TestHashTrieMap(t *testing.T) {
|
||||||
|
|
@ -921,3 +922,61 @@ func init() {
|
||||||
testDataLarge[i] = fmt.Sprintf("%b", i)
|
testDataLarge[i] = fmt.Sprintf("%b", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestConcurrentCache tests HashTrieMap in a scenario where it is used as
|
||||||
|
// the basis of a memory-efficient concurrent cache. We're specifically
|
||||||
|
// looking to make sure that CompareAndSwap and CompareAndDelete are
|
||||||
|
// atomic with respect to one another. When competing for the same
|
||||||
|
// key-value pair, they must not both succeed.
|
||||||
|
//
|
||||||
|
// This test is a regression test for issue #70970.
|
||||||
|
func TestConcurrentCache(t *testing.T) {
|
||||||
|
type dummy [32]byte
|
||||||
|
|
||||||
|
var m isync.HashTrieMap[int, weak.Pointer[dummy]]
|
||||||
|
|
||||||
|
type cleanupArg struct {
|
||||||
|
key int
|
||||||
|
value weak.Pointer[dummy]
|
||||||
|
}
|
||||||
|
cleanup := func(arg cleanupArg) {
|
||||||
|
m.CompareAndDelete(arg.key, arg.value)
|
||||||
|
}
|
||||||
|
get := func(m *isync.HashTrieMap[int, weak.Pointer[dummy]], key int) *dummy {
|
||||||
|
nv := new(dummy)
|
||||||
|
nw := weak.Make(nv)
|
||||||
|
for {
|
||||||
|
w, loaded := m.LoadOrStore(key, nw)
|
||||||
|
if !loaded {
|
||||||
|
runtime.AddCleanup(nv, cleanup, cleanupArg{key, nw})
|
||||||
|
return nv
|
||||||
|
}
|
||||||
|
if v := w.Value(); v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weak pointer was reclaimed, try to replace it with nw.
|
||||||
|
if m.CompareAndSwap(key, w, nw) {
|
||||||
|
runtime.AddCleanup(nv, cleanup, cleanupArg{key, nw})
|
||||||
|
return nv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const N = 100_000
|
||||||
|
const P = 5_000
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(N)
|
||||||
|
for i := range N {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
a := get(&m, i%P)
|
||||||
|
b := get(&m, i%P)
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("consecutive cache reads returned different values: a != b (%p vs %p)\n", a, b)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue