Skip to content

Commit 3c96ae0

Browse files
mknyszekcagedmantis
authored andcommitted
[release-branch.go1.22] runtime: update large object stats before freeSpan in sweep
Currently freeSpan is called before large object stats are updated when sweeping large objects. This means heapStats.inHeap might get subtracted before the large object is added to the largeFree field. The end result is that the /memory/classes/heap/unused:bytes metric, which subtracts live objects (alloc-free) from inHeap may overflow. Fix this by always updating the large object stats before calling freeSpan. For #67019. Fixes #67188. Change-Id: Ib02bd8dcd1cf8cd1bc0110b6141e74f678c10445 Reviewed-on: https://go-review.googlesource.com/c/go/+/583380 Auto-Submit: Michael Knyszek <[email protected]> Reviewed-by: Felix Geisendörfer <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]> (cherry picked from commit 36d32f6) Reviewed-on: https://go-review.googlesource.com/c/go/+/584339 Reviewed-by: Carlos Amedee <[email protected]>
1 parent 6b89e7d commit 3c96ae0

File tree

2 files changed

+48
-10
lines changed

2 files changed

+48
-10
lines changed

src/runtime/metrics_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,3 +1290,38 @@ func (w *contentionWorker) run() {
12901290
for w.fn() {
12911291
}
12921292
}
1293+
1294+
func TestMetricHeapUnusedLargeObjectOverflow(t *testing.T) {
1295+
// This test makes sure /memory/classes/heap/unused:bytes
1296+
// doesn't overflow when allocating and deallocating large
1297+
// objects. It is a regression test for #67019.
1298+
done := make(chan struct{})
1299+
var wg sync.WaitGroup
1300+
wg.Add(1)
1301+
go func() {
1302+
defer wg.Done()
1303+
for {
1304+
for i := 0; i < 10; i++ {
1305+
runtime.Escape(make([]byte, 1<<20))
1306+
}
1307+
runtime.GC()
1308+
select {
1309+
case <-done:
1310+
return
1311+
default:
1312+
}
1313+
}
1314+
}()
1315+
s := []metrics.Sample{
1316+
{Name: "/memory/classes/heap/unused:bytes"},
1317+
}
1318+
for i := 0; i < 1000; i++ {
1319+
metrics.Read(s)
1320+
if s[0].Value.Uint64() > 1<<40 {
1321+
t.Errorf("overflow")
1322+
break
1323+
}
1324+
}
1325+
done <- struct{}{}
1326+
wg.Wait()
1327+
}

src/runtime/mgcsweep.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,19 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
770770
if nfreed != 0 {
771771
// Free large object span to heap.
772772

773+
// Count the free in the consistent, external stats.
774+
//
775+
// Do this before freeSpan, which might update heapStats' inHeap
776+
// value. If it does so, then metrics that subtract object footprint
777+
// from inHeap might overflow. See #67019.
778+
stats := memstats.heapStats.acquire()
779+
atomic.Xadd64(&stats.largeFreeCount, 1)
780+
atomic.Xadd64(&stats.largeFree, int64(size))
781+
memstats.heapStats.release()
782+
783+
// Count the free in the inconsistent, internal stats.
784+
gcController.totalFree.Add(int64(size))
785+
773786
// NOTE(rsc,dvyukov): The original implementation of efence
774787
// in CL 22060046 used sysFree instead of sysFault, so that
775788
// the operating system would eventually give the memory
@@ -802,16 +815,6 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
802815
// invalid pointer. See arena.go:(*mheap).allocUserArenaChunk.
803816
*(*uintptr)(unsafe.Pointer(&s.largeType)) = 0
804817
}
805-
806-
// Count the free in the consistent, external stats.
807-
stats := memstats.heapStats.acquire()
808-
atomic.Xadd64(&stats.largeFreeCount, 1)
809-
atomic.Xadd64(&stats.largeFree, int64(size))
810-
memstats.heapStats.release()
811-
812-
// Count the free in the inconsistent, internal stats.
813-
gcController.totalFree.Add(int64(size))
814-
815818
return true
816819
}
817820

0 commit comments

Comments
 (0)