Skip to content

Commit 7ed7669

Browse files
committed
runtime: ensure mheap lock stack growth invariant is maintained
Currently there's an invariant in the runtime wherein the heap lock can only be acquired on the system stack, otherwise a self-deadlock could occur if the stack grows while the lock is held. This invariant is upheld and documented in a number of situations (e.g. allocManual, freeManual) but there are other places where the invariant is either not maintained at all which risks self-deadlock (e.g. setGCPercent, gcResetMarkState, allocmcache) or is maintained but undocumented (e.g. gcSweep, readGCStats_m). This change adds go:systemstack to any function that acquires the heap lock or adds a systemstack(func() { ... }) around the critical section, where appropriate. It also documents the invariant on (*mheap).lock directly and updates repetitive documentation to refer to that comment. Fixes #32105. Change-Id: I702b1290709c118b837389c78efde25c51a2cafb Reviewed-on: https://go-review.googlesource.com/c/go/+/177857 Run-TryBot: Michael Knyszek <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent db32555 commit 7ed7669

File tree

5 files changed

+64
-35
lines changed

5 files changed

+64
-35
lines changed

src/runtime/export_test.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -545,18 +545,23 @@ type Span struct {
545545
}
546546

547547
func AllocSpan(base, npages uintptr, scavenged bool) Span {
548-
lock(&mheap_.lock)
549-
s := (*mspan)(mheap_.spanalloc.alloc())
550-
unlock(&mheap_.lock)
548+
var s *mspan
549+
systemstack(func() {
550+
lock(&mheap_.lock)
551+
s = (*mspan)(mheap_.spanalloc.alloc())
552+
unlock(&mheap_.lock)
553+
})
551554
s.init(base, npages)
552555
s.scavenged = scavenged
553556
return Span{s}
554557
}
555558

556559
func (s *Span) Free() {
557-
lock(&mheap_.lock)
558-
mheap_.spanalloc.free(unsafe.Pointer(s.mspan))
559-
unlock(&mheap_.lock)
560+
systemstack(func() {
561+
lock(&mheap_.lock)
562+
mheap_.spanalloc.free(unsafe.Pointer(s.mspan))
563+
unlock(&mheap_.lock)
564+
})
560565
s.mspan = nil
561566
}
562567

@@ -629,9 +634,11 @@ func (t *Treap) Insert(s Span) {
629634
// allocation which requires the mheap_ lock to manipulate.
630635
// Locking here is safe because the treap itself never allocs
631636
// or otherwise ends up grabbing this lock.
632-
lock(&mheap_.lock)
633-
t.insert(s.mspan)
634-
unlock(&mheap_.lock)
637+
systemstack(func() {
638+
lock(&mheap_.lock)
639+
t.insert(s.mspan)
640+
unlock(&mheap_.lock)
641+
})
635642
t.CheckInvariants()
636643
}
637644

@@ -644,17 +651,21 @@ func (t *Treap) Erase(i TreapIter) {
644651
// freeing which requires the mheap_ lock to manipulate.
645652
// Locking here is safe because the treap itself never allocs
646653
// or otherwise ends up grabbing this lock.
647-
lock(&mheap_.lock)
648-
t.erase(i.treapIter)
649-
unlock(&mheap_.lock)
654+
systemstack(func() {
655+
lock(&mheap_.lock)
656+
t.erase(i.treapIter)
657+
unlock(&mheap_.lock)
658+
})
650659
t.CheckInvariants()
651660
}
652661

653662
func (t *Treap) RemoveSpan(s Span) {
654663
// See Erase about locking.
655-
lock(&mheap_.lock)
656-
t.removeSpan(s.mspan)
657-
unlock(&mheap_.lock)
664+
systemstack(func() {
665+
lock(&mheap_.lock)
666+
t.removeSpan(s.mspan)
667+
unlock(&mheap_.lock)
668+
})
658669
t.CheckInvariants()
659670
}
660671

