Skip to content

Commit 4f4c235

Browse files
committed
runtime: fix debug non-concurrent sweep mode after activeSweep changes
Currently the GC creates a sweepLocker before restarting the world at the end of the mark phase, so that it can safely flush mcaches without the runtime incorrectly concluding that sweeping is done before that happens. However, with GODEBUG=gcstoptheworld=2, where sweeping happens during that STW phase, creating that sweepLocker will fail, since the runtime will conclude that sweeping is in fact complete (all the queues will be drained). The problem however is that gcSweep, which does the non-concurrent sweeping, doesn't actually flush mcaches. In essence, this failure to create a sweepLocker is indicating a real issue: sweeping is marked as complete, but we haven't flush the mcaches yet! The fix to this is to flush mcaches in gcSweep when in a non-concurrent sweep. Now that gcSweep actually completes a full sweep, it's safe to ignore a failure to create a sweepLocker (and in fact, it *must* fail). While we're here, let's also remove _ConcurrentSweep, the debug flag. There's already an alias for it called concurrentSweep, and there's only one use of it in gcSweep. Lastly, add a dist test for the GODEBUG=gcstoptheworld=2 mode. Fixes #53885. Change-Id: I8a1e5b8f362ed8abd03f76e4950d3211f145ab1f Reviewed-on: https://go-review.googlesource.com/c/go/+/479517 Run-TryBot: Michael Knyszek <[email protected]> Reviewed-by: Cherry Mui <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 16ec514 commit 4f4c235

File tree

3 files changed

+54
-15
lines changed

3 files changed

+54
-15
lines changed

src/cmd/dist/test.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -676,8 +676,22 @@ func (t *tester) registerTests() {
676676
})
677677
}
678678

