diff --git a/cannon/mipsevm/multithreaded/instrumented_test.go b/cannon/mipsevm/multithreaded/instrumented_test.go index ec079e50c8ce7..6c8fade9ff537 100644 --- a/cannon/mipsevm/multithreaded/instrumented_test.go +++ b/cannon/mipsevm/multithreaded/instrumented_test.go @@ -36,27 +36,90 @@ func TestInstrumentedState_Claim(t *testing.T) { func TestInstrumentedState_MultithreadedProgram(t *testing.T) { t.Parallel() - state, _ := testutil.LoadELFProgram(t, testutil.ProgramPath("multithreaded"), CreateInitialState, false) - oracle := testutil.StaticOracle(t, []byte{}) - - var stdOutBuf, stdErrBuf bytes.Buffer - us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), nil) - for i := 0; i < 2_000_000; i++ { - if us.GetState().GetExited() { - break - } - _, err := us.Step(false) - require.NoError(t, err) + cases := []struct { + name string + expectedOutput []string + programName string + }{ + { + name: "wg and chan test", + expectedOutput: []string{ + "waitgroup result: 42", + "channels result: 1234", + }, + programName: "mt-wg", + }, + { + name: "mutex test", + expectedOutput: []string{ + "Mutex test passed", + }, + programName: "mt-mutex", + }, + { + name: "cond test", + expectedOutput: []string{ + "Cond test passed", + }, + programName: "mt-cond", + }, + { + name: "rwmutex test", + expectedOutput: []string{ + "RWMutex test passed", + }, + programName: "mt-rwmutex", + }, + { + name: "once test", + expectedOutput: []string{ + "Once test passed", + }, + programName: "mt-once", + }, + { + name: "map test", + expectedOutput: []string{ + "Map test passed", + }, + programName: "mt-map", + }, + { + name: "pool test", + expectedOutput: []string{ + "Pool test passed", + }, + programName: "mt-pool", + }, } - t.Logf("Completed in %d steps", state.Step) - require.True(t, state.Exited, "must complete program") - require.Equal(t, uint8(0), state.ExitCode, "exit with 0") - require.Contains(t, "waitgroup result: 42", stdErrBuf.String()) - require.Contains(t, "channels result: 1234", stdErrBuf.String()) - require.Equal(t, "", stdErrBuf.String(), "should not print any errors") -} + for _, test := range cases { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + state, _ := testutil.LoadELFProgram(t, testutil.ProgramPath(test.programName), CreateInitialState, false) + oracle := testutil.StaticOracle(t, []byte{}) + var stdOutBuf, stdErrBuf bytes.Buffer + us := NewInstrumentedState(state, oracle, io.MultiWriter(&stdOutBuf, os.Stdout), io.MultiWriter(&stdErrBuf, os.Stderr), testutil.CreateLogger(), nil) + for i := 0; i < 5_000_000; i++ { + if us.GetState().GetExited() { + break + } + _, err := us.Step(false) + require.NoError(t, err) + } + t.Logf("Completed in %d steps", state.Step) + + require.True(t, state.Exited, "must complete program") + require.Equal(t, uint8(0), state.ExitCode, "exit with 0") + for _, expected := range test.expectedOutput { + require.Contains(t, stdOutBuf.String(), expected) + } + require.Equal(t, "", stdErrBuf.String(), "should not print any errors") + }) + } +} func TestInstrumentedState_Alloc(t *testing.T) { if os.Getenv("SKIP_SLOW_TESTS") == "true" { t.Skip("Skipping slow test because SKIP_SLOW_TESTS is enabled") diff --git a/cannon/testdata/example/multithreaded/go.mod b/cannon/testdata/example/mt-cond/go.mod similarity index 58% rename from cannon/testdata/example/multithreaded/go.mod rename to cannon/testdata/example/mt-cond/go.mod index e1bdb77a9aff6..d6d1853d5af2b 100644 --- a/cannon/testdata/example/multithreaded/go.mod +++ b/cannon/testdata/example/mt-cond/go.mod @@ -1,4 +1,4 @@ -module multithreaded +module cond go 1.22 diff --git a/cannon/testdata/example/mt-cond/main.go b/cannon/testdata/example/mt-cond/main.go new file mode 100644 index 0000000000000..2b584cec999b3 --- /dev/null +++ b/cannon/testdata/example/mt-cond/main.go @@ -0,0 +1,302 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/cond_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "reflect" + "runtime" + "sync" +) + +func main() { + TestCondSignal() + TestCondSignalGenerations() + TestCondBroadcast() + TestRace() + TestCondSignalStealing() + TestCondCopy() + + fmt.Println("Cond test passed") +} + +func TestCondSignal() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 2 + running := make(chan bool, n) + awake := make(chan bool, n) + for i := 0; i < n; i++ { + go func() { + m.Lock() + running <- true + c.Wait() + awake <- true + m.Unlock() + }() + } + for i := 0; i < n; i++ { + <-running // Wait for everyone to run. + } + for n > 0 { + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "goroutine not asleep") + os.Exit(1) + default: + } + m.Lock() + c.Signal() + m.Unlock() + <-awake // Will deadlock if no goroutine wakes up + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "too many goroutines awake") + os.Exit(1) + default: + } + n-- + } + c.Signal() +} + +func TestCondSignalGenerations() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 100 + running := make(chan bool, n) + awake := make(chan int, n) + for i := 0; i < n; i++ { + go func(i int) { + m.Lock() + running <- true + c.Wait() + awake <- i + m.Unlock() + }(i) + if i > 0 { + a := <-awake + if a != i-1 { + _, _ = fmt.Fprintf(os.Stderr, "wrong goroutine woke up: want %d, got %d\n", i-1, a) + os.Exit(1) + } + } + <-running + m.Lock() + c.Signal() + m.Unlock() + } +} + +func TestCondBroadcast() { + var m sync.Mutex + c := sync.NewCond(&m) + n := 5 + running := make(chan int, n) + awake := make(chan int, n) + exit := false + for i := 0; i < n; i++ { + go func(g int) { + m.Lock() + for !exit { + running <- g + c.Wait() + awake <- g + } + m.Unlock() + }(i) + } + for i := 0; i < n; i++ { + for i := 0; i < n; i++ { + <-running // Will deadlock unless n are running. + } + if i == n-1 { + m.Lock() + exit = true + m.Unlock() + } + select { + case <-awake: + _, _ = fmt.Fprintln(os.Stderr, "goroutine not asleep") + os.Exit(1) + default: + } + m.Lock() + c.Broadcast() + m.Unlock() + seen := make([]bool, n) + for i := 0; i < n; i++ { + g := <-awake + if seen[g] { + _, _ = fmt.Fprintln(os.Stderr, "goroutine woke up twice") + os.Exit(1) + } + seen[g] = true + } + } + select { + case <-running: + _, _ = fmt.Fprintln(os.Stderr, "goroutine still running") + os.Exit(1) + default: + } + c.Broadcast() +} + +func TestRace() { + x := 0 + c := sync.NewCond(&sync.Mutex{}) + done := make(chan bool) + go func() { + c.L.Lock() + x = 1 + c.Wait() + if x != 2 { + _, _ = fmt.Fprintln(os.Stderr, "want 2") + os.Exit(1) + } + x = 3 + c.Signal() + c.L.Unlock() + done <- true + }() + go func() { + c.L.Lock() + for { + if x == 1 { + x = 2 + c.Signal() + break + } + c.L.Unlock() + runtime.Gosched() + c.L.Lock() + } + c.L.Unlock() + done <- true + }() + go func() { + c.L.Lock() + for { + if x == 2 { + c.Wait() + if x != 3 { + _, _ = fmt.Fprintln(os.Stderr, "want 3") + os.Exit(1) + } + break + } + if x == 3 { + break + } + c.L.Unlock() + runtime.Gosched() + c.L.Lock() + } + c.L.Unlock() + done <- true + }() + <-done + <-done + <-done +} + +func TestCondSignalStealing() { + for iters := 0; iters < 5; iters++ { + var m sync.Mutex + cond := sync.NewCond(&m) + + // Start a waiter. + ch := make(chan struct{}) + go func() { + m.Lock() + ch <- struct{}{} + cond.Wait() + m.Unlock() + + ch <- struct{}{} + }() + + <-ch + m.Lock() + m.Unlock() + + // We know that the waiter is in the cond.Wait() call because we + // synchronized with it, then acquired/released the mutex it was + // holding when we synchronized. + // + // Start two goroutines that will race: one will broadcast on + // the cond var, the other will wait on it. + // + // The new waiter may or may not get notified, but the first one + // has to be notified. + done := false + go func() { + cond.Broadcast() + }() + + go func() { + m.Lock() + for !done { + cond.Wait() + } + m.Unlock() + }() + + // Check that the first waiter does get signaled. + <-ch + + // Release the second waiter in case it didn't get the + // broadcast. + m.Lock() + done = true + m.Unlock() + cond.Broadcast() + } +} + +func TestCondCopy() { + defer func() { + err := recover() + if err == nil || err.(string) != "sync.Cond is copied" { + _, _ = fmt.Fprintf(os.Stderr, "got %v, expect sync.Cond is copied", err) + os.Exit(1) + } + }() + c := sync.Cond{L: &sync.Mutex{}} + c.Signal() + var c2 sync.Cond + reflect.ValueOf(&c2).Elem().Set(reflect.ValueOf(&c).Elem()) // c2 := c, hidden from vet + c2.Signal() +} diff --git a/cannon/testdata/example/mt-map/go.mod b/cannon/testdata/example/mt-map/go.mod new file mode 100644 index 0000000000000..1d7b890f7f1ad --- /dev/null +++ b/cannon/testdata/example/mt-map/go.mod @@ -0,0 +1,5 @@ +module map + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-map/main.go b/cannon/testdata/example/mt-map/main.go new file mode 100644 index 0000000000000..085319b88d87e --- /dev/null +++ b/cannon/testdata/example/mt-map/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + var m sync.Map + + m.Store("hello", "world") + m.Store("foo", "bar") + m.Store("baz", "qux") + + m.Delete("foo") + m.Load("baz") + + go func() { + m.CompareAndDelete("hello", "world") + m.LoadAndDelete("baz") + }() + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + m.Load("hello") + m.Load("baz") + m.Range(func(k, v interface{}) bool { + m.Load("hello") + m.Load("baz") + return true + }) + m.CompareAndSwap("hello", "world", "Go") + m.LoadOrStore("hello", "world") + wg.Done() + }() + } + + wg.Wait() + + fmt.Println("Map test passed") +} diff --git a/cannon/testdata/example/mt-mutex/go.mod b/cannon/testdata/example/mt-mutex/go.mod new file mode 100644 index 0000000000000..368cf9be0ae5d --- /dev/null +++ b/cannon/testdata/example/mt-mutex/go.mod @@ -0,0 +1,5 @@ +module mutex + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-mutex/main.go b/cannon/testdata/example/mt-mutex/main.go new file mode 100644 index 0000000000000..4f96e5d2308df --- /dev/null +++ b/cannon/testdata/example/mt-mutex/main.go @@ -0,0 +1,82 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/mutex_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "sync" +) + +func main() { + TestMutex() +} + +func TestMutex() { + m := new(sync.Mutex) + + m.Lock() + if m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex locked") + os.Exit(1) + } + m.Unlock() + if !m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock failed with mutex unlocked") + os.Exit(1) + } + m.Unlock() + + c := make(chan bool) + for i := 0; i < 10; i++ { + go HammerMutex(m, 1000, c) + } + for i := 0; i < 10; i++ { + <-c + } + fmt.Println("Mutex test passed") +} + +func HammerMutex(m *sync.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 +} diff --git a/cannon/testdata/example/mt-once/go.mod b/cannon/testdata/example/mt-once/go.mod new file mode 100644 index 0000000000000..7595e1de483fa --- /dev/null +++ b/cannon/testdata/example/mt-once/go.mod @@ -0,0 +1,5 @@ +module once + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-once/main.go b/cannon/testdata/example/mt-once/main.go new file mode 100644 index 0000000000000..3be753e2f702e --- /dev/null +++ b/cannon/testdata/example/mt-once/main.go @@ -0,0 +1,98 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/once_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "sync" +) + +func main() { + TestOnce() + TestOncePanic() + + fmt.Println("Once test passed") +} + +type one int + +func (o *one) Increment() { + *o++ +} + +func run(once *sync.Once, o *one, c chan bool) { + once.Do(func() { o.Increment() }) + if v := *o; v != 1 { + _, _ = fmt.Fprintf(os.Stderr, "once failed inside run: %d is not 1\n", v) + os.Exit(1) + } + c <- true +} + +func TestOnce() { + o := new(one) + once := new(sync.Once) + c := make(chan bool) + const N = 10 + for i := 0; i < N; i++ { + go run(once, o, c) + } + for i := 0; i < N; i++ { + <-c + } + if *o != 1 { + _, _ = fmt.Fprintf(os.Stderr, "once failed outside run: %d is not 1\n", *o) + os.Exit(1) + } +} + +func TestOncePanic() { + var once sync.Once + func() { + defer func() { + if r := recover(); r == nil { + _, _ = fmt.Fprintf(os.Stderr, "Once.Do did not panic") + os.Exit(1) + } + }() + once.Do(func() { + panic("failed") + }) + }() + + once.Do(func() { + _, _ = fmt.Fprintf(os.Stderr, "Once.Do called twice") + os.Exit(1) + }) +} diff --git a/cannon/testdata/example/mt-pool/go.mod b/cannon/testdata/example/mt-pool/go.mod new file mode 100644 index 0000000000000..f57bdf9b81e0b --- /dev/null +++ b/cannon/testdata/example/mt-pool/go.mod @@ -0,0 +1,5 @@ +module pool + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-pool/main.go b/cannon/testdata/example/mt-pool/main.go new file mode 100644 index 0000000000000..267710f3ea3a2 --- /dev/null +++ b/cannon/testdata/example/mt-pool/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "sync" +) + +func main() { + var x sync.Pool + + x.Put(1) + x.Put(2) + + // try some concurrency! + var wg sync.WaitGroup + wg.Add(2) + go func() { + x.Put(3) + wg.Done() + }() + go func() { + x.Put(4) + wg.Done() + }() + + wg.Wait() + + wg.Add(4) + for i := 0; i < 4; i++ { + go func() { + x.Get() + wg.Done() + }() + } + wg.Wait() + + fmt.Println("Pool test passed") +} diff --git a/cannon/testdata/example/mt-rwmutex/go.mod b/cannon/testdata/example/mt-rwmutex/go.mod new file mode 100644 index 0000000000000..a0a433e911990 --- /dev/null +++ b/cannon/testdata/example/mt-rwmutex/go.mod @@ -0,0 +1,5 @@ +module rwmutex + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/mt-rwmutex/main.go b/cannon/testdata/example/mt-rwmutex/main.go new file mode 100644 index 0000000000000..8553bba75ef4a --- /dev/null +++ b/cannon/testdata/example/mt-rwmutex/main.go @@ -0,0 +1,226 @@ +// Portions of this code are derived from code written by The Go Authors. +// See original source: https://github.com/golang/go/blob/400433af3660905ecaceaf19ddad3e6c24b141df/src/sync/rwmutex_test.go +// +// --- Original License Notice --- +// +// Copyright 2009 The Go Authors. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "fmt" + "os" + "runtime" + "sync" + "sync/atomic" +) + +func main() { + TestParallelReaders() + TestRLocker() + TestRWMutex() + + fmt.Println("RWMutex test passed") +} + +func parallelReader(m *sync.RWMutex, clocked, cunlock, cdone chan bool) { + m.RLock() + clocked <- true + <-cunlock + m.RUnlock() + cdone <- true +} + +func doTestParallelReaders(numReaders, gomaxprocs int) { + runtime.GOMAXPROCS(gomaxprocs) + var m sync.RWMutex + clocked := make(chan bool) + cunlock := make(chan bool) + cdone := make(chan bool) + for i := 0; i < numReaders; i++ { + go parallelReader(&m, clocked, cunlock, cdone) + } + // Wait for all parallel RLock()s to succeed. + for i := 0; i < numReaders; i++ { + <-clocked + } + for i := 0; i < numReaders; i++ { + cunlock <- true + } + // Wait for the goroutines to finish. + for i := 0; i < numReaders; i++ { + <-cdone + } +} + +func TestParallelReaders() { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + doTestParallelReaders(1, 4) + doTestParallelReaders(3, 4) + doTestParallelReaders(4, 2) +} + +func reader(rwm *sync.RWMutex, num_iterations int, activity *int32, cdone chan bool) { + for i := 0; i < num_iterations; i++ { + rwm.RLock() + n := atomic.AddInt32(activity, 1) + if n < 1 || n >= 10000 { + rwm.RUnlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -1) + rwm.RUnlock() + } + cdone <- true +} + +func writer(rwm *sync.RWMutex, num_iterations int, activity *int32, cdone chan bool) { + for i := 0; i < num_iterations; i++ { + rwm.Lock() + n := atomic.AddInt32(activity, 10000) + if n != 10000 { + rwm.Unlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -10000) + rwm.Unlock() + } + cdone <- true +} + +func HammerRWMutex(gomaxprocs, numReaders, num_iterations int) { + runtime.GOMAXPROCS(gomaxprocs) + // Number of active readers + 10000 * number of active writers. + var activity int32 + var rwm sync.RWMutex + cdone := make(chan bool) + go writer(&rwm, num_iterations, &activity, cdone) + var i int + for i = 0; i < numReaders/2; i++ { + go reader(&rwm, num_iterations, &activity, cdone) + } + go writer(&rwm, num_iterations, &activity, cdone) + for ; i < numReaders; i++ { + go reader(&rwm, num_iterations, &activity, cdone) + } + // Wait for the 2 writers and all readers to finish. + for i := 0; i < 2+numReaders; i++ { + <-cdone + } +} + +func TestRWMutex() { + var m sync.RWMutex + + m.Lock() + if m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex locked") + os.Exit(1) + } + if m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock succeeded with mutex locked") + os.Exit(1) + } + m.Unlock() + + if !m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock failed with mutex unlocked") + os.Exit(1) + } + m.Unlock() + + if !m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock failed with mutex unlocked") + os.Exit(1) + } + if !m.TryRLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryRLock failed with mutex unlocked") + os.Exit(1) + } + if m.TryLock() { + _, _ = fmt.Fprintln(os.Stderr, "TryLock succeeded with mutex rlocked") + os.Exit(1) + } + m.RUnlock() + m.RUnlock() + + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + n := 5 + + HammerRWMutex(1, 1, n) + HammerRWMutex(1, 3, n) + HammerRWMutex(1, 10, n) + HammerRWMutex(4, 1, n) + HammerRWMutex(4, 3, n) + HammerRWMutex(4, 10, n) + HammerRWMutex(10, 1, n) + HammerRWMutex(10, 3, n) + HammerRWMutex(10, 10, n) + HammerRWMutex(10, 5, n) +} + +func TestRLocker() { + var wl sync.RWMutex + var rl sync.Locker + wlocked := make(chan bool, 1) + rlocked := make(chan bool, 1) + rl = wl.RLocker() + n := 10 + go func() { + for i := 0; i < n; i++ { + rl.Lock() + rl.Lock() + rlocked <- true + wl.Lock() + wlocked <- true + } + }() + for i := 0; i < n; i++ { + <-rlocked + rl.Unlock() + select { + case <-wlocked: + _, _ = fmt.Fprintln(os.Stderr, "RLocker() didn't read-lock it") + os.Exit(1) + default: + } + rl.Unlock() + <-wlocked + select { + case <-rlocked: + _, _ = fmt.Fprintln(os.Stderr, "RLocker() didn't respect the write lock") + os.Exit(1) + default: + } + wl.Unlock() + } +} diff --git a/cannon/testdata/example/mt-wg/go.mod b/cannon/testdata/example/mt-wg/go.mod new file mode 100644 index 0000000000000..c8a518a535b5a --- /dev/null +++ b/cannon/testdata/example/mt-wg/go.mod @@ -0,0 +1,5 @@ +module wg + +go 1.22 + +toolchain go1.22.0 diff --git a/cannon/testdata/example/multithreaded/main.go b/cannon/testdata/example/mt-wg/main.go similarity index 79% rename from cannon/testdata/example/multithreaded/main.go rename to cannon/testdata/example/mt-wg/main.go index bc871f56e5248..841125c6b9252 100644 --- a/cannon/testdata/example/multithreaded/main.go +++ b/cannon/testdata/example/mt-wg/main.go @@ -2,8 +2,6 @@ package main import ( "fmt" - "os" - "runtime" "sync" "sync/atomic" ) @@ -39,8 +37,4 @@ func main() { a <- 1234 out := <-c fmt.Printf("channels result: %d\n", out) - - // try a GC! (the runtime might not have run one yet) - runtime.GC() - _, _ = os.Stdout.Write([]byte("GC complete!\n")) }