diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2024-02-20 21:16:33 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-03-22 16:12:01 +0000 |
| commit | c9c88d73f5cb58d0e40cb1b0481c102e6b8b24f1 (patch) | |
| tree | a5b170d5fbb4fb4ee00d72eb14bffdcc6e6bf027 /src/runtime/trace2runtime.go | |
| parent | 27f41bb15391668fa8ba18561efe364bab9b8312 (diff) | |
| download | go-c9c88d73f5cb58d0e40cb1b0481c102e6b8b24f1.tar.xz | |
runtime: add tracing for iter.Pull
This change resolves a TODO in the coroutine switch implementation (used
exclusively by iter.Pull at the moment) to enable tracing. This was
blocked on eliminating the atomic load in the tracer's "off" path
(completed in the previous CL in this series) and the addition of new
tracer events to minimize the overhead of tracing in this circumstance.
This change introduces 3 new event types to support coroutine switches:
GoCreateBlocked, GoSwitch, and GoSwitchDestroy.
GoCreateBlocked needs to be introduced because the goroutine created for
the coroutine starts out in a blocked state. There's no way to represent
this in the tracer right now, so we need a new event for it.
GoSwitch represents the actual coroutine switch, which conceptually
consists of a GoUnblock, a GoBlock, and a GoStart event in series
(unblocking the next goroutine to run, blocking the current goroutine,
and then starting the next goroutine to run).
GoSwitchDestroy is closely related to GoSwitch, implementing the same
semantics except that GoBlock is replaced with GoDestroy. This is used
when exiting the coroutine.
The implementation of all this is fairly straightforward, and the trace
parser simply translates GoSwitch* into the three constituent events.
Because GoSwitch and GoSwitchDestroy imply a GoUnblock and a GoStart,
they need to synchronize with other past and future GoStart events to
create a correct partial ordering in the trace. Therefore, these events
need a sequence number for the goroutine that will be unblocked and
started.
Also, while implementing this, I noticed that the coroutine
implementation is actually buggy with respect to LockOSThread. In fact,
it blatantly disregards its invariants without an explicit panic. While
such a case is likely to be rare (and inefficient!) we should decide how
iter.Pull behaves with respect to runtime.LockOSThread.
Lastly, this change also bumps the trace version from Go 1.22 to Go
1.23. We're adding events that are incompatible with a Go 1.22 parser,
but Go 1.22 traces are all valid Go 1.23 traces, so the newer parser
supports both (and the CL otherwise updates the Go 1.22 definitions of
events and such). We may want to reconsider the structure and naming of
some of these packages though; it could quickly get confusing.
For #61897.
Change-Id: I96897a46d5852c02691cde9f957dc6c13ef4d8e7
Reviewed-on: https://go-review.googlesource.com/c/go/+/565937
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Diffstat (limited to 'src/runtime/trace2runtime.go')
| -rw-r--r-- | src/runtime/trace2runtime.go | 40 |
1 files changed, 33 insertions, 7 deletions
diff --git a/src/runtime/trace2runtime.go b/src/runtime/trace2runtime.go index 7b88c258ba..b391fd79ff 100644 --- a/src/runtime/trace2runtime.go +++ b/src/runtime/trace2runtime.go @@ -389,9 +389,13 @@ func (tl traceLocker) GCMarkAssistDone() { } // GoCreate emits a GoCreate event. -func (tl traceLocker) GoCreate(newg *g, pc uintptr) { +func (tl traceLocker) GoCreate(newg *g, pc uintptr, blocked bool) { newg.trace.setStatusTraced(tl.gen) - tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoCreate, traceArg(newg.goid), tl.startPC(pc), tl.stack(2)) + ev := traceEvGoCreate + if blocked { + ev = traceEvGoCreateBlocked + } + tl.eventWriter(traceGoRunning, traceProcRunning).commit(ev, traceArg(newg.goid), tl.startPC(pc), tl.stack(2)) } // GoStart emits a GoStart event. @@ -442,14 +446,36 @@ func (tl traceLocker) GoPark(reason traceBlockReason, skip int) { func (tl traceLocker) GoUnpark(gp *g, skip int) { // Emit a GoWaiting status if necessary for the unblocked goroutine. w := tl.eventWriter(traceGoRunning, traceProcRunning) - if !gp.trace.statusWasTraced(tl.gen) && gp.trace.acquireStatus(tl.gen) { - // Careful: don't use the event writer. We never want status or in-progress events - // to trigger more in-progress events. - w.w = w.w.writeGoStatus(gp.goid, -1, traceGoWaiting, gp.inMarkAssist) - } + // Careful: don't use the event writer. We never want status or in-progress events + // to trigger more in-progress events. + w.w = emitUnblockStatus(w.w, gp, tl.gen) w.commit(traceEvGoUnblock, traceArg(gp.goid), gp.trace.nextSeq(tl.gen), tl.stack(skip)) } +// GoCoroswitch emits a GoSwitch event. If destroy is true, the calling goroutine +// is simultaneously being destroyed. +func (tl traceLocker) GoSwitch(nextg *g, destroy bool) { + // Emit a GoWaiting status if necessary for the unblocked goroutine. + w := tl.eventWriter(traceGoRunning, traceProcRunning) + // Careful: don't use the event writer. We never want status or in-progress events + // to trigger more in-progress events. + w.w = emitUnblockStatus(w.w, nextg, tl.gen) + ev := traceEvGoSwitch + if destroy { + ev = traceEvGoSwitchDestroy + } + w.commit(ev, traceArg(nextg.goid), nextg.trace.nextSeq(tl.gen)) +} + +// emitUnblockStatus emits a GoStatus GoWaiting event for a goroutine about to be +// unblocked to the trace writer. +func emitUnblockStatus(w traceWriter, gp *g, gen uintptr) traceWriter { + if !gp.trace.statusWasTraced(gen) && gp.trace.acquireStatus(gen) { + w = w.writeGoStatus(gp.goid, -1, traceGoWaiting, gp.inMarkAssist) + } + return w +} + // GoSysCall emits a GoSyscallBegin event. // // Must be called with a valid P. |
