Skip to content

Commit 51225f6

Browse files
nsrip-ddgopherbot
authored andcommitted
runtime: record parent goroutine ID, and print it in stack traces
Fixes #38651 Change-Id: Id46d684ee80e208c018791a06c26f304670ed159 Reviewed-on: https://go-review.googlesource.com/c/go/+/435337 Run-TryBot: Nick Ripley <[email protected]> Reviewed-by: Ethan Reesor <[email protected]> Auto-Submit: Michael Pratt <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 81eda3a commit 51225f6

File tree

7 files changed

+44
-6
lines changed

7 files changed

+44
-6
lines changed

src/runtime/export_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,10 @@ func Getg() *G {
540540
return getg()
541541
}
542542

543+
func Goid() uint64 {
544+
return getg().goid
545+
}
546+
543547
func GIsWaitingOnMutex(gp *G) bool {
544548
return readgstatus(gp) == _Gwaiting && gp.waitreason.isMutexWait()
545549
}

src/runtime/proc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4283,6 +4283,7 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
42834283
newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
42844284
newg.sched.g = guintptr(unsafe.Pointer(newg))
42854285
gostartcallfn(&newg.sched, fn)
4286+
newg.parentGoid = callergp.goid
42864287
newg.gopc = callerpc
42874288
newg.ancestors = saveAncestors(callergp)
42884289
newg.startpc = fn.fn

src/runtime/proc_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,10 @@ func TestNumGoroutine(t *testing.T) {
415415
n := runtime.NumGoroutine()
416416
buf = buf[:runtime.Stack(buf, true)]
417417

418-
nstk := strings.Count(string(buf), "goroutine ")
418+
// To avoid double-counting "goroutine" in "goroutine $m [running]:"
419+
// and "created by $func in goroutine $n", remove the latter
420+
output := strings.ReplaceAll(string(buf), "in goroutine", "")
421+
nstk := strings.Count(output, "goroutine ")
419422
if n == nstk {
420423
break
421424
}

src/runtime/runtime2.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ type g struct {
479479
sigcode0 uintptr
480480
sigcode1 uintptr
481481
sigpc uintptr
482+
parentGoid uint64 // goid of goroutine that created this goroutine
482483
gopc uintptr // pc of go statement that created this goroutine
483484
ancestors *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
484485
startpc uintptr // pc of goroutine function

src/runtime/sizeof_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func TestSizeof(t *testing.T) {
2121
_32bit uintptr // size on 32bit platforms
2222
_64bit uintptr // size on 64bit platforms
2323
}{
24-
{runtime.G{}, 240, 392}, // g, but exported for testing
24+
{runtime.G{}, 248, 400}, // g, but exported for testing
2525
{runtime.Sudog{}, 56, 88}, // sudog, but exported for testing
2626
}
2727

src/runtime/traceback.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -701,12 +701,16 @@ func printcreatedby(gp *g) {
701701
pc := gp.gopc
702702
f := findfunc(pc)
703703
if f.valid() && showframe(f, gp, false, funcID_normal, funcID_normal) && gp.goid != 1 {
704-
printcreatedby1(f, pc)
704+
printcreatedby1(f, pc, gp.parentGoid)
705705
}
706706
}
707707

708-
func printcreatedby1(f funcInfo, pc uintptr) {
709-
print("created by ", funcname(f), "\n")
708+
func printcreatedby1(f funcInfo, pc uintptr, goid uint64) {
709+
print("created by ", funcname(f))
710+
if goid != 0 {
711+
print(" in goroutine ", goid)
712+
}
713+
print("\n")
710714
tracepc := pc // back up to CALL instruction for funcline.
711715
if pc > f.entry() {
712716
tracepc -= sys.PCQuantum
@@ -806,7 +810,9 @@ func printAncestorTraceback(ancestor ancestorInfo) {
806810
// Show what created goroutine, except main goroutine (goid 1).
807811
f := findfunc(ancestor.gopc)
808812
if f.valid() && showfuncinfo(f, false, funcID_normal, funcID_normal) && ancestor.goid != 1 {
809-
printcreatedby1(f, ancestor.gopc)
813+
// In ancestor mode, we'll already print the goroutine ancestor.
814+
// Pass 0 for the goid parameter so we don't print it again.
815+
printcreatedby1(f, ancestor.gopc, 0)
810816
}
811817
}
812818

src/runtime/traceback_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ package runtime_test
66

77
import (
88
"bytes"
9+
"fmt"
910
"internal/abi"
1011
"internal/testenv"
1112
"runtime"
13+
"strings"
14+
"sync"
1215
"testing"
1316
)
1417

@@ -420,3 +423,23 @@ func testTracebackArgs11b(a, b, c, d int32) int {
420423
func poisonStack() [20]int {
421424
return [20]int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}
422425
}
426+
427+
func TestTracebackParentChildGoroutines(t *testing.T) {
428+
parent := fmt.Sprintf("goroutine %d", runtime.Goid())
429+
var wg sync.WaitGroup
430+
wg.Add(1)
431+
go func() {
432+
defer wg.Done()
433+
buf := make([]byte, 1<<10)
434+
// We collect the stack only for this goroutine (by passing
435+
// false to runtime.Stack). We expect to see the current
436+
// goroutine ID, and the parent goroutine ID in a message like
437+
// "created by ... in goroutine N".
438+
stack := string(buf[:runtime.Stack(buf, false)])
439+
child := fmt.Sprintf("goroutine %d", runtime.Goid())
440+
if !strings.Contains(stack, parent) || !strings.Contains(stack, child) {
441+
t.Errorf("did not see parent (%s) and child (%s) IDs in stack, got %s", parent, child, stack)
442+
}
443+
}()
444+
wg.Wait()
445+
}

0 commit comments

Comments
 (0)