Skip to content

Commit 8c68a1c

Browse files
VladSaiocUbergopherbot
authored andcommitted
runtime,net/http/pprof: goroutine leak detection by using the garbage collector
Proposal #74609 Change-Id: I97a754b128aac1bc5b7b9ab607fcd5bb390058c8 GitHub-Last-Rev: 60f2a19 GitHub-Pull-Request: #74622 Reviewed-on: https://go-review.googlesource.com/c/go/+/688335 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: t hepudds <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> Reviewed-by: Michael Knyszek <[email protected]> Reviewed-by: Carlos Amedee <[email protected]>
1 parent 84db201 commit 8c68a1c

File tree

95 files changed

+10766
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+10766
-89
lines changed

src/cmd/link/internal/loader/loader.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,6 +2450,9 @@ var blockedLinknames = map[string][]string{
24502450
"sync_test.runtime_blockUntilEmptyCleanupQueue": {"sync_test"},
24512451
"time.runtimeIsBubbled": {"time"},
24522452
"unique.runtime_blockUntilEmptyCleanupQueue": {"unique"},
2453+
// Experimental features
2454+
"runtime.goroutineLeakGC": {"runtime/pprof"},
2455+
"runtime.goroutineleakcount": {"runtime/pprof"},
24532456
// Others
24542457
"net.newWindowsFile": {"net"}, // pushed from os
24552458
"testing/synctest.testingSynctestTest": {"testing/synctest"}, // pushed from testing

src/internal/goexperiment/exp_goroutineleakprofile_off.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/goexperiment/exp_goroutineleakprofile_on.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/goexperiment/flags.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,7 @@ type Flags struct {
118118

119119
// SizeSpecializedMalloc enables malloc implementations that are specialized per size class.
120120
SizeSpecializedMalloc bool
121+
122+
// GoroutineLeakProfile enables the collection of goroutine leak profiles.
123+
GoroutineLeakProfile bool
121124
}

src/net/http/pprof/pprof.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import (
7777
"fmt"
7878
"html"
7979
"internal/godebug"
80+
"internal/goexperiment"
8081
"internal/profile"
8182
"io"
8283
"log"
@@ -353,6 +354,7 @@ func collectProfile(p *pprof.Profile) (*profile.Profile, error) {
353354
var profileSupportsDelta = map[handler]bool{
354355
"allocs": true,
355356
"block": true,
357+
"goroutineleak": true,
356358
"goroutine": true,
357359
"heap": true,
358360
"mutex": true,
@@ -372,6 +374,12 @@ var profileDescriptions = map[string]string{
372374
"trace": "A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.",
373375
}
374376

377+
func init() {
378+
if goexperiment.GoroutineLeakProfile {
379+
profileDescriptions["goroutineleak"] = "Stack traces of all leaked goroutines. Use debug=2 as a query parameter to export in the same format as an unrecovered panic."
380+
}
381+
}
382+
375383
type profileEntry struct {
376384
Name string
377385
Href string

src/runtime/chan.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
263263
}
264264
// No stack splits between assigning elem and enqueuing mysg
265265
// on gp.waiting where copystack can find it.
266-
mysg.elem = ep
266+
mysg.elem.set(ep)
267267
mysg.waitlink = nil
268268
mysg.g = gp
269269
mysg.isSelect = false
270-
mysg.c = c
270+
mysg.c.set(c)
271271
gp.waiting = mysg
272272
gp.param = nil
273273
c.sendq.enqueue(mysg)
@@ -298,7 +298,7 @@ func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
298298
if mysg.releasetime > 0 {
299299
blockevent(mysg.releasetime-t0, 2)
300300
}
301-
mysg.c = nil
301+
mysg.c.set(nil)
302302
releaseSudog(mysg)
303303
if closed {
304304
if c.closed == 0 {
@@ -336,9 +336,9 @@ func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
336336
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
337337
}
338338
}
339-
if sg.elem != nil {
339+
if sg.elem.get() != nil {
340340
sendDirect(c.elemtype, sg, ep)
341-
sg.elem = nil
341+
sg.elem.set(nil)
342342
}
343343
gp := sg.g
344344
unlockf()
@@ -395,7 +395,7 @@ func sendDirect(t *_type, sg *sudog, src unsafe.Pointer) {
395395
// Once we read sg.elem out of sg, it will no longer
396396
// be updated if the destination's stack gets copied (shrunk).
397397
// So make sure that no preemption points can happen between read & use.
398-
dst := sg.elem
398+
dst := sg.elem.get()
399399
typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)
400400
// No need for cgo write barrier checks because dst is always
401401
// Go memory.
@@ -406,7 +406,7 @@ func recvDirect(t *_type, sg *sudog, dst unsafe.Pointer) {
406406
// dst is on our stack or the heap, src is on another stack.
407407
// The channel is locked, so src will not move during this
408408
// operation.
409-
src := sg.elem
409+
src := sg.elem.get()
410410
typeBitsBulkBarrier(t, uintptr(dst), uintptr(src), t.Size_)
411411
memmove(dst, src, t.Size_)
412412
}
@@ -441,9 +441,9 @@ func closechan(c *hchan) {
441441
if sg == nil {
442442
break
443443
}
444-
if sg.elem != nil {
445-
typedmemclr(c.elemtype, sg.elem)
446-
sg.elem = nil
444+
if sg.elem.get() != nil {
445+
typedmemclr(c.elemtype, sg.elem.get())
446+
sg.elem.set(nil)
447447
}
448448
if sg.releasetime != 0 {
449449
sg.releasetime = cputicks()
@@ -463,7 +463,7 @@ func closechan(c *hchan) {
463463
if sg == nil {
464464
break
465465
}
466-
sg.elem = nil
466+
sg.elem.set(nil)
467467
if sg.releasetime != 0 {
468468
sg.releasetime = cputicks()
469469
}
@@ -642,13 +642,13 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
642642
}
643643
// No stack splits between assigning elem and enqueuing mysg
644644
// on gp.waiting where copystack can find it.
645-
mysg.elem = ep
645+
mysg.elem.set(ep)
646646
mysg.waitlink = nil
647647
gp.waiting = mysg
648648

649649
mysg.g = gp
650650
mysg.isSelect = false
651-
mysg.c = c
651+
mysg.c.set(c)
652652
gp.param = nil
653653
c.recvq.enqueue(mysg)
654654
if c.timer != nil {
@@ -680,7 +680,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
680680
}
681681
success := mysg.success
682682
gp.param = nil
683-
mysg.c = nil
683+
mysg.c.set(nil)
684684
releaseSudog(mysg)
685685
return true, success
686686
}
@@ -727,14 +727,14 @@ func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
727727
typedmemmove(c.elemtype, ep, qp)
728728
}
729729
// copy data from sender to queue
730-
typedmemmove(c.elemtype, qp, sg.elem)
730+
typedmemmove(c.elemtype, qp, sg.elem.get())
731731
c.recvx++
732732
if c.recvx == c.dataqsiz {
733733
c.recvx = 0
734734
}
735735
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
736736
}
737-
sg.elem = nil
737+
sg.elem.set(nil)
738738
gp := sg.g
739739
unlockf()
740740
gp.param = unsafe.Pointer(sg)

src/runtime/crash_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,22 @@ func buildTestProg(t *testing.T, binary string, flags ...string) (string, error)
186186
t.Logf("running %v", cmd)
187187
cmd.Dir = "testdata/" + binary
188188
cmd = testenv.CleanCmdEnv(cmd)
189+
190+
// If tests need any experimental flags, add them here.
191+
//
192+
// TODO(vsaioc): Remove `goroutineleakprofile` once the feature is no longer experimental.
193+
edited := false
194+
for i := range cmd.Env {
195+
e := cmd.Env[i]
196+
if _, vars, ok := strings.Cut(e, "GOEXPERIMENT="); ok {
197+
cmd.Env[i] = "GOEXPERIMENT=" + vars + ",goroutineleakprofile"
198+
edited, _ = true, vars
199+
}
200+
}
201+
if !edited {
202+
cmd.Env = append(cmd.Env, "GOEXPERIMENT=goroutineleakprofile")
203+
}
204+
189205
out, err := cmd.CombinedOutput()
190206
if err != nil {
191207
target.err = fmt.Errorf("building %s %v: %v\n%s", binary, flags, err, out)

0 commit comments

Comments
 (0)