]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: add a non-functional memory limit to the pacer
authorMichael Anthony Knyszek <mknyszek@google.com>
Wed, 2 Mar 2022 20:49:36 +0000 (20:49 +0000)
committerMichael Knyszek <mknyszek@google.com>
Tue, 3 May 2022 15:12:04 +0000 (15:12 +0000)
Nothing much to see here, just some plumbing to make latter CLs smaller
and clearer.

For #48409.

Change-Id: Ide23812d5553e0b6eea5616c277d1a760afb4ed0
Reviewed-on: https://go-review.googlesource.com/c/go/+/393401
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
src/runtime/debug/stubs.go
src/runtime/export_test.go
src/runtime/mgc.go
src/runtime/mgcpacer.go

index 2cba136044bbce34f3ca4533e326392be694ca8b..913d4b9b09e56a6d3dc39c3b4698f9204ea61fd3 100644 (file)
@@ -15,3 +15,4 @@ func setMaxStack(int) int
 func setGCPercent(int32) int32
 func setPanicOnFault(bool) bool
 func setMaxThreads(int) int
+func setMemoryLimit(int64) int64
index c364e5bea9814c86d60e3a5a9374a6d69dda122c..2925c1b0a632e47be3f3b3a0975915c3298bad72 100644 (file)
@@ -1263,7 +1263,7 @@ func NewGCController(gcPercent int) *GCController {
        // space.
        g := Escape(new(GCController))
        g.gcControllerState.test = true // Mark it as a test copy.
-       g.init(int32(gcPercent))
+       g.init(int32(gcPercent), maxInt64)
        return g
 }
 
index d7e373b5d85887543589b681735eed6280bad764..75cd32ee6fb3e09e7ca39f48479a7918d1ef95d4 100644 (file)
@@ -158,7 +158,8 @@ func gcinit() {
 
        // Initialize GC pacer state.
        // Use the environment variable GOGC for the initial gcPercent value.
-       gcController.init(readGOGC())
+       // Use the environment variable GOMEMLIMIT for the initial memoryLimit value.
+       gcController.init(readGOGC(), maxInt64)
 
        work.startSema = 1
        work.markDoneSema = 1
index e106824c951c1c4802c6574f8a5482fe1ba808f6..2824b73878b65654b5a7d4cb01ea5cae20b6f1ff 100644 (file)
@@ -90,12 +90,21 @@ func init() {
 var gcController gcControllerState
 
 type gcControllerState struct {
-
        // Initialized from GOGC. GOGC=off means no GC.
        gcPercent atomic.Int32
 
        _ uint32 // padding so following 64-bit values are 8-byte aligned
 
+       // memoryLimit is the soft memory limit in bytes.
+       //
+       // Initialized from GOMEMLIMIT. GOMEMLIMIT=off is equivalent to MaxInt64
+       // which means no soft memory limit in practice.
+       //
+       // This is an int64 instead of a uint64 to more easily maintain parity with
+       // the SetMemoryLimit API, which sets a maximum at MaxInt64. This value
+       // should never be negative.
+       memoryLimit atomic.Int64
+
        // heapMinimum is the minimum heap size at which to trigger GC.
        // For small heaps, this overrides the usual GOGC*live set rule.
        //
@@ -352,7 +361,7 @@ type gcControllerState struct {
        _ cpu.CacheLinePad
 }
 
-func (c *gcControllerState) init(gcPercent int32) {
+func (c *gcControllerState) init(gcPercent int32, memoryLimit int64) {
        c.heapMinimum = defaultHeapMinimum
 
        c.consMarkController = piController{
@@ -376,8 +385,9 @@ func (c *gcControllerState) init(gcPercent int32) {
                max: 1000,
        }
 
-       // This will also compute and set the GC trigger and goal.
        c.setGCPercent(gcPercent)
+       c.setMemoryLimit(memoryLimit)
+       c.commit()
 }
 
 // startCycle resets the GC controller's state and computes estimates
@@ -1051,11 +1061,9 @@ func (c *gcControllerState) effectiveGrowthRatio() float64 {
        return egogc
 }
 
-// setGCPercent updates gcPercent and all related pacer state.
+// setGCPercent updates gcPercent. commit must be called after.
 // Returns the old value of gcPercent.
 //
-// Calls gcControllerState.commit.
-//
 // The world must be stopped, or mheap_.lock must be held.
 func (c *gcControllerState) setGCPercent(in int32) int32 {
        if !c.test {
@@ -1068,8 +1076,6 @@ func (c *gcControllerState) setGCPercent(in int32) int32 {
        }
        c.heapMinimum = defaultHeapMinimum * uint64(in) / 100
        c.gcPercent.Store(in)
-       // Update pacing in response to gcPercent change.
-       c.commit()
 
        return out
 }
@@ -1080,6 +1086,7 @@ func setGCPercent(in int32) (out int32) {
        systemstack(func() {
                lock(&mheap_.lock)
                out = gcController.setGCPercent(in)
+               gcController.commit()
                gcPaceSweeper(gcController.trigger)
                gcPaceScavenger(gcController.heapGoal, gcController.lastHeapGoal)
                unlock(&mheap_.lock)
@@ -1105,6 +1112,56 @@ func readGOGC() int32 {
        return 100
 }
 
+// setMemoryLimit updates memoryLimit. commit must be called after
+// Returns the old value of memoryLimit.
+//
+// The world must be stopped, or mheap_.lock must be held.
+func (c *gcControllerState) setMemoryLimit(in int64) int64 {
+       if !c.test {
+               assertWorldStoppedOrLockHeld(&mheap_.lock)
+       }
+
+       out := c.memoryLimit.Load()
+       if in >= 0 {
+               c.memoryLimit.Store(in)
+       }
+
+       return out
+}
+
+//go:linkname setMemoryLimit runtime/debug.setMemoryLimit
+func setMemoryLimit(in int64) (out int64) {
+       // Run on the system stack since we grab the heap lock.
+       systemstack(func() {
+               lock(&mheap_.lock)
+               out = gcController.setMemoryLimit(in)
+               if in < 0 || out == in {
+                       // If we're just checking the value or not changing
+                       // it, there's no point in doing the rest.
+                       unlock(&mheap_.lock)
+                       return
+               }
+               gcController.commit()
+               gcPaceSweeper(gcController.trigger)
+               gcPaceScavenger(gcController.heapGoal, gcController.lastHeapGoal)
+               unlock(&mheap_.lock)
+       })
+       return out
+}
+
+func readGOMEMLIMIT() int64 {
+       p := gogetenv("GOMEMLIMIT")
+       if p == "" || p == "off" {
+               return maxInt64
+       }
+       n, ok := parseByteCount(p)
+       if !ok {
+               print("GOMEMLIMIT=", p, "\n")
+               throw("malformed GOMEMLIMIT; see `go doc runtime/debug.SetMemoryLimit`")
+       }
+       return n
+}
+
 type piController struct {
        kp float64 // Proportional constant.
        ti float64 // Integral time constant.