Skip to content

Commit e5ce13c

Browse files
committed
runtime: add option to scavenge with lock held throughout
This change adds a "locked" parameter to scavenge() and scavengeone() which allows these methods to be run with the heap lock acquired, and synchronously with respect to others which acquire the heap lock. This mode is necessary for both heap-growth scavenging (multiple asynchronous scavengers here could be problematic) and debug.FreeOSMemory. Updates #35112. Change-Id: I24eea8e40f971760999c980981893676b4c9b666 Reviewed-on: https://go-review.googlesource.com/c/go/+/195699 Reviewed-by: Austin Clements <[email protected]> Reviewed-by: Keith Randall <[email protected]>
1 parent e1ddf05 commit e5ce13c

File tree

3 files changed

+41
-19
lines changed

3 files changed

+41
-19
lines changed

src/runtime/export_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -866,9 +866,9 @@ func (p *PageAlloc) Bounds() (ChunkIdx, ChunkIdx) {
866866
func (p *PageAlloc) PallocData(i ChunkIdx) *PallocData {
867867
return (*PallocData)(&((*pageAlloc)(p).chunks[i]))
868868
}
869-
func (p *PageAlloc) Scavenge(nbytes uintptr) (r uintptr) {
869+
func (p *PageAlloc) Scavenge(nbytes uintptr, locked bool) (r uintptr) {
870870
systemstack(func() {
871-
r = (*pageAlloc)(p).scavenge(nbytes)
871+
r = (*pageAlloc)(p).scavenge(nbytes, locked)
872872
})
873873
return
874874
}

src/runtime/mgcscavenge.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -421,16 +421,17 @@ func bgscavenge(c chan int) {
421421
//
422422
// Returns the amount of memory scavenged in bytes.
423423
//
424-
// s.mheapLock must not be locked.
424+
// If locked == false, s.mheapLock must not be locked. If locked == true,
425+
// s.mheapLock must be locked.
425426
//
426427
// Must run on the system stack because scavengeOne must run on the
427428
// system stack.
428429
//
429430
//go:systemstack
430-
func (s *pageAlloc) scavenge(nbytes uintptr) uintptr {
431+
func (s *pageAlloc) scavenge(nbytes uintptr, locked bool) uintptr {
431432
released := uintptr(0)
432433
for released < nbytes {
433-
r := s.scavengeOne(nbytes - released)
434+
r := s.scavengeOne(nbytes-released, locked)
434435
if r == 0 {
435436
// Nothing left to scavenge! Give up.
436437
break
@@ -457,11 +458,14 @@ func (s *pageAlloc) resetScavengeAddr() {
457458
//
458459
// Should it exhaust the heap, it will return 0 and set s.scavAddr to minScavAddr.
459460
//
460-
// s.mheapLock must not be locked. Must be run on the system stack because it
461-
// acquires the heap lock.
461+
// If locked == false, s.mheapLock must not be locked.
462+
// If locked == true, s.mheapLock must be locked.
463+
//
464+
// Must be run on the system stack because it either acquires the heap lock
465+
// or executes with the heap lock acquired.
462466
//
463467
//go:systemstack
464-
func (s *pageAlloc) scavengeOne(max uintptr) uintptr {
468+
func (s *pageAlloc) scavengeOne(max uintptr, locked bool) uintptr {
465469
// Calculate the maximum number of pages to scavenge.
466470
//
467471
// This should be alignUp(max, pageSize) / pageSize but max can and will
@@ -483,10 +487,22 @@ func (s *pageAlloc) scavengeOne(max uintptr) uintptr {
483487
minPages = 1
484488
}
485489

486-
lock(s.mheapLock)
490+
// Helpers for locking and unlocking only if locked == false.
491+
lockHeap := func() {
492+
if !locked {
493+
lock(s.mheapLock)
494+
}
495+
}
496+
unlockHeap := func() {
497+
if !locked {
498+
unlock(s.mheapLock)
499+
}
500+
}
501+
502+
lockHeap()
487503
top := chunkIndex(s.scavAddr)
488504
if top < s.start {
489-
unlock(s.mheapLock)
505+
unlockHeap()
490506
return 0
491507
}
492508

@@ -498,10 +514,10 @@ func (s *pageAlloc) scavengeOne(max uintptr) uintptr {
498514
// If we found something, scavenge it and return!
499515
if npages != 0 {
500516
s.scavengeRangeLocked(ci, base, npages)
501-
unlock(s.mheapLock)
517+
unlockHeap()
502518
return uintptr(npages) * pageSize
503519
}
504-
unlock(s.mheapLock)
520+
unlockHeap()
505521

506522
// Slow path: iterate optimistically looking for any free and unscavenged page.
507523
// If we think we see something, stop and verify it!
@@ -528,30 +544,30 @@ func (s *pageAlloc) scavengeOne(max uintptr) uintptr {
528544
}
529545

530546
// We found a candidate, so let's lock and verify it.
531-
lock(s.mheapLock)
547+
lockHeap()
532548

533549
// Find, verify, and scavenge if we can.
534550
chunk := &s.chunks[i]
535551
base, npages := chunk.findScavengeCandidate(pallocChunkPages-1, minPages, maxPages)
536552
if npages > 0 {
537553
// We found memory to scavenge! Mark the bits and report that up.
538554
s.scavengeRangeLocked(i, base, npages)
539-
unlock(s.mheapLock)
555+
unlockHeap()
540556
return uintptr(npages) * pageSize
541557
}
542558

543559
// We were fooled, let's take this opportunity to move the scavAddr
544560
// all the way down to where we searched as scavenged for future calls
545561
// and keep iterating.
546562
s.scavAddr = chunkBase(i-1) + pallocChunkPages*pageSize - 1
547-
unlock(s.mheapLock)
563+
unlockHeap()
548564
}
549565

550-
lock(s.mheapLock)
566+
lockHeap()
551567
// We couldn't find anything, so signal that there's nothing left
552568
// to scavenge.
553569
s.scavAddr = minScavAddr
554-
unlock(s.mheapLock)
570+
unlockHeap()
555571

556572
return 0
557573
}

src/runtime/mgcscavenge_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,19 +364,25 @@ func TestPageAllocScavenge(t *testing.T) {
364364
}
365365
for name, v := range tests {
366366
v := v
367-
t.Run(name, func(t *testing.T) {
367+
runTest := func(t *testing.T, locked bool) {
368368
b := NewPageAlloc(v.beforeAlloc, v.beforeScav)
369369
defer FreePageAlloc(b)
370370

371371
for iter, h := range v.expect {
372-
if got := b.Scavenge(h.request); got != h.expect {
372+
if got := b.Scavenge(h.request, locked); got != h.expect {
373373
t.Fatalf("bad scavenge #%d: want %d, got %d", iter+1, h.expect, got)
374374
}
375375
}
376376
want := NewPageAlloc(v.beforeAlloc, v.afterScav)
377377
defer FreePageAlloc(want)
378378

379379
checkPageAlloc(t, want, b)
380+
}
381+
t.Run(name, func(t *testing.T) {
382+
runTest(t, false)
383+
})
384+
t.Run(name+"Locked", func(t *testing.T) {
385+
runTest(t, true)
380386
})
381387
}
382388
}

0 commit comments

Comments
 (0)