Skip to content

Commit f9b761a

Browse files
committed
runtime: regression test for issue 50936
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 <[email protected]> Run-TryBot: Michael Pratt <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent bec8a10 commit f9b761a

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed

src/runtime/crash_cgo_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,19 @@ func TestCgoCCodeSIGPROF(t *testing.T) {
216216
}
217217
}
218218

219+
func TestCgoPprofCallback(t *testing.T) {
220+
t.Parallel()
221+
switch runtime.GOOS {
222+
case "windows", "plan9":
223+
t.Skipf("skipping cgo pprof callback test on %s", runtime.GOOS)
224+
}
225+
got := runTestProg(t, "testprogcgo", "CgoPprofCallback")
226+
want := "OK\n"
227+
if got != want {
228+
t.Errorf("expected %q got %v", want, got)
229+
}
230+
}
231+
219232
func TestCgoCrashTraceback(t *testing.T) {
220233
t.Parallel()
221234
switch platform := runtime.GOOS + "/" + runtime.GOARCH; platform {

src/runtime/testdata/testprogcgo/aprof.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ package main
77
// Test that SIGPROF received in C code does not crash the process
88
// looking for the C code's func pointer.
99

10-
// The test fails when the function is the first C function.
11-
// The exported functions are the first C functions, so we use that.
10+
// This is a regression test for issue 14599, where profiling fails when the
11+
// function is the first C function. Exported functions are the first C
12+
// functions, so we use an exported function. Exported functions are created in
13+
// lexigraphical order of source files, so this file is named aprof.go to
14+
// ensure its function is first.
1215

1316
// extern void CallGoNop();
1417
import "C"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !plan9 && !windows
6+
7+
package main
8+
9+
// Make many C-to-Go callback while collecting a CPU profile.
10+
//
11+
// This is a regression test for issue 50936.
12+
13+
/*
14+
#include <unistd.h>
15+
16+
void goCallbackPprof();
17+
18+
static void callGo() {
19+
// Spent >20us in C so this thread is eligible for sysmon to retake its
20+
// P.
21+
usleep(50);
22+
goCallbackPprof();
23+
}
24+
*/
25+
import "C"
26+
27+
import (
28+
"fmt"
29+
"os"
30+
"runtime/pprof"
31+
"runtime"
32+
"time"
33+
)
34+
35+
func init() {
36+
register("CgoPprofCallback", CgoPprofCallback)
37+
}
38+
39+
//export goCallbackPprof
40+
func goCallbackPprof() {
41+
// No-op. We want to stress the cgocall and cgocallback internals,
42+
// landing as many pprof signals there as possible.
43+
}
44+
45+
func CgoPprofCallback() {
46+
// Issue 50936 was a crash in the SIGPROF handler when the signal
47+
// arrived during the exitsyscall following a cgocall(back) in dropg or
48+
// execute, when updating mp.curg.
49+
//
50+
// These are reachable only when exitsyscall finds no P available. Thus
51+
// we make C calls from significantly more Gs than there are available
52+
// Ps. Lots of runnable work combined with >20us spent in callGo makes
53+
// it possible for sysmon to retake Ps, forcing C calls to go down the
54+
// desired exitsyscall path.
55+
//
56+
// High GOMAXPROCS is used to increase opportunities for failure on
57+
// high CPU machines.
58+
const (
59+
P = 16
60+
G = 64
61+
)
62+
runtime.GOMAXPROCS(P)
63+
64+
f, err := os.CreateTemp("", "prof")
65+
if err != nil {
66+
fmt.Fprintln(os.Stderr, err)
67+
os.Exit(2)
68+
}
69+
defer f.Close()
70+
71+
if err := pprof.StartCPUProfile(f); err != nil {
72+
fmt.Fprintln(os.Stderr, err)
73+
os.Exit(2)
74+
}
75+
76+
for i := 0; i < G; i++ {
77+
go func() {
78+
for {
79+
C.callGo()
80+
}
81+
}()
82+
}
83+
84+
time.Sleep(time.Second)
85+
86+
pprof.StopCPUProfile()
87+
88+
fmt.Println("OK")
89+
}

0 commit comments

Comments
 (0)