1
0
mirror of https://github.com/golang/go synced 2024-11-23 05:10:09 -07:00

runtime: enable/disable SIGPROF if needed when profiling

This ensures that SIGPROF is handled correctly when using
runtime/pprof in a c-archive or c-shared library.

Separate profiler handling into pre-process changes and per-thread
changes. Simplify the Windows code slightly accordingly.

Fixes #18220.

Change-Id: I5060f7084c91ef0bbe797848978bdc527c312777
Reviewed-on: https://go-review.googlesource.com/34018
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
Run-TryBot: Austin Clements <austin@google.com>
This commit is contained in:
Ian Lance Taylor 2016-12-06 20:54:41 -08:00
parent 6a29806e01
commit e24228af25
8 changed files with 152 additions and 14 deletions

View File

@ -557,3 +557,38 @@ func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
}
return false
}
func TestSIGPROF(t *testing.T) {
switch GOOS {
case "windows", "plan9":
t.Skipf("skipping SIGPROF test on %s", GOOS)
}
t.Parallel()
defer func() {
os.Remove("testp6" + exeSuffix)
os.Remove("libgo6.a")
os.Remove("libgo6.h")
}()
cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "libgo6")
cmd.Env = gopathEnv
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
argv := cmdToRun("./testp6")
cmd = exec.Command(argv[0], argv[1:]...)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("%s", out)
t.Fatal(err)
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2016 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.
// Test that using the Go profiler in a C program does not crash.
#include <stddef.h>
#include <sys/time.h>
#include "libgo6.h"
int main(int argc, char **argv) {
struct timeval tvstart, tvnow;
int diff;
gettimeofday(&tvstart, NULL);
go_start_profile();
// Busy wait so we have something to profile.
// If we just sleep the profiling signal will never fire.
while (1) {
gettimeofday(&tvnow, NULL);
diff = (tvnow.tv_sec - tvstart.tv_sec) * 1000 * 1000 + (tvnow.tv_usec - tvstart.tv_usec);
// Profile frequency is 100Hz so we should definitely
// get a signal in 50 milliseconds.
if (diff > 50 * 1000)
break;
}
go_stop_profile();
return 0;
}

View File

@ -0,0 +1,25 @@
// Copyright 2016 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 (
"io/ioutil"
"runtime/pprof"
)
import "C"
//export go_start_profile
func go_start_profile() {
pprof.StartCPUProfile(ioutil.Discard)
}
//export go_stop_profile
func go_stop_profile() {
pprof.StopCPUProfile()
}
func main() {
}

View File

