aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2024-05-05 21:17:27 +0000
committerCarlos Amedee <carlos@golang.org>2024-05-24 21:00:06 +0000
commit3c96ae08701dd3ed66e0f8a81a80e3336a4d9aae (patch)
treed3db0016a622d732401341ab0b1cf83e8b0b962f
parent6b89e7dc5a8f6c86db6dbb72f756bd555e8552e0 (diff)
downloadgo-3c96ae08701dd3ed66e0f8a81a80e3336a4d9aae.tar.xz
[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 <mknyszek@google.com> Reviewed-by: Felix Geisendörfer <felix.geisendoerfer@datadoghq.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> (cherry picked from commit 36d32f68f41561fb64677297e3733f5d5b866c2a) Reviewed-on: https://go-review.googlesource.com/c/go/+/584339 Reviewed-by: Carlos Amedee <carlos@golang.org>
-rw-r--r--src/runtime/metrics_test.go35
-rw-r--r--src/runtime/mgcsweep.go23
2 files changed, 48 insertions, 10 deletions
diff --git a/src/runtime/metrics_test.go b/src/runtime/metrics_test.go
index d7f41334cd..5866107275 100644
--- a/src/runtime/metrics_test.go
+++ b/src/runtime/metrics_test.go
@@ -1290,3 +1290,38 @@ func (w *contentionWorker) run() {
for w.fn() {
}
}
+
+func TestMetricHeapUnusedLargeObjectOverflow(t *testing.T) {
+ // This test makes sure /memory/classes/heap/unused:bytes
+ // doesn't overflow when allocating and deallocating large
+ // objects. It is a regression test for #67019.
+ done := make(chan struct{})
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for {
+ for i := 0; i < 10; i++ {
+ runtime.Escape(make([]byte, 1<<20))
+ }
+ runtime.GC()
+ select {
+ case <-done:
+ return
+ default:
+ }
+ }
+ }()
+ s := []metrics.Sample{
+ {Name: "/memory/classes/heap/unused:bytes"},
+ }
+ for i := 0; i < 1000; i++ {
+ metrics.Read(s)
+ if s[0].Value.Uint64() > 1<<40 {
+ t.Errorf("overflow")
+ break
+ }
+ }
+ done <- struct{}{}
+ wg.Wait()
+}
diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go
index 3dbe9bcec7..35be794947 100644
--- a/src/runtime/mgcsweep.go
+++ b/src/runtime/mgcsweep.go
@@ -770,6 +770,19 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
if nfreed != 0 {
// Free large object span to heap.
+ // Count the free in the consistent, external stats.
+ //
+ // Do this before freeSpan, which might update heapStats' inHeap
+ // value. If it does so, then metrics that subtract object footprint
+ // from inHeap might overflow. See #67019.
+ stats := memstats.heapStats.acquire()
+ atomic.Xadd64(&stats.largeFreeCount, 1)
+ atomic.Xadd64(&stats.largeFree, int64(size))
+ memstats.heapStats.release()
+
+ // Count the free in the inconsistent, internal stats.
+ gcController.totalFree.Add(int64(size))
+
// NOTE(rsc,dvyukov): The original implementation of efence
// in CL 22060046 used sysFree instead of sysFault, so that
// the operating system would eventually give the memory
@@ -802,16 +815,6 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
// invalid pointer. See arena.go:(*mheap).allocUserArenaChunk.
*(*uintptr)(unsafe.Pointer(&s.largeType)) = 0
}
-
- // Count the free in the consistent, external stats.
- stats := memstats.heapStats.acquire()
- atomic.Xadd64(&stats.largeFreeCount, 1)
- atomic.Xadd64(&stats.largeFree, int64(size))
- memstats.heapStats.release()
-
- // Count the free in the inconsistent, internal stats.
- gcController.totalFree.Add(int64(size))
-
return true
}