Skip to content

Commit 3aecb3a

Browse files
committed
runtime: fix sweep termination condition
Currently, there is a chance that the sweep termination condition could flap, causing e.g. runtime.GC to return before all sweep work has not only been drained, but also completed. CL 307915 and CL 307916 attempted to fix this problem, but it is still possible that mheap_.sweepDrained is marked before any outstanding sweepers are accounted for in mheap_.sweepers, leaving a window in which a thread could observe isSweepDone as true before it actually was (and after some time it would revert to false, then true again, depending on the number of outstanding sweepers at that point). This change fixes the sweep termination condition by merging mheap_.sweepers and mheap_.sweepDrained into a single atomic value. This value is updated such that a new potential sweeper will increment the oustanding sweeper count iff there are still outstanding spans to be swept without an outstanding sweeper to pick them up. This design simplifies the sweep termination condition into a single atomic load and comparison and ensures the condition never flaps. Updates #46500. Fixes #45315. Change-Id: I6d69aff156b8d48428c4cc8cfdbf28be346dbf04 Reviewed-on: https://go-review.googlesource.com/c/go/+/333389 Trust: Michael Knyszek <[email protected]> Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent f288526 commit 3aecb3a

File tree

5 files changed

+208
-131
lines changed

5 files changed

+208
-131
lines changed

src/runtime/mcentral.go

+47-44
Original file line numberDiff line numberDiff line change
@@ -102,59 +102,62 @@ func (c *mcentral) cacheSpan() *mspan {
102102
spanBudget := 100
103103

104104
var s *mspan
105-
sl := newSweepLocker()
106-
sg := sl.sweepGen
105+
var sl sweepLocker
107106

108107
// Try partial swept spans first.
108+
sg := mheap_.sweepgen
109109
if s = c.partialSwept(sg).pop(); s != nil {
110110
goto havespan
111111
}
112112

113-
// Now try partial unswept spans.
114-
for ; spanBudget >= 0; spanBudget-- {
115-
s = c.partialUnswept(sg).pop()
116-
if s == nil {
117-
break
118-
}
119-
if s, ok := sl.tryAcquire(s); ok {
120-
// We got ownership of the span, so let's sweep it and use it.
121-
s.sweep(true)
122-
sl.dispose()
123-
goto havespan
124-
}
125-
// We failed to get ownership of the span, which means it's being or
126-
// has been swept by an asynchronous sweeper that just couldn't remove it
127-
// from the unswept list. That sweeper took ownership of the span and
128-
// responsibility for either freeing it to the heap or putting it on the
129-
// right swept list. Either way, we should just ignore it (and it's unsafe
130-
// for us to do anything else).
131-
}
132-
// Now try full unswept spans, sweeping them and putting them into the
133-
// right list if we fail to get a span.
134-
for ; spanBudget >= 0; spanBudget-- {
135-
s = c.fullUnswept(sg).pop()
136-
if s == nil {
137-
break
138-
}
139-
if s, ok := sl.tryAcquire(s); ok {
140-
// We got ownership of the span, so let's sweep it.
141-
s.sweep(true)
142-
// Check if there's any free space.
143-
freeIndex := s.nextFreeIndex()
144-
if freeIndex != s.nelems {
145-
s.freeindex = freeIndex
146-
sl.dispose()
113+
sl = sweep.active.begin()
114+
if sl.valid {
115+
// Now try partial unswept spans.
116+
for ; spanBudget >= 0; spanBudget-- {
117+
s = c.partialUnswept(sg).pop()
118+
if s == nil {
119+
break
120+
}
121+
if s, ok := sl.tryAcquire(s); ok {
122+
// We got ownership of the span, so let's sweep it and use it.
123+
s.sweep(true)
124+
sweep.active.end(sl)
147125
goto havespan
148126
}
149-
// Add it to the swept list, because sweeping didn't give us any free space.
150-
c.fullSwept(sg).push(s.mspan)
127+
// We failed to get ownership of the span, which means it's being or
128+
// has been swept by an asynchronous sweeper that just couldn't remove it
129+
// from the unswept list. That sweeper took ownership of the span and
130+
// responsibility for either freeing it to the heap or putting it on the
131+
// right swept list. Either way, we should just ignore it (and it's unsafe
132+
// for us to do anything else).
133+
}
134+
// Now try full unswept spans, sweeping them and putting them into the
135+
// right list if we fail to get a span.
136+
for ; spanBudget >= 0; spanBudget-- {
137+
s = c.fullUnswept(sg).pop()
138+
if s == nil {
139+
break
140+
}
141+
if s, ok := sl.tryAcquire(s); ok {
142+
// We got ownership of the span, so let's sweep it.
143+
s.sweep(true)
144+
// Check if there's any free space.
145+
freeIndex := s.nextFreeIndex()
146+
if freeIndex != s.nelems {
147+
s.freeindex = freeIndex
148+
sweep.active.end(sl)
149+
goto havespan
150+
}
151+
// Add it to the swept list, because sweeping didn't give us any free space.
152+
c.fullSwept(sg).push(s.mspan)
153+
}
154+
// See comment for partial unswept spans.
155+
}
156+
sweep.active.end(sl)
157+
if trace.enabled {
158+
traceGCSweepDone()
159+
traceDone = true
151160
}
152-
// See comment for partial unswept spans.
153-
}
154-
sl.dispose()
155-
if trace.enabled {
156-
traceGCSweepDone()
157-
traceDone = true
158161
}
159162

160163
// We failed to get a span from the mcentral so get one from mheap.

src/runtime/mgc.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func gcinit() {
154154
throw("size of Workbuf is suboptimal")
155155
}
156156
// No sweep on the first cycle.
157-
mheap_.sweepDrained = 1
157+
sweep.active.state.Store(sweepDrainedMask)
158158

159159
// Initialize GC pacer state.
160160
// Use the environment variable GOGC for the initial gcPercent value.
@@ -1022,8 +1022,10 @@ func gcMarkTermination(nextTriggerRatio float64) {
10221022
// Those aren't tracked in any sweep lists, so we need to
10231023
// count them against sweep completion until we ensure all
10241024
// those spans have been forced out.
1025-
sl := newSweepLocker()
1026-
sl.blockCompletion()
1025+
sl := sweep.active.begin()
1026+
if !sl.valid {
1027+
throw("failed to set sweep barrier")
1028+
}
10271029

10281030
systemstack(func() { startTheWorldWithSema(true) })
10291031

@@ -1050,7 +1052,7 @@ func gcMarkTermination(nextTriggerRatio float64) {
10501052
})
10511053
// Now that we've swept stale spans in mcaches, they don't
10521054
// count against unswept spans.
1053-
sl.dispose()
1055+
sweep.active.end(sl)
10541056

10551057
// Print gctrace before dropping worldsema. As soon as we drop
10561058
// worldsema another cycle could start and smash the stats
@@ -1457,7 +1459,7 @@ func gcSweep(mode gcMode) {
14571459

14581460
lock(&mheap_.lock)
14591461
mheap_.sweepgen += 2
1460-
mheap_.sweepDrained = 0
1462+
sweep.active.reset()
14611463
mheap_.pagesSwept.Store(0)
14621464
mheap_.sweepArenas = mheap_.allArenas
14631465
mheap_.reclaimIndex.Store(0)

0 commit comments

Comments
 (0)