]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: add thread exit plus vgetrandom stress test
authorMichael Pratt <mpratt@google.com>
Thu, 3 Apr 2025 15:16:36 +0000 (15:16 +0000)
committerGopher Robot <gobot@golang.org>
Fri, 4 Apr 2025 13:37:49 +0000 (06:37 -0700)
Add a regression test similar to the reproducer from #73141 to try to
help catch future issues with vgetrandom and thread exit. Though the
test isn't very precise, it just hammers thread exit.

When the test reproduces #73141, it simply crashes with a SIGSEGV and no
output or stack trace, which would be very unfortunate on a builder.
https://go.dev/issue/49165 tracks collecting core dumps from builders,
which would make this more tractable to debug.

For #73141.

Change-Id: I6a6a636c7d7b41e2729ff6ceb30fd7f979aa9978
Reviewed-on: https://go-review.googlesource.com/c/go/+/662636
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Pratt <mpratt@google.com>

src/runtime/proc_test.go
src/runtime/testdata/testprog/lockosthread_linux.go [new file with mode: 0644]

index a930ea707f12ccd3601e2c53af53ddf2631d57e6..3b606f62e4320cc944af19c71b0493d1d7b220b0 100644 (file)
@@ -1026,6 +1026,17 @@ func TestLockOSThreadTemplateThreadRace(t *testing.T) {
        }
 }
 
+func TestLockOSThreadVgetrandom(t *testing.T) {
+       if runtime.GOOS != "linux" {
+               t.Skipf("vgetrandom only relevant on Linux")
+       }
+       output := runTestProg(t, "testprog", "LockOSThreadVgetrandom")
+       want := "OK\n"
+       if output != want {
+               t.Errorf("want %q, got %q", want, output)
+       }
+}
+
 // fakeSyscall emulates a system call.
 //
 //go:nosplit
diff --git a/src/runtime/testdata/testprog/lockosthread_linux.go b/src/runtime/testdata/testprog/lockosthread_linux.go
new file mode 100644 (file)
index 0000000..5e49b43
--- /dev/null
@@ -0,0 +1,71 @@
+// Copyright 2025 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.
+
+package main
+
+import (
+       "internal/syscall/unix"
+       "runtime"
+)
+
+func init() {
+       register("LockOSThreadVgetrandom", LockOSThreadVgetrandom)
+}
+
+var sinkInt int
+
+func LockOSThreadVgetrandom() {
+       // This is a regression test for https://go.dev/issue/73141. When that
+       // reproduces, this crashes with SIGSEGV with no output or stack trace,
+       // and detail only available in a core file.
+       //
+       // Thread exit via mexit cleans up vgetrandom state. Stress test thread
+       // exit + vgetrandom to look for issues by creating lots of threads
+       // that use GetRandom and then exit.
+
+       // Launch at most 100 threads at a time.
+       const parallelism = 100
+       ch := make(chan struct{}, parallelism)
+       for range 100 {
+               ch <- struct{}{}
+       }
+
+       // Create at most 1000 threads to avoid completely exhausting the
+       // system. This test generally reproduces https://go.dev/issue/73141 in
+       // less than 500 iterations.
+       const iterations = 1000
+       for range iterations {
+               <-ch
+               go func() {
+                       defer func() {
+                               ch <- struct{}{}
+                       }()
+
+                       // Exit with LockOSThread held.
+                       runtime.LockOSThread()
+
+                       // Be sure to use GetRandom to initialize vgetrandom state.
+                       b := make([]byte, 1)
+                       _, err := unix.GetRandom(b, 0)
+                       if err != nil {
+                               panic(err)
+                       }
+
+                       // Do some busy-work. It is unclear why this is
+                       // necessary to reproduce. Perhaps to introduce
+                       // interesting scheduling where threads get descheduled
+                       // in the middle of getting or putting vgetrandom
+                       // state.
+                       for range 10 * 1000 * 1000 {
+                               sinkInt = 1
+                       }
+               }()
+       }
+
+       // Wait for all threads to finish.
+       for range parallelism {
+               <-ch
+       }
+       println("OK")
+}