src/runtime/mcache.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,13 @@ type stackfreelist struct {
8383
var emptymspan mspan
8484

8585
func allocmcache() *mcache {
86-
lock(&mheap_.lock)
87-
c := (*mcache)(mheap_.cachealloc.alloc())
88-
c.flushGen = mheap_.sweepgen
89-
unlock(&mheap_.lock)
86+
var c *mcache
87+
systemstack(func() {
88+
lock(&mheap_.lock)
89+
c = (*mcache)(mheap_.cachealloc.alloc())
90+
c.flushGen = mheap_.sweepgen
91+
unlock(&mheap_.lock)
92+
})
9093
for i := range c.alloc {
9194
c.alloc[i] = &emptymspan
9295
}

src/runtime/mgc.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -216,16 +216,19 @@ func gcenable() {
216216

217217
//go:linkname setGCPercent runtime/debug.setGCPercent
218218
func setGCPercent(in int32) (out int32) {
219-
lock(&mheap_.lock)
220-
out = gcpercent
221-
if in < 0 {
222-
in = -1
223-
}
224-
gcpercent = in
225-
heapminimum = defaultHeapMinimum * uint64(gcpercent) / 100
226-
// Update pacing in response to gcpercent change.
227-
gcSetTriggerRatio(memstats.triggerRatio)
228-
unlock(&mheap_.lock)
219+
// Run on the system stack since we grab the heap lock.
220+
systemstack(func() {
221+
lock(&mheap_.lock)
222+
out = gcpercent
223+
if in < 0 {
224+
in = -1
225+
}
226+
gcpercent = in
227+
heapminimum = defaultHeapMinimum * uint64(gcpercent) / 100
228+
// Update pacing in response to gcpercent change.
229+
gcSetTriggerRatio(memstats.triggerRatio)
230+
unlock(&mheap_.lock)
231+
})
229232

230233
// If we just disabled GC, wait for any concurrent GC mark to
231234
// finish so we always return with no GC running.
@@ -1261,7 +1264,7 @@ func gcStart(trigger gcTrigger) {
12611264

12621265
gcBgMarkStartWorkers()
12631266

1264-
gcResetMarkState()
1267+
systemstack(gcResetMarkState)
12651268

12661269
work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
12671270
if work.stwprocs > ncpu {
@@ -2078,6 +2081,9 @@ func gcMark(start_time int64) {
20782081
}
20792082
}
20802083

2084+
// gcSweep must be called on the system stack because it acquires the heap
2085+
// lock. See mheap for details.
2086+
//go:systemstack
20812087
func gcSweep(mode gcMode) {
20822088
if gcphase != _GCoff {
20832089
throw("gcSweep being done but phase is not GCoff")
@@ -2134,6 +2140,11 @@ func gcSweep(mode gcMode) {
21342140
//
21352141
// This is safe to do without the world stopped because any Gs created
21362142
// during or after this will start out in the reset state.
2143+
//
2144+
// gcResetMarkState must be called on the system stack because it acquires
2145+
// the heap lock. See mheap for details.
2146+
//
2147+
//go:systemstack
21372148
func gcResetMarkState() {
21382149
// This may be called during a concurrent phase, so make sure
21392150
// allgs doesn't change.

src/runtime/mheap.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const minPhysPageSize = 4096
2929
//
3030
//go:notinheap
3131
type mheap struct {
32+
// lock must only be acquired on the system stack, otherwise a g
33+
// could self-deadlock if its stack grows with the lock held.
3234
lock mutex
3335
free mTreap // free spans
3436
sweepgen uint32 // sweep generation, see comment in mspan
@@ -1095,9 +1097,8 @@ func (h *mheap) alloc(npage uintptr, spanclass spanClass, large bool, needzero b
10951097
// The memory backing the returned span may not be zeroed if
10961098
// span.needzero is set.
10971099
//
1098-
// allocManual must be called on the system stack to prevent stack
1099-
// growth. Since this is used by the stack allocator, stack growth
1100-
// during allocManual would self-deadlock.
1100+
// allocManual must be called on the system stack because it acquires
1101+
// the heap lock. See mheap for details.
11011102
//
11021103
//go:systemstack
11031104
func (h *mheap) allocManual(npage uintptr, stat *uint64) *mspan {
@@ -1303,8 +1304,8 @@ func (h *mheap) freeSpan(s *mspan, large bool) {
13031304
// This must only be called when gcphase == _GCoff. See mSpanState for
13041305
// an explanation.
13051306
//
1306-
// freeManual must be called on the system stack to prevent stack
1307-
// growth, just like allocManual.
1307+
// freeManual must be called on the system stack because it acquires
1308+
// the heap lock. See mheap for details.
13081309
//
13091310
//go:systemstack
13101311
func (h *mheap) freeManual(s *mspan, stat *uint64) {

src/runtime/mstats.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ func readGCStats(pauses *[]uint64) {
470470
})
471471
}
472472

473+
// readGCStats_m must be called on the system stack because it acquires the heap
474+
// lock. See mheap for details.
475+
//go:systemstack
473476
func readGCStats_m(pauses *[]uint64) {
474477
p := *pauses
475478
// Calling code in runtime/debug should make the slice large enough.

0 commit comments

Comments
 (0)