Skip to content

Commit

Permalink
[release-branch.go1.22] runtime: update large object stats before fre…
Browse files Browse the repository at this point in the history
…eSpan 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 36d32f6)
Reviewed-on: https://go-review.googlesource.com/c/go/+/584339
Reviewed-by: Carlos Amedee <carlos@golang.org>
  • Loading branch information
mknyszek authored and cagedmantis committed May 24, 2024
1 parent 6b89e7d commit 3c96ae0
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 10 deletions.
35 changes: 35 additions & 0 deletions src/runtime/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
23 changes: 13 additions & 10 deletions src/runtime/mgcsweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 3c96ae0

Please sign in to comment.