Skip to content

Commit

Permalink
runtime: refactor runtime->tracer API to appear more like a lock
Browse files Browse the repository at this point in the history
Currently the execution tracer synchronizes with itself using very
heavyweight operations. As a result, it's totally fine for most of the
tracer code to look like:

    if traceEnabled() {
	traceXXX(...)
    }

However, if we want to make that synchronization more lightweight (as
issue #60773 proposes), then this is insufficient. In particular, we
need to make sure the tracer can't observe an inconsistency between g
atomicstatus and the event that would be emitted for a particular
g transition. This means making the g status change appear to happen
atomically with the corresponding trace event being written out from the
perspective of the tracer.

This requires a change in API to something more like a lock. While we're
here, we might as well make sure that trace events can *only* be emitted
while this lock is held. This change introduces such an API:
traceAcquire, which returns a value that can emit events, and
traceRelease, which requires the value that was returned by
traceAcquire. In practice, this won't be a real lock, it'll be more like
a seqlock.

For the current tracer, this API is completely overkill and the value
returned by traceAcquire basically just checks trace.enabled. But it's
necessary for the tracer described in #60773 and we can implement that
more cleanly if we do this refactoring now instead of later.

For #60773.

Change-Id: Ibb9ff5958376339fafc2b5180aef65cf2ba18646
Reviewed-on: https://go-review.googlesource.com/c/go/+/515635
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
  • Loading branch information
mknyszek authored and gopherbot committed Nov 9, 2023
1 parent e3585c6 commit f119abb
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 177 deletions.
24 changes: 15 additions & 9 deletions src/runtime/debugcall.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,12 @@ func debugCallWrap(dispatch uintptr) {
gp.schedlink = 0

// Park the calling goroutine.
if traceEnabled() {
traceGoPark(traceBlockDebugCall, 1)
}
trace := traceAcquire()
casGToWaiting(gp, _Grunning, waitReasonDebugCall)
if trace.ok() {
trace.GoPark(traceBlockDebugCall, 1)
traceRelease(trace)
}
dropg()

// Directly execute the new goroutine. The debug
Expand Down Expand Up @@ -225,19 +227,23 @@ func debugCallWrap1() {
// Switch back to the calling goroutine. At some point
// the scheduler will schedule us again and we'll
// finish exiting.
if traceEnabled() {
traceGoSched()
}
trace := traceAcquire()
casgstatus(gp, _Grunning, _Grunnable)
if trace.ok() {
trace.GoSched()
traceRelease(trace)
}
dropg()
lock(&sched.lock)
globrunqput(gp)
unlock(&sched.lock)

if traceEnabled() {
traceGoUnpark(callingG, 0)
}
trace = traceAcquire()
casgstatus(callingG, _Gwaiting, _Grunnable)
if trace.ok() {
trace.GoUnpark(callingG, 0)
traceRelease(trace)
}
execute(callingG, true)
})
}
Expand Down
20 changes: 14 additions & 6 deletions src/runtime/mcentral.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ func (c *mcentral) cacheSpan() *mspan {
deductSweepCredit(spanBytes, 0)

traceDone := false
if traceEnabled() {
traceGCSweepStart()
trace := traceAcquire()
if trace.ok() {
trace.GCSweepStart()
traceRelease(trace)
}

// If we sweep spanBudget spans without finding any free
Expand Down Expand Up @@ -157,9 +159,11 @@ func (c *mcentral) cacheSpan() *mspan {
}
sweep.active.end(sl)
}
if traceEnabled() {
traceGCSweepDone()
trace = traceAcquire()
if trace.ok() {
trace.GCSweepDone()
traceDone = true
traceRelease(trace)
}

// We failed to get a span from the mcentral so get one from mheap.
Expand All @@ -170,8 +174,12 @@ func (c *mcentral) cacheSpan() *mspan {

// At this point s is a span that should have free slots.
havespan:
if traceEnabled() && !traceDone {
traceGCSweepDone()
if !traceDone {
trace := traceAcquire()
if trace.ok() {
trace.GCSweepDone()
traceRelease(trace)
}
}
n := int(s.nelems) - int(s.allocCount)
if n == 0 || s.freeindex == s.nelems || s.allocCount == s.nelems {
Expand Down
12 changes: 8 additions & 4 deletions src/runtime/mgc.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,8 +647,10 @@ func gcStart(trigger gcTrigger) {
// Update it under gcsema to avoid gctrace getting wrong values.
work.userForced = trigger.kind == gcTriggerCycle

if traceEnabled() {
traceGCStart()
trace := traceAcquire()
if trace.ok() {
trace.GCStart()
traceRelease(trace)
}

// Check that all Ps have finished deferred mcache flushes.
Expand Down Expand Up @@ -989,8 +991,10 @@ func gcMarkTermination() {
mp.traceback = 0
casgstatus(curgp, _Gwaiting, _Grunning)

if traceEnabled() {
traceGCDone()
trace := traceAcquire()
if trace.ok() {
trace.GCDone()
traceRelease(trace)
}

// all done
Expand Down
27 changes: 21 additions & 6 deletions src/runtime/mgcmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,11 @@ retry:
// If the CPU limiter is enabled, intentionally don't
// assist to reduce the amount of CPU time spent in the GC.
if traced {
traceGCMarkAssistDone()
trace := traceAcquire()
if trace.ok() {
trace.GCMarkAssistDone()
traceRelease(trace)
}
}
return
}
Expand Down Expand Up @@ -461,15 +465,22 @@ retry:
// We were able to steal all of the credit we
// needed.
if traced {
traceGCMarkAssistDone()
trace := traceAcquire()
if trace.ok() {
trace.GCMarkAssistDone()
traceRelease(trace)
}
}
return
}
}

if traceEnabled() && !traced {
traced = true
traceGCMarkAssistStart()
trace := traceAcquire()
if trace.ok() {
traced = true
trace.GCMarkAssistStart()
traceRelease(trace)
}
}

// Perform assist work
Expand Down Expand Up @@ -515,7 +526,11 @@ retry:
// this G's assist debt, or the GC cycle is over.
}
if traced {
traceGCMarkAssistDone()
trace := traceAcquire()
if trace.ok() {
trace.GCMarkAssistDone()
traceRelease(trace)
}
}
}

Expand Down
24 changes: 16 additions & 8 deletions src/runtime/mgcpacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,9 +807,11 @@ func (c *gcControllerState) findRunnableGCWorker(pp *p, now int64) (*g, int64) {

// Run the background mark worker.
gp := node.gp.ptr()
trace := traceAcquire()
casgstatus(gp, _Gwaiting, _Grunnable)
if traceEnabled() {
traceGoUnpark(gp, 0)
if trace.ok() {
trace.GoUnpark(gp, 0)
traceRelease(trace)
}
return gp, now
}
Expand All @@ -828,8 +830,10 @@ func (c *gcControllerState) resetLive(bytesMarked uint64) {
c.triggered = ^uint64(0) // Reset triggered.

// heapLive was updated, so emit a trace event.
if traceEnabled() {
traceHeapAlloc(bytesMarked)
trace := traceAcquire()
if trace.ok() {
trace.HeapAlloc(bytesMarked)
traceRelease(trace)
}
}

Expand All @@ -856,10 +860,12 @@ func (c *gcControllerState) markWorkerStop(mode gcMarkWorkerMode, duration int64

func (c *gcControllerState) update(dHeapLive, dHeapScan int64) {
if dHeapLive != 0 {
trace := traceAcquire()
live := gcController.heapLive.Add(dHeapLive)
if traceEnabled() {
if trace.ok() {
// gcController.heapLive changed.
traceHeapAlloc(live)
trace.HeapAlloc(live)
traceRelease(trace)
}
}
if gcBlackenEnabled == 0 {
Expand Down Expand Up @@ -1428,8 +1434,10 @@ func gcControllerCommit() {

// TODO(mknyszek): This isn't really accurate any longer because the heap
// goal is computed dynamically. Still useful to snapshot, but not as useful.
if traceEnabled() {
traceHeapGoal()
trace := traceAcquire()
if trace.ok() {
trace.HeapGoal()
traceRelease(trace)
}

trigger, heapGoal := gcController.trigger()
Expand Down
18 changes: 12 additions & 6 deletions src/runtime/mgcsweep.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,10 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
throw("mspan.sweep: bad span state")
}

if traceEnabled() {
traceGCSweepSpan(s.npages * _PageSize)
trace := traceAcquire()
if trace.ok() {
trace.GCSweepSpan(s.npages * _PageSize)
traceRelease(trace)
}

mheap_.pagesSwept.Add(int64(s.npages))
Expand Down Expand Up @@ -889,8 +891,10 @@ func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
return
}

if traceEnabled() {
traceGCSweepStart()
trace := traceAcquire()
if trace.ok() {
trace.GCSweepStart()
traceRelease(trace)
}

// Fix debt if necessary.
Expand Down Expand Up @@ -929,8 +933,10 @@ retry:
}
}

if traceEnabled() {
traceGCSweepDone()
trace = traceAcquire()
if trace.ok() {
trace.GCSweepDone()
traceRelease(trace)
}
}

Expand Down
18 changes: 12 additions & 6 deletions src/runtime/mheap.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,8 +791,10 @@ func (h *mheap) reclaim(npage uintptr) {
// traceGCSweepStart/Done pair on the P.
mp := acquirem()

if traceEnabled() {
traceGCSweepStart()
trace := traceAcquire()
if trace.ok() {
trace.GCSweepStart()
traceRelease(trace)
}

arenas := h.sweepArenas
Expand Down Expand Up @@ -839,8 +841,10 @@ func (h *mheap) reclaim(npage uintptr) {
unlock(&h.lock)
}

if traceEnabled() {
traceGCSweepDone()
trace = traceAcquire()
if trace.ok() {
trace.GCSweepDone()
traceRelease(trace)
}
releasem(mp)
}
Expand Down Expand Up @@ -911,10 +915,12 @@ func (h *mheap) reclaimChunk(arenas []arenaIdx, pageIdx, n uintptr) uintptr {
n -= uintptr(len(inUse) * 8)
}
sweep.active.end(sl)
if traceEnabled() {
trace := traceAcquire()
if trace.ok() {
unlock(&h.lock)
// Account for pages scanned but not reclaimed.
traceGCSweepSpan((n0 - nFreed) * pageSize)
trace.GCSweepSpan((n0 - nFreed) * pageSize)
traceRelease(trace)
lock(&h.lock)
}

Expand Down
Loading

0 comments on commit f119abb

Please sign in to comment.