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
7package 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
16void goCallbackPprof();
17
18static 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*/
25import "C"
26
27import (
28	"fmt"
29	"os"
30	"runtime"
31	"runtime/pprof"
32	"time"
33)
34
35func init() {
36	register("CgoPprofCallback", CgoPprofCallback)
37}
38
39//export goCallbackPprof
40func goCallbackPprof() {
41	// No-op. We want to stress the cgocall and cgocallback internals,
42	// landing as many pprof signals there as possible.
43}
44
45func 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}
90