Skip to content

Commit e115cce

Browse files
committed
Addressing some of the comments.
1 parent 76ff6cf commit e115cce

File tree

11 files changed

+107
-141
lines changed

11 files changed

+107
-141
lines changed

src/internal/goexperiment/exp_goleakfindergc_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_goleakfindergc_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/exp_golfgc_off.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/internal/goexperiment/exp_golfgc_on.go

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/internal/goexperiment/flags.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,6 @@ type Flags struct {
130130
// GreenTeaGC enables the Green Tea GC implementation.
131131
GreenTeaGC bool
132132

133-
// GolfGC enables the Deadlock GC implementation.
134-
GolfGC bool
133+
// GoroutineLeakFinderGC enables the Deadlock GC implementation.
134+
GoroutineLeakFinderGC bool
135135
}

src/runtime/mbitmap.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,6 +1247,28 @@ func markBitsForSpan(base uintptr) (mbits markBits) {
12471247
return mbits
12481248
}
12491249

1250+
// isMarkedOrNotInHeap returns true if a pointer is in the heap and marked,
1251+
// or if the pointer is not in the heap. Used by goroutine leak detection
1252+
// to determine if concurrency resources are reachable in memory.
1253+
func isMarkedOrNotInHeap(p unsafe.Pointer) bool {
1254+
obj, span, objIndex := findObject(uintptr(p), 0, 0)
1255+
if obj != 0 {
1256+
mbits := span.markBitsForIndex(objIndex)
1257+
return mbits.isMarked()
1258+
}
1259+
1260+
// If we fall through to get here, the object is not in the heap.
1261+
// In this case, it is either a pointer to a stack object or a global resource.
1262+
// Treat it as reachable in memory by default, to be safe.
1263+
//
1264+
// (vsaioc) TODO: we could possibly be more precise by only checking against the stacks
1265+
// of runnable goroutines. I don't think this is necessary, based on what we've seen, but
1266+
// let's keep the option open in case the runtime evolves.
1267+
// This will (naively) lead to quadratic blow-up for goroutine leak detection,
1268+
// but if it is only run on demand, maybe the extra cost is not a show-stopper.
1269+
return true
1270+
}
1271+
12501272
// advance advances the markBits to the next object in the span.
12511273
func (m *markBits) advance() {
12521274
if m.mask == 1<<7 {

src/runtime/mgc.go

Lines changed: 44 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -381,16 +381,18 @@ type workType struct {
381381

382382
// The following fields monitor the GC phase of the current cycle during
383383
// goroutine leak detection.
384-
//
385-
// - pendingGoleakDetection: The GC has been instructed to perform goroutine leak
386-
// detection during the next GC cycle; it is set by DetectGoroutineLeaks()
387-
// and unset during gcStart().
388-
// - detectingGoleaks: The GC is running in goroutine leak detection mode; it is set
389-
// during gcStart() and unset during gcMarkTermination().
390-
// - detectedGoleaks: The GC has performed goroutine leak detection during the current
391-
// GC cycle; it is set during gcMarkDone(), right after goroutine leak detection has concluded,
392-
// and unset during gcStart().
393-
pendingGoleakDetection, detectingGoleaks, detectedGoleaks bool
384+
goroutineLeakFinder struct {
385+
// The GC has been instructed to perform goroutine leak detection during the next GC cycle;
386+
// it is set by DetectGoroutineLeaks() and unset during gcStart().
387+
pending atomic.Bool
388+
// The GC is running in goroutine leak detection mode; it is set during gcStart()
389+
// and unset during gcMarkTermination(). Is protected by STW.
390+
enabled bool
391+
// The GC has performed goroutine leak detection during the current GC cycle; it is set
392+
// during gcMarkDone(), right after goroutine leak detection has concluded, and unset during
393+
// gcStart(). Is protected by STW.
394+
done bool
395+
}
394396

395397
// Base indexes of each root type. Set by gcPrepareMarkRoots.
396398
baseData, baseBSS, baseSpans, baseStacks, baseEnd uint32
@@ -571,22 +573,17 @@ func GC() {
571573
// FindGoleaks instructs the Go garbage collector to attempt
572574
// goroutine leak detection during the next GC cycle.
573575
//
574-
// Only operates if golfgc is enabled in GOEXPERIMENT.
576+
// Only operates if goroutineleakfindergc is enabled in GOEXPERIMENT.
575577
// Otherwise, it just runs runtime.GC().
576578
func FindGoLeaks() {
577-
if !goexperiment.GolfGC {
579+
if !goexperiment.GoroutineLeakFinderGC {
578580
GC()
579581
return
580582
}
581583

582-
// This write should be thread-safe, as the overwritten value is true.
583-
// pendingGoleakDetection is only set to false under STW at the start
584-
// of the GC cycle that picks it up.
585-
work.pendingGoleakDetection = true
584+
work.goroutineLeakFinder.pending.Store(true)
586585

587-
// This read should be thread-safe for the same reason as the write above above.
588-
// At most, we trigger the GC an additional time.
589-
for work.pendingGoleakDetection {
586+
for work.goroutineLeakFinder.pending.Load() {
590587
GC()
591588
}
592589
}
@@ -736,8 +733,8 @@ func gcStart(trigger gcTrigger) {
736733
mode = gcForceMode
737734
} else if debug.gcstoptheworld == 2 {
738735
mode = gcForceBlockMode
739-
} else if goexperiment.GolfGC {
740-
if work.pendingGoleakDetection {
736+
} else if goexperiment.GoroutineLeakFinderGC {
737+
if work.goroutineLeakFinder.pending.Load() {
741738
// Fully stop the world if running deadlock detection.
742739
mode = gcForceBlockMode
743740
}
@@ -803,7 +800,7 @@ func gcStart(trigger gcTrigger) {
803800
clearpools()
804801

805802
work.cycles.Add(1)
806-
work.detectedGoleaks = false
803+
work.goroutineLeakFinder.done = false
807804

808805
// Assists and workers can start the moment we start
809806
// the world.
@@ -835,12 +832,9 @@ func gcStart(trigger gcTrigger) {
835832
// possible.
836833
setGCPhase(_GCmark)
837834

838-
if goexperiment.GolfGC {
839-
if work.pendingGoleakDetection {
840-
// Write is thread-safe because the world is stopped
841-
work.detectingGoleaks = true
842-
work.pendingGoleakDetection = false
843-
}
835+
if work.goroutineLeakFinder.pending.Load() {
836+
work.goroutineLeakFinder.enabled = true
837+
work.goroutineLeakFinder.pending.Store(false)
844838
}
845839

846840
gcBgMarkPrepare() // Must happen before assists are enabled.
@@ -943,10 +937,8 @@ func gcMarkDone() {
943937
// Ensure only one thread is running the ragged barrier at a
944938
// time.
945939
semacquire(&work.markDoneSema)
946-
if goexperiment.GolfGC {
947-
if work.detectingGoleaks {
948-
gcDiscoverMoreStackRoots()
949-
}
940+
if work.goroutineLeakFinder.enabled {
941+
gcDiscoverMoreStackRoots()
950942
}
951943

952944
top:
@@ -1007,7 +999,8 @@ top:
1007999
// communicated work since we took markDoneSema. Therefore
10081000
// there are no grey objects and no more objects can be
10091001
// shaded. Transition to mark termination.
1010-
var now int64
1002+
now := nanotime()
1003+
work.tMarkTerm = now
10111004
getg().m.preemptoff = "gcing"
10121005
var stw worldStop
10131006
systemstack(func() {
@@ -1053,44 +1046,13 @@ top:
10531046
})
10541047
semrelease(&worldsema)
10551048
goto top
1056-
} else if goexperiment.GolfGC {
1049+
} else if goexperiment.GoroutineLeakFinderGC {
10571050
// If we are detecting goroutine leaks, do so now.
1058-
if work.detectingGoleaks && !work.detectedGoleaks {
1051+
if work.goroutineLeakFinder.enabled && !work.goroutineLeakFinder.done {
10591052
// Detect goroutine leaks. If the returned value is true, then
10601053
// detection was performed during this cycle. Otherwise, more mark work is needed,
10611054
// or live goroutines were found.
1062-
work.detectedGoleaks = findGoleaks()
1063-
1064-
getg().m.preemptoff = ""
1065-
systemstack(func() {
1066-
// Accumulate the time we were stopped before we had to start again.
1067-
work.cpuStats.accumulateGCPauseTime(nanotime()-stw.finishedStopping, work.maxprocs)
1068-
1069-
now := startTheWorldWithSema(0, stw)
1070-
work.pauseNS += now - stw.startedStopping
1071-
})
1072-
semrelease(&worldsema)
1073-
goto top
1074-
}
1075-
1076-
now = nanotime()
1077-
work.tMarkTerm = now
1078-
// Check again whether any P needs to flush its write barrier
1079-
// to the GC work queue.
1080-
systemstack(func() {
1081-
for _, p := range allp {
1082-
wbBufFlush1(p)
1083-
if !p.gcw.empty() {
1084-
restart = true
1085-
break
1086-
}
1087-
}
1088-
})
1089-
1090-
// If that is the case, restart again. Once restarts are no longer needed,
1091-
// run this without deadlock detection.
1092-
if restart {
1093-
gcDebugMarkDone.restartedDueTo27993 = true
1055+
work.goroutineLeakFinder.done = findGoleaks()
10941056

10951057
getg().m.preemptoff = ""
10961058
systemstack(func() {
@@ -1141,24 +1103,11 @@ top:
11411103
gcMarkTermination(stw)
11421104
}
11431105

1144-
// Check if an object is marked in the heap.
1145-
func checkIfMarked(p unsafe.Pointer) bool {
1146-
obj, span, objIndex := findObject(uintptr(p), 0, 0)
1147-
if obj != 0 {
1148-
mbits := span.markBitsForIndex(objIndex)
1149-
return mbits.isMarked()
1150-
}
1151-
// if we fall through to get here, we are within the stack ranges of reachable goroutines
1152-
return true
1153-
}
1154-
1155-
// maybeLive checks whether a goroutine may still be semantically runnable.
1156-
// This returns true if the goroutine is waiting on at least one concurrency primitive
1157-
// which is reachable in memory, i.e., has been by the GC.
1158-
//
1106+
// checkIfMaybeRunnable checks whether a goroutine may still be semantically runnable.
11591107
// For goroutines which are semantically runnable, this will eventually return true
1160-
// as the GC marking phase progresses.
1161-
func (gp *g) maybeLive() bool {
1108+
// as the GC marking phase progresses. It returns false for leaked goroutines, or for
1109+
// goroutines which are not yet computed as possibly runnable by the GC.
1110+
func (gp *g) checkIfMaybeRunnable() bool {
11621111
// Unmask the goroutine address to ensure we are not
11631112
// dereferencing a masked address.
11641113
gp = gp.unmask()
@@ -1176,7 +1125,7 @@ func (gp *g) maybeLive() bool {
11761125
// Cycle all through all *sudog to check whether
11771126
// the goroutine is waiting on a marked channel.
11781127
for sg := gp.waiting; sg != nil; sg = sg.waitlink {
1179-
if checkIfMarked(unsafe.Pointer(sg.c)) {
1128+
if isMarkedOrNotInHeap(unsafe.Pointer(sg.c)) {
11801129
return true
11811130
}
11821131
}
@@ -1190,7 +1139,7 @@ func (gp *g) maybeLive() bool {
11901139
// check if the synchronization primitive attached to the sudog is marked.
11911140
if gp.waiting != nil {
11921141
// Unmask the sema address and check if it's marked.
1193-
return checkIfMarked(gcUnmask(gp.waiting.elem))
1142+
return isMarkedOrNotInHeap(gcUnmask(gp.waiting.elem))
11941143
}
11951144
}
11961145
return true
@@ -1223,13 +1172,13 @@ func gcDiscoverMoreStackRoots() {
12231172
// Reorder goroutine list
12241173
for vIndex < ivIndex {
12251174
gp := work.stackRoots[vIndex]
1226-
if gp.maybeLive() {
1175+
if gp.checkIfMaybeRunnable() {
12271176
work.stackRoots[vIndex] = gp
12281177
vIndex = vIndex + 1
12291178
continue
12301179
}
12311180
for ivIndex = ivIndex - 1; ivIndex != vIndex; ivIndex = ivIndex - 1 {
1232-
if swapGp := work.stackRoots[ivIndex]; swapGp.maybeLive() {
1181+
if swapGp := work.stackRoots[ivIndex]; swapGp.checkIfMaybeRunnable() {
12331182
work.stackRoots[ivIndex] = gp
12341183
work.stackRoots[vIndex] = swapGp.unmask()
12351184
vIndex = vIndex + 1
@@ -1268,7 +1217,7 @@ func findGoleaks() bool {
12681217
var foundMoreWork bool
12691218
for i := work.nLiveStackRoots; i < work.nStackRoots; i++ {
12701219
gp := work.stackRoots[i].unmask()
1271-
if readgstatus(gp) == _Gwaiting && !gp.maybeLive() {
1220+
if readgstatus(gp) == _Gwaiting && !gp.checkIfMaybeRunnable() {
12721221
// Blocking unrunnable goroutines will be skipped.
12731222
continue
12741223
}
@@ -1459,12 +1408,8 @@ func gcMarkTermination(stw worldStop) {
14591408
}
14601409

14611410
systemstack(func() {
1462-
if goexperiment.GolfGC {
1463-
// Pull the GC out of goroutine leak detection mode.
1464-
// Write is thread-safe because the world is stopped, and only one
1465-
// GC cycle can run at a time.
1466-
work.detectingGoleaks = false
1467-
}
1411+
// Pull the GC out of goroutine leak detection mode.
1412+
work.goroutineLeakFinder.enabled = false
14681413

14691414
// The memstats updated above must be updated with the world
14701415
// stopped to ensure consistency of some values, such as
@@ -1893,12 +1838,12 @@ func gcMarkWorkAvailable(p *p) bool {
18931838
if !work.full.empty() || !work.spanq.empty() {
18941839
return true // global work available
18951840
}
1896-
if !work.detectingGoleaks {
1897-
return work.markrootNext < work.markrootJobs
1898-
}
18991841
rootNext := atomic.Load(&work.markrootNext)
19001842
rootJobs := atomic.Load(&work.markrootJobs)
1901-
return rootNext < rootJobs
1843+
if rootNext < rootJobs {
1844+
return true // root scan work available
1845+
}
1846+
return false
19021847
}
19031848

19041849
// gcMark runs the mark (or, for concurrent GC, mark termination)

src/runtime/mgcmark.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const (
6060
//
6161
//go:nosplit
6262
func gcMask(p unsafe.Pointer) unsafe.Pointer {
63-
if goexperiment.GolfGC {
63+
if goexperiment.GoroutineLeakFinderGC {
6464
return unsafe.Pointer(uintptr(p) | gcBitMask)
6565
}
6666
return p
@@ -70,7 +70,7 @@ func gcMask(p unsafe.Pointer) unsafe.Pointer {
7070
//
7171
//go:nosplit
7272
func gcUnmask(p unsafe.Pointer) unsafe.Pointer {
73-
if goexperiment.GolfGC {
73+
if goexperiment.GoroutineLeakFinderGC {
7474
return unsafe.Pointer(uintptr(p) & gcUndoBitMask)
7575
}
7676
return p
@@ -170,19 +170,14 @@ func gcPrepareMarkRoots() {
170170
// ignore them because they begin life without any roots, so
171171
// there's nothing to scan, and any roots they create during
172172
// the concurrent phase will be caught by the write barrier.
173-
if goexperiment.GolfGC {
174-
if work.detectingGoleaks {
175-
work.stackRoots, work.nLiveStackRoots = allGsSnapshotSortedForGC()
176-
} else {
177-
// regular GC --- scan every go routine
178-
work.stackRoots = allGsSnapshot()
179-
work.nLiveStackRoots = len(work.stackRoots)
180-
}
173+
if work.goroutineLeakFinder.enabled {
174+
work.stackRoots, work.nLiveStackRoots = allGsSnapshotSortedForGC()
181175
} else {
182176
// regular GC --- scan every go routine
183177
work.stackRoots = allGsSnapshot()
184178
work.nLiveStackRoots = len(work.stackRoots)
185179
}
180+
186181
work.nStackRoots = len(work.stackRoots)
187182

188183
work.markrootNext = 0
@@ -1621,9 +1616,9 @@ func scanobject(b uintptr, gcw *gcWork) {
16211616

16221617
// At this point we have extracted the next potential pointer.
16231618
// Quickly filter out nil and pointers back to the current object.
1624-
// The GC will skip masked addresses if GolfGC is enabled.
1619+
// The GC will skip masked addresses if GoroutineLeakFinderGC is enabled.
16251620
if obj != 0 && obj-b >= n &&
1626-
(!goexperiment.GolfGC || obj <= gcUndoBitMask) {
1621+
(!goexperiment.GoroutineLeakFinderGC || obj <= gcUndoBitMask) {
16271622
// Test if obj points into the Go heap and, if so,
16281623
// mark the object.
16291624
//

0 commit comments

Comments
 (0)