]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: record parent goroutine ID, and print it in stack traces
authorNick Ripley <nick.ripley@datadoghq.com>
Wed, 28 Sep 2022 18:44:56 +0000 (14:44 -0400)
committerGopher Robot <gobot@golang.org>
Tue, 21 Feb 2023 17:35:22 +0000 (17:35 +0000)
Fixes #38651

Change-Id: Id46d684ee80e208c018791a06c26f304670ed159
Reviewed-on: https://go-review.googlesource.com/c/go/+/435337
Run-TryBot: Nick Ripley <nick.ripley@datadoghq.com>
Reviewed-by: Ethan Reesor <ethan.reesor@gmail.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/runtime/export_test.go
src/runtime/proc.go
src/runtime/proc_test.go
src/runtime/runtime2.go
src/runtime/sizeof_test.go
src/runtime/traceback.go
src/runtime/traceback_test.go

index e7476e606b48be2359442d1f5c47ab313739f149..25758972f134a1e86712bcd206d61bda2187a6aa 100644 (file)
@@ -540,6 +540,10 @@ func Getg() *G {
        return getg()
 }
 
+func Goid() uint64 {
+       return getg().goid
+}
+
 func GIsWaitingOnMutex(gp *G) bool {
        return readgstatus(gp) == _Gwaiting && gp.waitreason.isMutexWait()
 }
index d57a31ce45eb9f11277bf3c8a87f28ceaf6fdcf6..aba2e2b27b6cfd16c4f49d579462945aebf9207c 100644 (file)
@@ -4283,6 +4283,7 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
        newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
        newg.sched.g = guintptr(unsafe.Pointer(newg))
        gostartcallfn(&newg.sched, fn)
+       newg.parentGoid = callergp.goid
        newg.gopc = callerpc
        newg.ancestors = saveAncestors(callergp)
        newg.startpc = fn.fn
index f354facc49539c1a94cc2bc76916a872d5ba6860..d240dc44044c8ba81249de92e5b2b266d995588e 100644 (file)
@@ -415,7 +415,10 @@ func TestNumGoroutine(t *testing.T) {
                n := runtime.NumGoroutine()
                buf = buf[:runtime.Stack(buf, true)]
 
-               nstk := strings.Count(string(buf), "goroutine ")
+               // To avoid double-counting "goroutine" in "goroutine $m [running]:"
+               // and "created by $func in goroutine $n", remove the latter
+               output := strings.ReplaceAll(string(buf), "in goroutine", "")
+               nstk := strings.Count(output, "goroutine ")
                if n == nstk {
                        break
                }
index 9381d1e3f70a0942b8a3602af6d258475952988f..044a9a715f5aea62528e6a702c5462da9ad83e82 100644 (file)
@@ -479,6 +479,7 @@ type g struct {
        sigcode0       uintptr
        sigcode1       uintptr
        sigpc          uintptr
+       parentGoid     uint64          // goid of goroutine that created this goroutine
        gopc           uintptr         // pc of go statement that created this goroutine
        ancestors      *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
        startpc        uintptr         // pc of goroutine function
index 9ce0a3afcdca5c246a01fb6175b06b44e6398331..bfb5d6e33e40ba320a8b7141185bf8eb1857ca39 100644 (file)
@@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) {
                _32bit uintptr // size on 32bit platforms
                _64bit uintptr // size on 64bit platforms
        }{
-               {runtime.G{}, 240, 392},   // g, but exported for testing
+               {runtime.G{}, 248, 400},   // g, but exported for testing
                {runtime.Sudog{}, 56, 88}, // sudog, but exported for testing
        }
 
index 37f35d5637edad069b0f254770953cb4db5076e8..677350990190a9c09c6305c6c229bbe70ab32e1e 100644 (file)
@@ -701,12 +701,16 @@ func printcreatedby(gp *g) {
        pc := gp.gopc
        f := findfunc(pc)
        if f.valid() && showframe(f, gp, false, funcID_normal, funcID_normal) && gp.goid != 1 {
-               printcreatedby1(f, pc)
+               printcreatedby1(f, pc, gp.parentGoid)
        }
 }
 
-func printcreatedby1(f funcInfo, pc uintptr) {
-       print("created by ", funcname(f), "\n")
+func printcreatedby1(f funcInfo, pc uintptr, goid uint64) {
+       print("created by ", funcname(f))
+       if goid != 0 {
+               print(" in goroutine ", goid)
+       }
+       print("\n")
        tracepc := pc // back up to CALL instruction for funcline.
        if pc > f.entry() {
                tracepc -= sys.PCQuantum
@@ -806,7 +810,9 @@ func printAncestorTraceback(ancestor ancestorInfo) {
        // Show what created goroutine, except main goroutine (goid 1).
        f := findfunc(ancestor.gopc)
        if f.valid() && showfuncinfo(f, false, funcID_normal, funcID_normal) && ancestor.goid != 1 {
-               printcreatedby1(f, ancestor.gopc)
+               // In ancestor mode, we'll already print the goroutine ancestor.
+               // Pass 0 for the goid parameter so we don't print it again.
+               printcreatedby1(f, ancestor.gopc, 0)
        }
 }
 
index 97eb92103b47d3e373bc83e8a6c35da754335f91..8b19087b9362aacc4623bc5840a57312419c75ee 100644 (file)
@@ -6,9 +6,12 @@ package runtime_test
 
 import (
        "bytes"
+       "fmt"
        "internal/abi"
        "internal/testenv"
        "runtime"
+       "strings"
+       "sync"
        "testing"
 )
 
@@ -420,3 +423,23 @@ func testTracebackArgs11b(a, b, c, d int32) int {
 func poisonStack() [20]int {
        return [20]int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
 }
+
+func TestTracebackParentChildGoroutines(t *testing.T) {
+       parent := fmt.Sprintf("goroutine %d", runtime.Goid())
+       var wg sync.WaitGroup
+       wg.Add(1)
+       go func() {
+               defer wg.Done()
+               buf := make([]byte, 1<<10)
+               // We collect the stack only for this goroutine (by passing
+               // false to runtime.Stack). We expect to see the current
+               // goroutine ID, and the parent goroutine ID in a message like
+               // "created by ... in goroutine N".
+               stack := string(buf[:runtime.Stack(buf, false)])
+               child := fmt.Sprintf("goroutine %d", runtime.Goid())
+               if !strings.Contains(stack, parent) || !strings.Contains(stack, child) {
+                       t.Errorf("did not see parent (%s) and child (%s) IDs in stack, got %s", parent, child, stack)
+               }
+       }()
+       wg.Wait()
+}