Skip to content

Commit e33e476

Browse files
committed
syscall: disable cpu profiling around fork
Fixes #5517. Fixes #5659. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/12183044
1 parent 9707f26 commit e33e476

File tree

4 files changed

+74
-4
lines changed

4 files changed

+74
-4
lines changed

src/pkg/runtime/pprof/pprof_test.go

+30-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// See issue 5659.
6-
// +build !race
7-
85
package pprof_test
96

107
import (
@@ -145,6 +142,36 @@ func testCPUProfile(t *testing.T, need []string, f func()) {
145142
}
146143
}
147144

145+
func TestCPUProfileWithFork(t *testing.T) {
146+
// Fork can hang if preempted with signals frequently enough (see issue 5517).
147+
// Ensure that we do not do this.
148+
heap := 1 << 30
149+
if testing.Short() {
150+
heap = 100 << 20
151+
}
152+
// This makes fork slower.
153+
garbage := make([]byte, heap)
154+
// Need to touch the slice, otherwise it won't be paged in.
155+
done := make(chan bool)
156+
go func() {
157+
for i := range garbage {
158+
garbage[i] = 42
159+
}
160+
done <- true
161+
}()
162+
<-done
163+
164+
var prof bytes.Buffer
165+
if err := StartCPUProfile(&prof); err != nil {
166+
t.Fatal(err)
167+
}
168+
defer StopCPUProfile()
169+
170+
for i := 0; i < 10; i++ {
171+
exec.Command("go").CombinedOutput()
172+
}
173+
}
174+
148175
// Operating systems that are expected to fail the tests. See issue 6047.
149176
var badOS = map[string]bool{
150177
"darwin": true,

src/pkg/runtime/proc.c

+30-1
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,29 @@ exitsyscall0(G *gp)
16101610
schedule(); // Never returns.
16111611
}
16121612

1613+
// Called from syscall package before fork.
1614+
void
1615+
syscall·runtime_BeforeFork(void)
1616+
{
1617+
// Fork can hang if preempted with signals frequently enough (see issue 5517).
1618+
// Ensure that we stay on the same M where we disable profiling.
1619+
m->locks++;
1620+
if(m->profilehz != 0)
1621+
runtime·resetcpuprofiler(0);
1622+
}
1623+
1624+
// Called from syscall package after fork in parent.
1625+
void
1626+
syscall·runtime_AfterFork(void)
1627+
{
1628+
int32 hz;
1629+
1630+
hz = runtime·sched.profilehz;
1631+
if(hz != 0)
1632+
runtime·resetcpuprofiler(hz);
1633+
m->locks--;
1634+
}
1635+
16131636
// Hook used by runtime·malg to call runtime·stackalloc on the
16141637
// scheduler stack. This exists because runtime·stackalloc insists
16151638
// on being called on the scheduler stack, to avoid trying to grow
@@ -2002,7 +2025,11 @@ runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
20022025
if(fn == nil)
20032026
hz = 0;
20042027

2005-
// Stop profiler on this cpu so that it is safe to lock prof.
2028+
// Disable preemption, otherwise we can be rescheduled to another thread
2029+
// that has profiling enabled.
2030+
m->locks++;
2031+
2032+
// Stop profiler on this thread so that it is safe to lock prof.
20062033
// if a profiling signal came in while we had prof locked,
20072034
// it would deadlock.
20082035
runtime·resetcpuprofiler(0);
@@ -2017,6 +2044,8 @@ runtime·setcpuprofilerate(void (*fn)(uintptr*, int32), int32 hz)
20172044

20182045
if(hz != 0)
20192046
runtime·resetcpuprofiler(hz);
2047+
2048+
m->locks--;
20202049
}
20212050

20222051
// Change number of processors. The world is stopped, sched is locked.

src/pkg/syscall/exec_bsd.go

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ type SysProcAttr struct {
2121
Noctty bool // Detach fd 0 from controlling terminal
2222
}
2323

24+
// Implemented in runtime package.
25+
func runtime_BeforeFork()
26+
func runtime_AfterFork()
27+
2428
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
2529
// If a dup or exec fails, write the errno error to pipe.
2630
// (Pipe is close-on-exec so if exec succeeds, it will be closed.)
@@ -57,8 +61,10 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
5761

5862
// About to call fork.
5963
// No more allocation or calls of non-assembly functions.
64+
runtime_BeforeFork()
6065
r1, r2, err1 = RawSyscall(SYS_FORK, 0, 0, 0)
6166
if err1 != 0 {
67+
runtime_AfterFork()
6268
return 0, err1
6369
}
6470

@@ -72,6 +78,7 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
7278

7379
if r1 != 0 {
7480
// parent; return PID
81+
runtime_AfterFork()
7582
return int(r1), 0
7683
}
7784

src/pkg/syscall/exec_linux.go

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ type SysProcAttr struct {
2222
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
2323
}
2424

25+
// Implemented in runtime package.
26+
func runtime_BeforeFork()
27+
func runtime_AfterFork()
28+
2529
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
2630
// If a dup or exec fails, write the errno error to pipe.
2731
// (Pipe is close-on-exec so if exec succeeds, it will be closed.)
@@ -56,13 +60,16 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
5660

5761
// About to call fork.
5862
// No more allocation or calls of non-assembly functions.
63+
runtime_BeforeFork()
5964
r1, _, err1 = RawSyscall(SYS_FORK, 0, 0, 0)
6065
if err1 != 0 {
66+
runtime_AfterFork()
6167
return 0, err1
6268
}
6369

6470
if r1 != 0 {
6571
// parent; return PID
72+
runtime_AfterFork()
6673
return int(r1), 0
6774
}
6875

0 commit comments

Comments
 (0)