]> Cypherpunks.ru repositories - gostls13.git/commitdiff
runtime: bypass scheduler when doing traceback for goroutine profile
authorCherry Mui <cherryyz@google.com>
Wed, 10 Nov 2021 00:50:47 +0000 (19:50 -0500)
committerCherry Mui <cherryyz@google.com>
Thu, 11 Nov 2021 15:34:02 +0000 (15:34 +0000)
When acquire a goroutine profile, we stop the world then acquire a
stack trace for each goroutine. When cgo traceback is used, the
traceback code may call the cgo traceback function using cgocall.
As the world is stopped, cgocall will be blocked at exitsyscall,
causing a deadlock. Bypass the scheduler (using asmcgocall) to fix
this.

Change-Id: Ic4e596adc3711310b6a983d73786d697ef15dd72
Reviewed-on: https://go-review.googlesource.com/c/go/+/362757
Trust: Cherry Mui <cherryyz@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/runtime/crash_cgo_test.go
src/runtime/mprof.go
src/runtime/testdata/testprogcgo/gprof.go [new file with mode: 0644]
src/runtime/testdata/testprogcgo/gprof_c.c [new file with mode: 0644]

index e6d1742a384a63549e10a8cdb3437b9d96d2c4d7..58c340f8ad3028fd2269fbba9e87ef2675e03566 100644 (file)
@@ -702,3 +702,11 @@ func TestNeedmDeadlock(t *testing.T) {
                t.Fatalf("want %s, got %s\n", want, output)
        }
 }
+
+func TestCgoTracebackGoroutineProfile(t *testing.T) {
+       output := runTestProg(t, "testprogcgo", "GoroutineProfile")
+       want := "OK\n"
+       if output != want {
+               t.Fatalf("want %s, got %s\n", want, output)
+       }
+}
index b4de8f53a919f801b60cd4ac7b550f7aa2cb1770..569c17f0a7cf4b9ee8f5c3ad3e4f9b5eb52338eb 100644 (file)
@@ -805,7 +805,11 @@ func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int
                                // truncated profile than to crash the entire process.
                                return
                        }
-                       saveg(^uintptr(0), ^uintptr(0), gp1, &r[0])
+                       // saveg calls gentraceback, which may call cgo traceback functions.
+                       // The world is stopped, so it cannot use cgocall (which will be
+                       // blocked at exitsyscall). Do it on the system stack so it won't
+                       // call into the schedular (see traceback.go:cgoContextPCs).
+                       systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &r[0]) })
                        if labels != nil {
                                lbl[0] = gp1.labels
                                lbl = lbl[1:]
diff --git a/src/runtime/testdata/testprogcgo/gprof.go b/src/runtime/testdata/testprogcgo/gprof.go
new file mode 100644 (file)
index 0000000..d453b4d
--- /dev/null
@@ -0,0 +1,46 @@
+// Copyright 2021 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
+
+// Test taking a goroutine profile with C traceback.
+
+/*
+// Defined in gprof_c.c.
+void CallGoSleep(void);
+void gprofCgoTraceback(void* parg);
+void gprofCgoContext(void* parg);
+*/
+import "C"
+
+import (
+       "fmt"
+       "io"
+       "runtime"
+       "runtime/pprof"
+       "time"
+       "unsafe"
+)
+
+func init() {
+       register("GoroutineProfile", GoroutineProfile)
+}
+
+func GoroutineProfile() {
+       runtime.SetCgoTraceback(0, unsafe.Pointer(C.gprofCgoTraceback), unsafe.Pointer(C.gprofCgoContext), nil)
+
+       go C.CallGoSleep()
+       go C.CallGoSleep()
+       go C.CallGoSleep()
+       time.Sleep(1 * time.Second)
+
+       prof := pprof.Lookup("goroutine")
+       prof.WriteTo(io.Discard, 1)
+       fmt.Println("OK")
+}
+
+//export GoSleep
+func GoSleep() {
+       time.Sleep(time.Hour)
+}
diff --git a/src/runtime/testdata/testprogcgo/gprof_c.c b/src/runtime/testdata/testprogcgo/gprof_c.c
new file mode 100644 (file)
index 0000000..6ddff44
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright 2021 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.
+
+// The C definitions for gprof.go. That file uses //export so
+// it can't put function definitions in the "C" import comment.
+
+#include <stdint.h>
+#include <stdlib.h>
+
+// Functions exported from Go.
+extern void GoSleep();
+
+struct cgoContextArg {
+       uintptr_t context;
+};
+
+void gprofCgoContext(void *arg) {
+       ((struct cgoContextArg*)arg)->context = 1;
+}
+
+void gprofCgoTraceback(void *arg) {
+       // spend some time here so the P is more likely to be retaken.
+       for (volatile int i = 0; i < 123456789; i++);
+}
+
+void CallGoSleep() {
+       GoSleep();
+}