@ -146,7 +146,10 @@ func sigdisable(sig uint32) {
func sigignore(sig uint32) {
}
func resetcpuprofiler(hz int32) {
func setProcessCPUProfiler(hz int32) {
}
func setThreadCPUProfiler(hz int32) {
// TODO: Enable profiling interrupts.
getg().m.profilehz = hz
}

View File

@ -273,7 +273,8 @@ func raisebadsignal(sig uint32) {
func madvise(addr unsafe.Pointer, n uintptr, flags int32) {}
func munmap(addr unsafe.Pointer, n uintptr) {}
func resetcpuprofiler(hz int32) {}
func setProcessCPUProfiler(hz int32) {}
func setThreadCPUProfiler(hz int32) {}
func sigdisable(uint32) {}
func sigenable(uint32) {}
func sigignore(uint32) {}

View File

@ -744,10 +744,7 @@ func profileloop1(param uintptr) uint32 {
}
}
var cpuprofilerlock mutex
func resetcpuprofiler(hz int32) {
lock(&cpuprofilerlock)
func setProcessCPUProfiler(hz int32) {
if profiletimer == 0 {
timer := stdcall3(_CreateWaitableTimerA, 0, 0, 0)
atomic.Storeuintptr(&profiletimer, timer)
@ -755,8 +752,9 @@ func resetcpuprofiler(hz int32) {
stdcall2(_SetThreadPriority, thread, _THREAD_PRIORITY_HIGHEST)
stdcall1(_CloseHandle, thread)
}
unlock(&cpuprofilerlock)
}
func setThreadCPUProfiler(hz int32) {
ms := int32(0)
due := ^int64(^uint64(1 << 63))
if hz > 0 {

View File

@ -1879,7 +1879,7 @@ func execute(gp *g, inheritTime bool) {
// Check whether the profiler needs to be turned on or off.
hz := sched.profilehz
if _g_.m.profilehz != hz {
resetcpuprofiler(hz)
setThreadCPUProfiler(hz)
}
if trace.enabled {
@ -2780,7 +2780,7 @@ func beforefork() {
// Ensure that we stay on the same M where we disable profiling.
gp.m.locks++
if gp.m.profilehz != 0 {
resetcpuprofiler(0)
setThreadCPUProfiler(0)
}
// This function is called before fork in syscall package.
@ -2805,7 +2805,7 @@ func afterfork() {
hz := sched.profilehz
if hz != 0 {
resetcpuprofiler(hz)
setThreadCPUProfiler(hz)
}
gp.m.locks--
}
@ -3439,12 +3439,15 @@ func setcpuprofilerate_m(hz int32) {
// Stop profiler on this thread so that it is safe to lock prof.
// if a profiling signal came in while we had prof locked,
// it would deadlock.
resetcpuprofiler(0)
setThreadCPUProfiler(0)
for !atomic.Cas(&prof.lock, 0, 1) {
osyield()
}
prof.hz = hz
if prof.hz != hz {
setProcessCPUProfiler(hz)
prof.hz = hz
}
atomic.Store(&prof.lock, 0)
lock(&sched.lock)
@ -3452,7 +3455,7 @@ func setcpuprofilerate_m(hz int32) {
unlock(&sched.lock)
if hz != 0 {
resetcpuprofiler(hz)
setThreadCPUProfiler(hz)
}
_g_.m.locks--

View File

@ -138,6 +138,11 @@ func sigenable(sig uint32) {
return
}
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig]
if t.flags&_SigNotify != 0 {
ensureSigM()
@ -158,6 +163,11 @@ func sigdisable(sig uint32) {
return
}
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig]
if t.flags&_SigNotify != 0 {
ensureSigM()
@ -182,6 +192,11 @@ func sigignore(sig uint32) {
return
}
// SIGPROF is handled specially for profiling.
if sig == _SIGPROF {
return
}
t := &sigtable[sig]
if t.flags&_SigNotify != 0 {
atomic.Store(&handlingSig[sig], 0)
@ -189,7 +204,31 @@ func sigignore(sig uint32) {
}
}
func resetcpuprofiler(hz int32) {
// setProcessCPUProfiler is called when the profiling timer changes.
// It is called with prof.lock held. hz is the new timer, and is 0 if
// profiling is being disabled. Enable or disable the signal as
// required for -buildmode=c-archive.
func setProcessCPUProfiler(hz int32) {
if hz != 0 {
// Enable the Go signal handler if not enabled.
if atomic.Cas(&handlingSig[_SIGPROF], 0, 1) {
atomic.Storeuintptr(&fwdSig[_SIGPROF], getsig(_SIGPROF))
setsig(_SIGPROF, funcPC(sighandler))
}
} else {
// If the Go signal handler should be disabled by default,
// disable it if it is enabled.
if !sigInstallGoHandler(_SIGPROF) {
if atomic.Cas(&handlingSig[_SIGPROF], 1, 0) {
setsig(_SIGPROF, atomic.Loaduintptr(&fwdSig[_SIGPROF]))
}
}
}
}
// setThreadCPUProfiler makes any thread-specific changes required to
// implement profiling at a rate of hz.
func setThreadCPUProfiler(hz int32) {
var it itimerval
if hz == 0 {
setitimer(_ITIMER_PROF, &it, nil)