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"
|
||||
"sync"
|
||||
"testing"
|
||||
"weak"
|
||||
)
|
||||
|
||||
func TestHashTrieMap(t *testing.T) {
|
||||
|
|
@ -921,3 +922,61 @@ func init() {
|
|||
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