]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: regression test for issue 50936
authorMichael Pratt <mpratt@google.com>
Mon, 31 Jan 2022 21:37:40 +0000 (16:37 -0500)
committerMichael Pratt <mpratt@google.com>
Thu, 3 Feb 2022 16:58:29 +0000 (16:58 +0000)
Add a regression test for issue 50936 which coerces the runtime into
frequent execution of the cgocall dropg/execute curg assignment race by
making many concurrent cgo calls eligible for P retake by sysmon. This
results in no P during exitsyscall, at which point they will update curg
and will crash if SIGPROF arrives in between updating mp.curg and
mp.curg.m.

This test is conceptually similar to the basic cgo callback test in
aprof.go but with additional concurrency and a sleep in C.

On my machine this test fails ~5% of the time prior to CL 382079.

For #50936.

Change-Id: I21b6c7f2594f9a615a64580ef70a88b692505678
Reviewed-on: https://go-review.googlesource.com/c/go/+/382244
Trust: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/runtime/crash_cgo_test.go
src/runtime/testdata/testprogcgo/aprof.go
src/runtime/testdata/testprogcgo/pprof_callback.go [new file with mode: 0644]

index dc8f6a7148517ffa4cfd8df263e512b1756457a6..8c250f72d658e8c50d0ceb0aead3bddd322ba155 100644 (file)
@@ -216,6 +216,19 @@ func TestCgoCCodeSIGPROF(t *testing.T) {
        }
 }
 
+func TestCgoPprofCallback(t *testing.T) {
+       t.Parallel()
+       switch runtime.GOOS {
+       case "windows", "plan9":
+               t.Skipf("skipping cgo pprof callback test on %s", runtime.GOOS)
+       }
+       got := runTestProg(t, "testprogcgo", "CgoPprofCallback")
+       want := "OK\n"
+       if got != want {
+               t.Errorf("expected %q got %v", want, got)
+       }
+}
+
 func TestCgoCrashTraceback(t *testing.T) {
        t.Parallel()
        switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform {
index 44a15b08650377adb509b61118081d79a3d26c05..c70d6333bbff9008a47797981f3b1a5d3a513354 100644 (file)
@@ -7,8 +7,11 @@ package main
 // Test that SIGPROF received in C code does not crash the process
 // looking for the C code's func pointer.
 
-// The test fails when the function is the first C function.
-// The exported functions are the first C functions, so we use that.
+// This is a regression test for issue 14599, where profiling fails when the
+// function is the first C function. Exported functions are the first C
+// functions, so we use an exported function. Exported functions are created in
+// lexigraphical order of source files, so this file is named aprof.go to
+// ensure its function is first.
 
 // extern void CallGoNop();
 import "C"
diff --git a/src/runtime/testdata/testprogcgo/pprof_callback.go b/src/runtime/testdata/testprogcgo/pprof_callback.go
new file mode 100644 (file)
index 0000000..e345643
--- /dev/null
@@ -0,0 +1,89 @@
+// Copyright 2022 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.
+
+//go:build !plan9 && !windows
+
+package main
+
+// Make many C-to-Go callback while collecting a CPU profile.
+//
+// This is a regression test for issue 50936.
+
+/*
+#include <unistd.h>
+
+void goCallbackPprof();
+
+static void callGo() {
+       // Spent >20us in C so this thread is eligible for sysmon to retake its
+       // P.
+       usleep(50);
+       goCallbackPprof();
+}
+*/
+import "C"
+
+import (
+       "fmt"
+       "os"
+       "runtime/pprof"
+       "runtime"
+       "time"
+)
+
+func init() {
+       register("CgoPprofCallback", CgoPprofCallback)
+}
+
+//export goCallbackPprof
+func goCallbackPprof() {
+       // No-op. We want to stress the cgocall and cgocallback internals,
+       // landing as many pprof signals there as possible.
+}
+
+func CgoPprofCallback() {
+       // Issue 50936 was a crash in the SIGPROF handler when the signal
+       // arrived during the exitsyscall following a cgocall(back) in dropg or
+       // execute, when updating mp.curg.
+       //
+       // These are reachable only when exitsyscall finds no P available. Thus
+       // we make C calls from significantly more Gs than there are available
+       // Ps. Lots of runnable work combined with >20us spent in callGo makes
+       // it possible for sysmon to retake Ps, forcing C calls to go down the
+       // desired exitsyscall path.
+       //
+       // High GOMAXPROCS is used to increase opportunities for failure on
+       // high CPU machines.
+       const (
+               P = 16
+               G = 64
+       )
+       runtime.GOMAXPROCS(P)
+
+       f, err := os.CreateTemp("", "prof")
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(2)
+       }
+       defer f.Close()
+
+       if err := pprof.StartCPUProfile(f); err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(2)
+       }
+
+       for i := 0; i < G; i++ {
+               go func() {
+                       for {
+                               C.callGo()
+                       }
+               }()
+       }
+
+       time.Sleep(time.Second)
+
+       pprof.StopCPUProfile()
+
+       fmt.Println("OK")
+}