679-
// morestack tests. We only run these on in long-test mode
680-
// (with GO_TEST_SHORT=false) because the runtime test is
679+
// GODEBUG=gcstoptheworld=2 tests. We only run these in long-test
680+
// mode (with GO_TEST_SHORT=0) because this is just testing a
681+
// non-critical debug setting.
682+
if !t.compileOnly && !t.short {
683+
t.registerTest("GODEBUG=gcstoptheworld=2 archive/zip",
684+
&goTest{
685+
variant: "runtime:gcstoptheworld2",
686+
timeout: 300 * time.Second,
687+
short: true,
688+
env: []string{"GODEBUG=gcstoptheworld=2"},
689+
pkg: "archive/zip",
690+
})
691+
}
692+
693+
// morestack tests. We only run these in long-test mode
694+
// (with GO_TEST_SHORT=0) because the runtime test is
681695
// already quite long and mayMoreStackMove makes it about
682696
// twice as slow.
683697
if !t.compileOnly && !t.short {

src/runtime/malloc.go

-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,6 @@ const (
117117
pageShift = _PageShift
118118
pageSize = _PageSize
119119

120-
concurrentSweep = _ConcurrentSweep
121-
122120
_PageSize = 1 << _PageShift
123121
_PageMask = _PageSize - 1
124122

src/runtime/mgc.go

+38-11
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,12 @@ import (
135135
)
136136

137137
const (
138-
_DebugGC = 0
139-
_ConcurrentSweep = true
140-
_FinBlockSize = 4 * 1024
138+
_DebugGC = 0
139+
_FinBlockSize = 4 * 1024
140+
141+
// concurrentSweep is a debug flag. Disabling this flag
142+
// ensures all spans are swept while the world is stopped.
143+
concurrentSweep = true
141144

142145
// debugScanConservative enables debug logging for stack
143146
// frames that are scanned conservatively.
@@ -969,6 +972,7 @@ func gcMarkTermination() {
969972
// before continuing.
970973
})
971974

975+
var stwSwept bool
972976
systemstack(func() {
973977
work.heap2 = work.bytesMarked
974978
if debug.gccheckmark > 0 {
@@ -987,7 +991,7 @@ func gcMarkTermination() {
987991

988992
// marking is complete so we can turn the write barrier off
989993
setGCPhase(_GCoff)
990-
gcSweep(work.mode)
994+
stwSwept = gcSweep(work.mode)
991995
})
992996

993997
mp.traceback = 0
@@ -1079,9 +1083,19 @@ func gcMarkTermination() {
10791083
// Those aren't tracked in any sweep lists, so we need to
10801084
// count them against sweep completion until we ensure all
10811085
// those spans have been forced out.
1086+
//
1087+
// If gcSweep fully swept the heap (for example if the sweep
1088+
// is not concurrent due to a GODEBUG setting), then we expect
1089+
// the sweepLocker to be invalid, since sweeping is done.
1090+
//
1091+
// N.B. Below we might duplicate some work from gcSweep; this is
1092+
// fine as all that work is idempotent within a GC cycle, and
1093+
// we're still holding worldsema so a new cycle can't start.
10821094
sl := sweep.active.begin()
1083-
if !sl.valid {
1095+
if !stwSwept && !sl.valid {
10841096
throw("failed to set sweep barrier")
1097+
} else if stwSwept && sl.valid {
1098+
throw("non-concurrent sweep failed to drain all sweep queues")
10851099
}
10861100

10871101
systemstack(func() { startTheWorldWithSema() })
@@ -1123,9 +1137,15 @@ func gcMarkTermination() {
11231137
pp.pinnerCache = nil
11241138
})
11251139
})
1126-
// Now that we've swept stale spans in mcaches, they don't
1127-
// count against unswept spans.
1128-
sweep.active.end(sl)
1140+
if sl.valid {
1141+
// Now that we've swept stale spans in mcaches, they don't
1142+
// count against unswept spans.
1143+
//
1144+
// Note: this sweepLocker may not be valid if sweeping had
1145+
// already completed during the STW. See the corresponding
1146+
// begin() call that produced sl.
1147+
sweep.active.end(sl)
1148+
}
11291149

11301150
// Print gctrace before dropping worldsema. As soon as we drop
11311151
// worldsema another cycle could start and smash the stats
@@ -1538,10 +1558,12 @@ func gcMark(startTime int64) {
15381558
// gcSweep must be called on the system stack because it acquires the heap
15391559
// lock. See mheap for details.
15401560
//
1561+
// Returns true if the heap was fully swept by this function.
1562+
//
15411563
// The world must be stopped.
15421564
//
15431565
//go:systemstack
1544-
func gcSweep(mode gcMode) {
1566+
func gcSweep(mode gcMode) bool {
15451567
assertWorldStopped()
15461568

15471569
if gcphase != _GCoff {
@@ -1559,12 +1581,16 @@ func gcSweep(mode gcMode) {
15591581

15601582
sweep.centralIndex.clear()
15611583

1562-
if !_ConcurrentSweep || mode == gcForceBlockMode {
1584+
if !concurrentSweep || mode == gcForceBlockMode {
15631585
// Special case synchronous sweep.
15641586
// Record that no proportional sweeping has to happen.
15651587
lock(&mheap_.lock)
15661588
mheap_.sweepPagesPerByte = 0
15671589
unlock(&mheap_.lock)
1590+
// Flush all mcaches.
1591+
for _, pp := range allp {
1592+
pp.mcache.prepareForSweep()
1593+
}
15681594
// Sweep all spans eagerly.
15691595
for sweepone() != ^uintptr(0) {
15701596
sweep.npausesweep++
@@ -1578,7 +1604,7 @@ func gcSweep(mode gcMode) {
15781604
// available immediately.
15791605
mProf_NextCycle()
15801606
mProf_Flush()
1581-
return
1607+
return true
15821608
}
15831609

15841610
// Background sweep.
@@ -1588,6 +1614,7 @@ func gcSweep(mode gcMode) {
15881614
ready(sweep.g, 0, true)
15891615
}
15901616
unlock(&sweep.lock)
1617+
return false
15911618
}
15921619

15931620
// gcResetMarkState resets global state prior to marking (concurrent

0 commit comments

Comments
 (0)