Skip to content

Commit 01f9edb

Browse files
committed
Removed bitmask and switched to tristate pointers (thank you @mknyszek).
Reset leaked goroutines to waiting when restarting GC cycle.
1 parent fcbf0bc commit 01f9edb

File tree

9 files changed

+189
-147
lines changed

9 files changed

+189
-147
lines changed

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/mgc.go

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,12 @@ func gcStart(trigger gcTrigger) {
816816
schedEnableUser(false)
817817
}
818818

819+
if work.goroutineLeakFinder.pending.Load() {
820+
work.goroutineLeakFinder.enabled = true
821+
work.goroutineLeakFinder.pending.Store(false)
822+
gcUntrackSyncObjects()
823+
}
824+
819825
// Enter concurrent mark phase and enable
820826
// write barriers.
821827
//
@@ -832,11 +838,6 @@ func gcStart(trigger gcTrigger) {
832838
// possible.
833839
setGCPhase(_GCmark)
834840

835-
if work.goroutineLeakFinder.pending.Load() {
836-
work.goroutineLeakFinder.enabled = true
837-
work.goroutineLeakFinder.pending.Store(false)
838-
}
839-
840841
gcBgMarkPrepare() // Must happen before assists are enabled.
841842
gcPrepareMarkRoots()
842843

@@ -1110,8 +1111,6 @@ top:
11101111
func (gp *g) checkIfMaybeRunnable() bool {
11111112
// Unmask the goroutine address to ensure we are not
11121113
// dereferencing a masked address.
1113-
gp = gp.unmask()
1114-
11151114
switch gp.waitreason {
11161115
case waitReasonSelectNoCases,
11171116
waitReasonChanSendNilChan,
@@ -1125,7 +1124,7 @@ func (gp *g) checkIfMaybeRunnable() bool {
11251124
// Cycle all through all *sudog to check whether
11261125
// the goroutine is waiting on a marked channel.
11271126
for sg := gp.waiting; sg != nil; sg = sg.waitlink {
1128-
if isMarkedOrNotInHeap(unsafe.Pointer(sg.c)) {
1127+
if isMarkedOrNotInHeap(unsafe.Pointer(sg.c.get())) {
11291128
return true
11301129
}
11311130
}
@@ -1139,26 +1138,12 @@ func (gp *g) checkIfMaybeRunnable() bool {
11391138
// check if the synchronization primitive attached to the sudog is marked.
11401139
if gp.waiting != nil {
11411140
// Unmask the sema address and check if it's marked.
1142-
return isMarkedOrNotInHeap(gcUnmask(gp.waiting.elem))
1141+
return isMarkedOrNotInHeap(gp.waiting.elem.get())
11431142
}
11441143
}
11451144
return true
11461145
}
11471146

1148-
// unmask returns a *g object with an unmasked address.
1149-
//
1150-
//go:nosplit
1151-
func (gp *g) unmask() *g {
1152-
return (*g)(gcUnmask(unsafe.Pointer(gp)))
1153-
}
1154-
1155-
// mask returns a *g object with a masked address.
1156-
//
1157-
//go:nosplit
1158-
func (gp *g) mask() *g {
1159-
return (*g)(gcMask(unsafe.Pointer(gp)))
1160-
}
1161-
11621147
// Check to see if more blocked but marked goroutines exist;
11631148
// if so add them into root set and increment work.markrootJobs accordingly
11641149
// return true if we need to run another phase of markroots; return false otherwise
@@ -1171,16 +1156,14 @@ func gcDiscoverMoreStackRoots() {
11711156

11721157
// Reorder goroutine list
11731158
for vIndex < ivIndex {
1174-
gp := work.stackRoots[vIndex]
1175-
if gp.checkIfMaybeRunnable() {
1176-
work.stackRoots[vIndex] = gp
1159+
if work.stackRoots[vIndex].checkIfMaybeRunnable() {
11771160
vIndex = vIndex + 1
11781161
continue
11791162
}
11801163
for ivIndex = ivIndex - 1; ivIndex != vIndex; ivIndex = ivIndex - 1 {
1181-
if swapGp := work.stackRoots[ivIndex]; swapGp.checkIfMaybeRunnable() {
1182-
work.stackRoots[ivIndex] = gp
1183-
work.stackRoots[vIndex] = swapGp.unmask()
1164+
if gp := work.stackRoots[ivIndex]; gp.checkIfMaybeRunnable() {
1165+
work.stackRoots[ivIndex] = work.stackRoots[vIndex]
1166+
work.stackRoots[vIndex] = gp
11841167
vIndex = vIndex + 1
11851168
break
11861169
}
@@ -1197,6 +1180,35 @@ func gcDiscoverMoreStackRoots() {
11971180
}
11981181
}
11991182

1183+
// getSyncObjectsUnreachable scans allgs and sets the elem and c fields of all sudogs to
1184+
// an untrackable pointer. This prevents the GC from marking these objects as live in memory
1185+
// by following these pointers when runnning deadlock detection.
1186+
func gcUntrackSyncObjects() {
1187+
assertWorldStopped()
1188+
1189+
forEachGRace(func(gp *g) {
1190+
for sg := gp.waiting; sg != nil; sg = sg.waitlink {
1191+
sg.elem.untrack()
1192+
sg.c.untrack()
1193+
}
1194+
})
1195+
}
1196+
1197+
// gcRestoreSyncObjects restores the elem and c fields of all sudogs to their original values.
1198+
// Should be invoked after the goroutine leak detection phase.
1199+
//
1200+
//go:nosplit
1201+
func gcRestoreSyncObjects() {
1202+
assertWorldStopped()
1203+
1204+
forEachGRace(func(gp *g) {
1205+
for sg := gp.waiting; sg != nil; sg = sg.waitlink {
1206+
sg.elem.track()
1207+
sg.c.track()
1208+
}
1209+
})
1210+
}
1211+
12001212
// findGoleaks scans the remaining stackRoots and marks any which are
12011213
// blocked over exclusively unreachable concurrency primitives as leaked (deadlocked).
12021214
// Returns true if the goroutine leak check was performed (or unnecessary).
@@ -1216,7 +1228,7 @@ func findGoleaks() bool {
12161228
// Make sure these are pushed to the runnable set and ready to be marked.
12171229
var foundMoreWork bool
12181230
for i := work.nLiveStackRoots; i < work.nStackRoots; i++ {
1219-
gp := work.stackRoots[i].unmask()
1231+
gp := work.stackRoots[i]
12201232
if readgstatus(gp) == _Gwaiting && !gp.checkIfMaybeRunnable() {
12211233
// Blocking unrunnable goroutines will be skipped.
12221234
continue
@@ -1237,7 +1249,7 @@ func findGoleaks() bool {
12371249

12381250
// For the remaining goroutines, mark them as unreachable and leaked.
12391251
for i := work.nLiveStackRoots; i < work.nStackRoots; i++ {
1240-
gp := work.stackRoots[i].unmask()
1252+
gp := work.stackRoots[i]
12411253
casgstatus(gp, _Gwaiting, _Gleaked)
12421254
fn := findfunc(gp.startpc)
12431255
if fn.valid() {
@@ -1247,7 +1259,6 @@ func findGoleaks() bool {
12471259
}
12481260
traceback(gp.sched.pc, gp.sched.sp, gp.sched.lr, gp)
12491261
println()
1250-
work.stackRoots[i] = gp
12511262
}
12521263
// Put the remaining roots as ready for marking and drain them.
12531264
work.markrootJobs += uint32(work.nStackRoots - work.nLiveStackRoots)
@@ -1407,6 +1418,11 @@ func gcMarkTermination(stw worldStop) {
14071418
throw("non-concurrent sweep failed to drain all sweep queues")
14081419
}
14091420

1421+
if work.goroutineLeakFinder.enabled {
1422+
// Restore the elem and c fields of all sudogs to their original values.
1423+
gcRestoreSyncObjects()
1424+
}
1425+
14101426
systemstack(func() {
14111427
// Pull the GC out of goroutine leak detection mode.
14121428
work.goroutineLeakFinder.enabled = false

src/runtime/mgcmark.go

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,8 @@ const (
5151
// Must be a multiple of the pageInUse bitmap element size and
5252
// must also evenly divide pagesPerArena.
5353
pagesPerSpanRoot = 512
54-
55-
gcUndoBitMask = uintptr(uintptrMask >> 2) // This constant reserves some bits of the address space for the GC to use in order to mask addresses.
56-
gcBitMask = ^gcUndoBitMask // This flips every bit in gcUndoBitMask of uinptr width
5754
)
5855

59-
// gcMask masks addresses that should not be automatically marked during the GC.
60-
//
61-
//go:nosplit
62-
func gcMask(p unsafe.Pointer) unsafe.Pointer {
63-
if goexperiment.GoroutineLeakFinderGC {
64-
return unsafe.Pointer(uintptr(p) | gcBitMask)
65-
}
66-
return p
67-
}
68-
69-
// gcUnmask undoes the bit-mask applied to a pointer.
70-
//
71-
//go:nosplit
72-
func gcUnmask(p unsafe.Pointer) unsafe.Pointer {
73-
if goexperiment.GoroutineLeakFinderGC {
74-
return unsafe.Pointer(uintptr(p) & gcUndoBitMask)
75-
}
76-
return p
77-
}
78-
7956
// internalBlocked returns true if the goroutine is blocked due to an
8057
// internal (non-leaking) waitReason, e.g. waiting for the netpoller or garbage collector.
8158
// Such goroutines are never leak detection candidates according to the GC.
@@ -96,19 +73,24 @@ func (gp *g) internalBlocked() bool {
9673
func allGsSnapshotSortedForGC() ([]*g, int) {
9774
assertWorldStoppedOrLockHeld(&allglock)
9875

76+
// Reset the status of leaked goroutines in order to improve
77+
// the precision of goroutine leak detection.
78+
for _, gp := range allgs {
79+
gp.atomicstatus.CompareAndSwap(_Gleaked, _Gwaiting)
80+
}
81+
9982
allgsSorted := make([]*g, len(allgs))
10083

10184
// Indices cutting off runnable and blocked Gs.
10285
var currIndex, blockedIndex = 0, len(allgsSorted) - 1
10386
for _, gp := range allgs {
104-
gp = gp.unmask()
10587
// not sure if we need atomic load because we are stopping the world,
10688
// but do it just to be safe for now
10789
if status := readgstatus(gp); status != _Gwaiting || gp.internalBlocked() {
10890
allgsSorted[currIndex] = gp
10991
currIndex++
11092
} else {
111-
allgsSorted[blockedIndex] = gp.mask()
93+
allgsSorted[blockedIndex] = gp
11294
blockedIndex--
11395
}
11496
}
@@ -1617,8 +1599,7 @@ func scanobject(b uintptr, gcw *gcWork) {
16171599
// At this point we have extracted the next potential pointer.
16181600
// Quickly filter out nil and pointers back to the current object.
16191601
// The GC will skip masked addresses if GoroutineLeakFinderGC is enabled.
1620-
if obj != 0 && obj-b >= n &&
1621-
(!goexperiment.GoroutineLeakFinderGC || obj <= gcUndoBitMask) {
1602+
if obj != 0 && obj-b >= n {
16221603
// Test if obj points into the Go heap and, if so,
16231604
// mark the object.
16241605
//

0 commit comments

Comments
 (0)