summaryrefslogtreecommitdiffstats
path: root/src/sync/mutex_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/sync/mutex_test.go
parentInitial commit. (diff)
downloadgolang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.tar.xz
golang-1.20-43a123c1ae6613b3efeed291fa552ecd909d3acf.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/sync/mutex_test.go')
-rw-r--r--src/sync/mutex_test.go335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/sync/mutex_test.go b/src/sync/mutex_test.go
new file mode 100644
index 0000000..cca0986
--- /dev/null
+++ b/src/sync/mutex_test.go
@@ -0,0 +1,335 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// GOMAXPROCS=10 go test
+
+package sync_test
+
+import (
+ "fmt"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "runtime"
+ "strings"
+ . "sync"
+ "testing"
+ "time"
+)
+
+func HammerSemaphore(s *uint32, loops int, cdone chan bool) {
+ for i := 0; i < loops; i++ {
+ Runtime_Semacquire(s)
+ Runtime_Semrelease(s, false, 0)
+ }
+ cdone <- true
+}
+
+func TestSemaphore(t *testing.T) {
+ s := new(uint32)
+ *s = 1
+ c := make(chan bool)
+ for i := 0; i < 10; i++ {
+ go HammerSemaphore(s, 1000, c)
+ }
+ for i := 0; i < 10; i++ {
+ <-c
+ }
+}
+
+func BenchmarkUncontendedSemaphore(b *testing.B) {
+ s := new(uint32)
+ *s = 1
+ HammerSemaphore(s, b.N, make(chan bool, 2))
+}
+
+func BenchmarkContendedSemaphore(b *testing.B) {
+ b.StopTimer()
+ s := new(uint32)
+ *s = 1
+ c := make(chan bool)
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
+ b.StartTimer()
+
+ go HammerSemaphore(s, b.N/2, c)
+ go HammerSemaphore(s, b.N/2, c)
+ <-c
+ <-c
+}
+
+func HammerMutex(m *Mutex, loops int, cdone chan bool) {
+ for i := 0; i < loops; i++ {
+ if i%3 == 0 {
+ if m.TryLock() {
+ m.Unlock()
+ }
+ continue
+ }
+ m.Lock()
+ m.Unlock()
+ }
+ cdone <- true
+}
+
+func TestMutex(t *testing.T) {
+ if n := runtime.SetMutexProfileFraction(1); n != 0 {
+ t.Logf("got mutexrate %d expected 0", n)
+ }
+ defer runtime.SetMutexProfileFraction(0)
+
+ m := new(Mutex)
+
+ m.Lock()
+ if m.TryLock() {
+ t.Fatalf("TryLock succeeded with mutex locked")
+ }
+ m.Unlock()
+ if !m.TryLock() {
+ t.Fatalf("TryLock failed with mutex unlocked")
+ }
+ m.Unlock()
+
+ c := make(chan bool)
+ for i := 0; i < 10; i++ {
+ go HammerMutex(m, 1000, c)
+ }
+ for i := 0; i < 10; i++ {
+ <-c
+ }
+}
+
+var misuseTests = []struct {
+ name string
+ f func()
+}{
+ {
+ "Mutex.Unlock",
+ func() {
+ var mu Mutex
+ mu.Unlock()
+ },
+ },
+ {
+ "Mutex.Unlock2",
+ func() {
+ var mu Mutex
+ mu.Lock()
+ mu.Unlock()
+ mu.Unlock()
+ },
+ },
+ {
+ "RWMutex.Unlock",
+ func() {
+ var mu RWMutex
+ mu.Unlock()
+ },
+ },
+ {
+ "RWMutex.Unlock2",
+ func() {
+ var mu RWMutex
+ mu.RLock()
+ mu.Unlock()
+ },
+ },
+ {
+ "RWMutex.Unlock3",
+ func() {
+ var mu RWMutex
+ mu.Lock()
+ mu.Unlock()
+ mu.Unlock()
+ },
+ },
+ {
+ "RWMutex.RUnlock",
+ func() {
+ var mu RWMutex
+ mu.RUnlock()
+ },
+ },
+ {
+ "RWMutex.RUnlock2",
+ func() {
+ var mu RWMutex
+ mu.Lock()
+ mu.RUnlock()
+ },
+ },
+ {
+ "RWMutex.RUnlock3",
+ func() {
+ var mu RWMutex
+ mu.RLock()
+ mu.RUnlock()
+ mu.RUnlock()
+ },
+ },
+}
+
+func init() {
+ if len(os.Args) == 3 && os.Args[1] == "TESTMISUSE" {
+ for _, test := range misuseTests {
+ if test.name == os.Args[2] {
+ func() {
+ defer func() { recover() }()
+ test.f()
+ }()
+ fmt.Printf("test completed\n")
+ os.Exit(0)
+ }
+ }
+ fmt.Printf("unknown test\n")
+ os.Exit(0)
+ }
+}
+
+func TestMutexMisuse(t *testing.T) {
+ testenv.MustHaveExec(t)
+ for _, test := range misuseTests {
+ out, err := exec.Command(os.Args[0], "TESTMISUSE", test.name).CombinedOutput()
+ if err == nil || !strings.Contains(string(out), "unlocked") {
+ t.Errorf("%s: did not find failure with message about unlocked lock: %s\n%s\n", test.name, err, out)
+ }
+ }
+}
+
+func TestMutexFairness(t *testing.T) {
+ var mu Mutex
+ stop := make(chan bool)
+ defer close(stop)
+ go func() {
+ for {
+ mu.Lock()
+ time.Sleep(100 * time.Microsecond)
+ mu.Unlock()
+ select {
+ case <-stop:
+ return
+ default:
+ }
+ }
+ }()
+ done := make(chan bool, 1)
+ go func() {
+ for i := 0; i < 10; i++ {
+ time.Sleep(100 * time.Microsecond)
+ mu.Lock()
+ mu.Unlock()
+ }
+ done <- true
+ }()
+ select {
+ case <-done:
+ case <-time.After(10 * time.Second):
+ t.Fatalf("can't acquire Mutex in 10 seconds")
+ }
+}
+
+func BenchmarkMutexUncontended(b *testing.B) {
+ type PaddedMutex struct {
+ Mutex
+ pad [128]uint8
+ }
+ b.RunParallel(func(pb *testing.PB) {
+ var mu PaddedMutex
+ for pb.Next() {
+ mu.Lock()
+ mu.Unlock()
+ }
+ })
+}
+
+func benchmarkMutex(b *testing.B, slack, work bool) {
+ var mu Mutex
+ if slack {
+ b.SetParallelism(10)
+ }
+ b.RunParallel(func(pb *testing.PB) {
+ foo := 0
+ for pb.Next() {
+ mu.Lock()
+ mu.Unlock()
+ if work {
+ for i := 0; i < 100; i++ {
+ foo *= 2
+ foo /= 2
+ }
+ }
+ }
+ _ = foo
+ })
+}
+
+func BenchmarkMutex(b *testing.B) {
+ benchmarkMutex(b, false, false)
+}
+
+func BenchmarkMutexSlack(b *testing.B) {
+ benchmarkMutex(b, true, false)
+}
+
+func BenchmarkMutexWork(b *testing.B) {
+ benchmarkMutex(b, false, true)
+}
+
+func BenchmarkMutexWorkSlack(b *testing.B) {
+ benchmarkMutex(b, true, true)
+}
+
+func BenchmarkMutexNoSpin(b *testing.B) {
+ // This benchmark models a situation where spinning in the mutex should be
+ // non-profitable and allows to confirm that spinning does not do harm.
+ // To achieve this we create excess of goroutines most of which do local work.
+ // These goroutines yield during local work, so that switching from
+ // a blocked goroutine to other goroutines is profitable.
+ // As a matter of fact, this benchmark still triggers some spinning in the mutex.
+ var m Mutex
+ var acc0, acc1 uint64
+ b.SetParallelism(4)
+ b.RunParallel(func(pb *testing.PB) {
+ c := make(chan bool)
+ var data [4 << 10]uint64
+ for i := 0; pb.Next(); i++ {
+ if i%4 == 0 {
+ m.Lock()
+ acc0 -= 100
+ acc1 += 100
+ m.Unlock()
+ } else {
+ for i := 0; i < len(data); i += 4 {
+ data[i]++
+ }
+ // Elaborate way to say runtime.Gosched
+ // that does not put the goroutine onto global runq.
+ go func() {
+ c <- true
+ }()
+ <-c
+ }
+ }
+ })
+}
+
+func BenchmarkMutexSpin(b *testing.B) {
+ // This benchmark models a situation where spinning in the mutex should be
+ // profitable. To achieve this we create a goroutine per-proc.
+ // These goroutines access considerable amount of local data so that
+ // unnecessary rescheduling is penalized by cache misses.
+ var m Mutex
+ var acc0, acc1 uint64
+ b.RunParallel(func(pb *testing.PB) {
+ var data [16 << 10]uint64
+ for i := 0; pb.Next(); i++ {
+ m.Lock()
+ acc0 -= 100
+ acc1 += 100
+ m.Unlock()
+ for i := 0; i < len(data); i += 4 {
+ data[i]++
+ }
+ }
+ })
+}