Skip to content

Commit 0d42ceb

Browse files
mknyszekgopherbot
authored andcommitted
runtime: report finalizer and cleanup queue length with checkfinalizer>0
This change adds tracking for approximate finalizer and cleanup queue lengths. These lengths are reported once every GC cycle as a single line printed to stderr when GODEBUG=checkfinalizer>0. This change lays the groundwork for runtime/metrics metrics to produce the same values. For #72948. For #72950. Change-Id: I081721238a0fc4c7e5bee2dbaba6cfb4120d1a33 Reviewed-on: https://go-review.googlesource.com/c/go/+/671437 Reviewed-by: Michael Pratt <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2aac5a5 commit 0d42ceb

File tree

5 files changed

+66
-7
lines changed

5 files changed

+66
-7
lines changed

src/runtime/mcleanup.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,20 @@ type cleanupQueue struct {
336336
//
337337
// Read without lock, written only with lock held.
338338
needg atomic.Uint32
339+
340+
// Cleanup queue stats.
341+
342+
// queued represents a monotonic count of queued cleanups. This is sharded across
343+
// Ps via the field cleanupsQueued in each p, so reading just this value is insufficient.
344+
// In practice, this value only includes the queued count of dead Ps.
345+
//
346+
// Writes are protected by STW.
347+
queued uint64
348+
349+
// executed is a monotonic count of executed cleanups.
350+
//
351+
// Read and updated atomically.
352+
executed atomic.Uint64
339353
}
340354

341355
// addWork indicates that n units of parallelizable work have been added to the queue.
@@ -387,6 +401,7 @@ func (q *cleanupQueue) enqueue(fn *funcval) {
387401
pp.cleanups = nil
388402
q.addWork(1)
389403
}
404+
pp.cleanupsQueued++
390405
releasem(mp)
391406
}
392407

@@ -586,6 +601,19 @@ func (q *cleanupQueue) endRunningCleanups() {
586601
releasem(mp)
587602
}
588603

604+
func (q *cleanupQueue) readQueueStats() (queued, executed uint64) {
605+
executed = q.executed.Load()
606+
queued = q.queued
607+
608+
// N.B. This is inconsistent, but that's intentional. It's just an estimate.
609+
// Read this _after_ reading executed to decrease the chance that we observe
610+
// an inconsistency in the statistics (executed > queued).
611+
for _, pp := range allp {
612+
queued += pp.cleanupsQueued
613+
}
614+
return
615+
}
616+
589617
func maxCleanupGs() uint32 {
590618
// N.B. Left as a function to make changing the policy easier.
591619
return uint32(max(gomaxprocs/4, 1))
@@ -636,6 +664,7 @@ func runCleanups() {
636664
}
637665
}
638666
gcCleanups.endRunningCleanups()
667+
gcCleanups.executed.Add(int64(b.n))
639668

640669
atomic.Store(&b.n, 0) // Synchronize with markroot. See comment in cleanupBlockHeader.
641670
gcCleanups.free.push(&b.lfnode)

src/runtime/mfinal.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ const (
4444
)
4545

4646
var (
47-
finlock mutex // protects the following variables
48-
fing *g // goroutine that runs finalizers
49-
finq *finBlock // list of finalizers that are to be executed
50-
finc *finBlock // cache of free blocks
51-
finptrmask [finBlockSize / goarch.PtrSize / 8]byte
47+
finlock mutex // protects the following variables
48+
fing *g // goroutine that runs finalizers
49+
finq *finBlock // list of finalizers that are to be executed
50+
finc *finBlock // cache of free blocks
51+
finptrmask [finBlockSize / goarch.PtrSize / 8]byte
52+
finqueued uint64 // monotonic count of queued finalizers
53+
finexecuted uint64 // monotonic count of executed finalizers
5254
)
5355

5456
var allfin *finBlock // list of all blocks
@@ -108,6 +110,7 @@ func queuefinalizer(p unsafe.Pointer, fn *funcval, nret uintptr, fint *_type, ot
108110
}
109111

110112
lock(&finlock)
113+
111114
if finq == nil || finq.cnt == uint32(len(finq.fin)) {
112115
if finc == nil {
113116
finc = (*finBlock)(persistentalloc(finBlockSize, 0, &memstats.gcMiscSys))
@@ -141,6 +144,7 @@ func queuefinalizer(p unsafe.Pointer, fn *funcval, nret uintptr, fint *_type, ot
141144
f.fint = fint
142145
f.ot = ot
143146
f.arg = p
147+
finqueued++
144148
unlock(&finlock)
145149
fingStatus.Or(fingWake)
146150
}
@@ -177,6 +181,14 @@ func finalizercommit(gp *g, lock unsafe.Pointer) bool {
177181
return true
178182
}
179183

184+
func finReadQueueStats() (queued, executed uint64) {
185+
lock(&finlock)
186+
queued = finqueued
187+
executed = finexecuted
188+
unlock(&finlock)
189+
return
190+
}
191+
180192
// This is the goroutine that runs all of the finalizers.
181193
func runFinalizers() {
182194
var (
@@ -204,7 +216,8 @@ func runFinalizers() {
204216
racefingo()
205217
}
206218
for fb != nil {
207-
for i := fb.cnt; i > 0; i-- {
219+
n := fb.cnt
220+
for i := n; i > 0; i-- {
208221
f := &fb.fin[i-1]
209222

210223
var regs abi.RegArgs
@@ -270,6 +283,7 @@ func runFinalizers() {
270283
}
271284
next := fb.next
272285
lock(&finlock)
286+
finexecuted += uint64(n)
273287
fb.next = finc
274288
finc = fb
275289
unlock(&finlock)

src/runtime/mgc.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,6 +1337,19 @@ func gcMarkTermination(stw worldStop) {
13371337
printunlock()
13381338
}
13391339

1340+
// Print finalizer/cleanup queue length. Like gctrace, do this before the next GC starts.
1341+
// The fact that the next GC might start is not that problematic here, but acts as a convenient
1342+
// lock on printing this information (so it cannot overlap with itself from the next GC cycle).
1343+
if debug.checkfinalizers > 0 {
1344+
fq, fe := finReadQueueStats()
1345+
fn := max(int64(fq)-int64(fe), 0)
1346+
1347+
cq, ce := gcCleanups.readQueueStats()
1348+
cn := max(int64(cq)-int64(ce), 0)
1349+
1350+
println("checkfinalizers: queue:", fn, "finalizers +", cn, "cleanups")
1351+
}
1352+
13401353
// Set any arena chunks that were deferred to fault.
13411354
lock(&userArenaState.lock)
13421355
faultList := userArenaState.fault

src/runtime/proc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5743,6 +5743,8 @@ func (pp *p) destroy() {
57435743
pp.raceprocctx = 0
57445744
}
57455745
pp.gcAssistTime = 0
5746+
gcCleanups.queued += pp.cleanupsQueued
5747+
pp.cleanupsQueued = 0
57465748
pp.status = _Pdead
57475749
}
57485750

src/runtime/runtime2.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,8 @@ type p struct {
732732
timers timers
733733

734734
// Cleanups.
735-
cleanups *cleanupBlock
735+
cleanups *cleanupBlock
736+
cleanupsQueued uint64 // monotonic count of cleanups queued by this P
736737

737738
// maxStackScanDelta accumulates the amount of stack space held by
738739
// live goroutines (i.e. those eligible for stack scanning).

0 commit comments

Comments
 (0)