mirror of
https://github.com/golang/go.git
synced 2025-05-17 21:34:36 +00:00
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>
This commit is contained in:
parent
27f41bb153
commit
c9c88d73f5
@ -566,8 +566,12 @@ func (e Event) StateTransition() StateTransition {
|
||||
case go122.EvProcStatus:
|
||||
// N.B. ordering.advance populates e.base.extra.
|
||||
s = procStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), go122ProcStatus2ProcState[e.base.args[1]])
|
||||
case go122.EvGoCreate:
|
||||
s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoRunnable)
|
||||
case go122.EvGoCreate, go122.EvGoCreateBlocked:
|
||||
status := GoRunnable
|
||||
if e.base.typ == go122.EvGoCreateBlocked {
|
||||
status = GoWaiting
|
||||
}
|
||||
s = goStateTransition(GoID(e.base.args[0]), GoNotExist, status)
|
||||
s.Stack = Stack{table: e.table, id: stackID(e.base.args[1])}
|
||||
case go122.EvGoCreateSyscall:
|
||||
s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall)
|
||||
@ -586,7 +590,10 @@ func (e Event) StateTransition() StateTransition {
|
||||
s = goStateTransition(e.ctx.G, GoRunning, GoWaiting)
|
||||
s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
|
||||
s.Stack = e.Stack() // This event references the resource the event happened on.
|
||||
case go122.EvGoUnblock:
|
||||
case go122.EvGoUnblock, go122.EvGoSwitch, go122.EvGoSwitchDestroy:
|
||||
// N.B. GoSwitch and GoSwitchDestroy both emit additional events, but
|
||||
// the first thing they both do is unblock the goroutine they name,
|
||||
// identically to an unblock event (even their arguments match).
|
||||
s = goStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable)
|
||||
case go122.EvGoSyscallBegin:
|
||||
s = goStateTransition(e.ctx.G, GoRunning, GoSyscall)
|
||||
@ -646,6 +653,9 @@ var go122Type2Kind = [...]EventKind{
|
||||
go122.EvUserRegionBegin: EventRegionBegin,
|
||||
go122.EvUserRegionEnd: EventRegionEnd,
|
||||
go122.EvUserLog: EventLog,
|
||||
go122.EvGoSwitch: EventStateTransition,
|
||||
go122.EvGoSwitchDestroy: EventStateTransition,
|
||||
go122.EvGoCreateBlocked: EventStateTransition,
|
||||
evSync: EventSync,
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,11 @@ const (
|
||||
EvUserRegionBegin // trace.{Start,With}Region [timestamp, internal task ID, name string ID, stack ID]
|
||||
EvUserRegionEnd // trace.{End,With}Region [timestamp, internal task ID, name string ID, stack ID]
|
||||
EvUserLog // trace.Log [timestamp, internal task ID, key string ID, value string ID, stack]
|
||||
|
||||
// Coroutines. Added in Go 1.23.
|
||||
EvGoSwitch // goroutine switch (coroswitch) [timestamp, goroutine ID, goroutine seq]
|
||||
EvGoSwitchDestroy // goroutine switch and destroy [timestamp, goroutine ID, goroutine seq]
|
||||
EvGoCreateBlocked // goroutine creation (starts blocked) [timestamp, new goroutine ID, new stack ID, stack ID]
|
||||
)
|
||||
|
||||
// EventString returns the name of a Go 1.22 event.
|
||||
@ -332,6 +337,22 @@ var specs = [...]event.Spec{
|
||||
StackIDs: []int{4},
|
||||
StringIDs: []int{2, 3},
|
||||
},
|
||||
EvGoSwitch: event.Spec{
|
||||
Name: "GoSwitch",
|
||||
Args: []string{"dt", "g", "g_seq"},
|
||||
IsTimedEvent: true,
|
||||
},
|
||||
EvGoSwitchDestroy: event.Spec{
|
||||
Name: "GoSwitchDestroy",
|
||||
Args: []string{"dt", "g", "g_seq"},
|
||||
IsTimedEvent: true,
|
||||
},
|
||||
EvGoCreateBlocked: event.Spec{
|
||||
Name: "GoCreateBlocked",
|
||||
Args: []string{"dt", "new_g", "new_stack", "stack"},
|
||||
IsTimedEvent: true,
|
||||
StackIDs: []int{3, 2},
|
||||
},
|
||||
}
|
||||
|
||||
type GoStatus uint8
|
||||
|
@ -334,7 +334,7 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64)
|
||||
curCtx.M = mid
|
||||
}
|
||||
o.queue.push(currentEvent())
|
||||
case go122.EvGoCreate:
|
||||
case go122.EvGoCreate, go122.EvGoCreateBlocked:
|
||||
// Goroutines must be created on a running P, but may or may not be created
|
||||
// by a running goroutine.
|
||||
reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
|
||||
@ -350,7 +350,11 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64)
|
||||
if _, ok := o.gStates[newgid]; ok {
|
||||
return false, fmt.Errorf("tried to create goroutine (%v) that already exists", newgid)
|
||||
}
|
||||
o.gStates[newgid] = &gState{id: newgid, status: go122.GoRunnable, seq: makeSeq(gen, 0)}
|
||||
status := go122.GoRunnable
|
||||
if typ == go122.EvGoCreateBlocked {
|
||||
status = go122.GoWaiting
|
||||
}
|
||||
o.gStates[newgid] = &gState{id: newgid, status: status, seq: makeSeq(gen, 0)}
|
||||
o.queue.push(currentEvent())
|
||||
case go122.EvGoDestroy, go122.EvGoStop, go122.EvGoBlock:
|
||||
// These are goroutine events that all require an active running
|
||||
@ -418,6 +422,64 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64)
|
||||
// N.B. No context to validate. Basically anything can unblock
|
||||
// a goroutine (e.g. sysmon).
|
||||
o.queue.push(currentEvent())
|
||||
case go122.EvGoSwitch, go122.EvGoSwitchDestroy:
|
||||
// GoSwitch and GoSwitchDestroy represent a trio of events:
|
||||
// - Unblock of the goroutine to switch to.
|
||||
// - Block or destroy of the current goroutine.
|
||||
// - Start executing the next goroutine.
|
||||
//
|
||||
// Because it acts like a GoStart for the next goroutine, we can
|
||||
// only advance it if the sequence numbers line up.
|
||||
//
|
||||
// The current goroutine on the thread must be actively running.
|
||||
if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
|
||||
return false, err
|
||||
}
|
||||
curGState, ok := o.gStates[curCtx.G]
|
||||
if !ok {
|
||||
return false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(typ), curCtx.G)
|
||||
}
|
||||
if curGState.status != go122.GoRunning {
|
||||
return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
|
||||
}
|
||||
nextg := GoID(ev.args[0])
|
||||
seq := makeSeq(gen, ev.args[1]) // seq is for nextg, not curCtx.G.
|
||||
nextGState, ok := o.gStates[nextg]
|
||||
if !ok || nextGState.status != go122.GoWaiting || !seq.succeeds(nextGState.seq) {
|
||||
// We can't make an inference as to whether this is bad. We could just be seeing
|
||||
// a GoSwitch on a different M before the goroutine was created, before it had its
|
||||
// state emitted, or before we got to the right point in the trace yet.
|
||||
return false, nil
|
||||
}
|
||||
o.queue.push(currentEvent())
|
||||
|
||||
// Update the state of the executing goroutine and emit an event for it
|
||||
// (GoSwitch and GoSwitchDestroy will be interpreted as GoUnblock events
|
||||
// for nextg).
|
||||
switch typ {
|
||||
case go122.EvGoSwitch:
|
||||
// Goroutine blocked. It's waiting now and not running on this M.
|
||||
curGState.status = go122.GoWaiting
|
||||
|
||||
// Emit a GoBlock event.
|
||||
// TODO(mknyszek): Emit a reason.
|
||||
o.queue.push(makeEvent(evt, curCtx, go122.EvGoBlock, ev.time, 0 /* no reason */, 0 /* no stack */))
|
||||
case go122.EvGoSwitchDestroy:
|
||||
// This goroutine is exiting itself.
|
||||
delete(o.gStates, curCtx.G)
|
||||
|
||||
// Emit a GoDestroy event.
|
||||
o.queue.push(makeEvent(evt, curCtx, go122.EvGoDestroy, ev.time))
|
||||
}
|
||||
// Update the state of the next goroutine.
|
||||
nextGState.status = go122.GoRunning
|
||||
nextGState.seq = seq
|
||||
newCtx.G = nextg
|
||||
|
||||
// Queue an event for the next goroutine starting to run.
|
||||
startCtx := curCtx
|
||||
startCtx.G = NoGoroutine
|
||||
o.queue.push(makeEvent(evt, startCtx, go122.EvGoStart, ev.time, uint64(nextg), ev.args[1]))
|
||||
case go122.EvGoSyscallBegin:
|
||||
// Entering a syscall requires an active running goroutine with a
|
||||
// proc on some thread. It is always advancable.
|
||||
@ -578,15 +640,7 @@ func (o *ordering) Advance(ev *baseEvent, evt *evTable, m ThreadID, gen uint64)
|
||||
newCtx.P = NoProc
|
||||
|
||||
// Queue an extra self-ProcSteal event.
|
||||
extra := Event{
|
||||
table: evt,
|
||||
ctx: curCtx,
|
||||
base: baseEvent{
|
||||
typ: go122.EvProcSteal,
|
||||
time: ev.time,
|
||||
},
|
||||
}
|
||||
extra.base.args[0] = uint64(curCtx.P)
|
||||
extra := makeEvent(evt, curCtx, go122.EvProcSteal, ev.time, uint64(curCtx.P))
|
||||
extra.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
|
||||
o.queue.push(extra)
|
||||
}
|
||||
@ -1155,3 +1209,21 @@ func (q *queue[T]) pop() (T, bool) {
|
||||
q.start++
|
||||
return value, true
|
||||
}
|
||||
|
||||
// makeEvent creates an Event from the provided information.
|
||||
//
|
||||
// It's just a convenience function; it's always OK to construct
|
||||
// an Event manually if this isn't quite the right way to express
|
||||
// the contents of the event.
|
||||
func makeEvent(table *evTable, ctx schedCtx, typ event.Type, time Time, args ...uint64) Event {
|
||||
ev := Event{
|
||||
table: table,
|
||||
ctx: ctx,
|
||||
base: baseEvent{
|
||||
typ: typ,
|
||||
time: time,
|
||||
},
|
||||
}
|
||||
copy(ev.base.args[:], args)
|
||||
return ev
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func NewReader(r io.Reader) (*Reader, error) {
|
||||
return &Reader{
|
||||
go121Events: convertOldFormat(tr),
|
||||
}, nil
|
||||
case version.Go122:
|
||||
case version.Go122, version.Go123:
|
||||
return &Reader{
|
||||
r: br,
|
||||
order: ordering{
|
||||
|
85
src/internal/trace/v2/testdata/testprog/iter-pull.go
vendored
Normal file
85
src/internal/trace/v2/testdata/testprog/iter-pull.go
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Tests coroutine switches.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"log"
|
||||
"os"
|
||||
"runtime/trace"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Start tracing.
|
||||
if err := trace.Start(os.Stdout); err != nil {
|
||||
log.Fatalf("failed to start tracing: %v", err)
|
||||
}
|
||||
|
||||
// Try simple pull iteration.
|
||||
i := pullRange(100)
|
||||
for {
|
||||
_, ok := i.next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Try bouncing the pull iterator between two goroutines.
|
||||
var wg sync.WaitGroup
|
||||
var iterChans [2]chan intIter
|
||||
wg.Add(2)
|
||||
iterChans[0] = make(chan intIter)
|
||||
iterChans[1] = make(chan intIter)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
iter := pullRange(100)
|
||||
iterChans[1] <- iter
|
||||
|
||||
for i := range iterChans[0] {
|
||||
_, ok := i.next()
|
||||
if !ok {
|
||||
close(iterChans[1])
|
||||
break
|
||||
}
|
||||
iterChans[1] <- i
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for i := range iterChans[1] {
|
||||
_, ok := i.next()
|
||||
if !ok {
|
||||
close(iterChans[0])
|
||||
break
|
||||
}
|
||||
iterChans[0] <- i
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// End of traced execution.
|
||||
trace.Stop()
|
||||
}
|
||||
|
||||
func pullRange(n int) intIter {
|
||||
next, stop := iter.Pull(func(yield func(v int) bool) {
|
||||
for i := range n {
|
||||
yield(i)
|
||||
}
|
||||
})
|
||||
return intIter{next: next, stop: stop}
|
||||
}
|
||||
|
||||
type intIter struct {
|
||||
next func() (int, bool)
|
||||
stop func()
|
||||
}
|
@ -531,6 +531,10 @@ func TestTraceWaitOnPipe(t *testing.T) {
|
||||
t.Skip("no applicable syscall.Pipe on " + runtime.GOOS)
|
||||
}
|
||||
|
||||
func TestTraceIterPull(t *testing.T) {
|
||||
testTraceProg(t, "iter-pull.go", nil)
|
||||
}
|
||||
|
||||
func testTraceProg(t *testing.T, progName string, extra func(t *testing.T, trace, stderr []byte, stress bool)) {
|
||||
testenv.MustHaveGoRun(t)
|
||||
|
||||
@ -547,7 +551,7 @@ func testTraceProg(t *testing.T, progName string, extra func(t *testing.T, trace
|
||||
cmd.Args = append(cmd.Args, "-race")
|
||||
}
|
||||
cmd.Args = append(cmd.Args, testPath)
|
||||
cmd.Env = append(os.Environ(), "GOEXPERIMENT=exectracer2")
|
||||
cmd.Env = append(os.Environ(), "GOEXPERIMENT=exectracer2", "GOEXPERIMENT=rangefunc")
|
||||
if stress {
|
||||
// Advance a generation constantly.
|
||||
cmd.Env = append(cmd.Env, "GODEBUG=traceadvanceperiod=0")
|
||||
|
@ -20,7 +20,8 @@ const (
|
||||
Go119 Version = 19
|
||||
Go121 Version = 21
|
||||
Go122 Version = 22
|
||||
Current = Go122
|
||||
Go123 Version = 23
|
||||
Current = Go123
|
||||
)
|
||||
|
||||
var versions = map[Version][]event.Spec{
|
||||
@ -31,6 +32,10 @@ var versions = map[Version][]event.Spec{
|
||||
Go121: nil,
|
||||
|
||||
Go122: go122.Specs(),
|
||||
// Go 1.23 adds backwards-incompatible events, but
|
||||
// traces produced by Go 1.22 are also always valid
|
||||
// Go 1.23 traces.
|
||||
Go123: go122.Specs(),
|
||||
}
|
||||
|
||||
// Specs returns the set of event.Specs for this version.
|
||||
|
@ -39,11 +39,9 @@ func newcoro(f func(*coro)) *coro {
|
||||
systemstack(func() {
|
||||
start := corostart
|
||||
startfv := *(**funcval)(unsafe.Pointer(&start))
|
||||
gp = newproc1(startfv, gp, pc)
|
||||
gp = newproc1(startfv, gp, pc, true, waitReasonCoroutine)
|
||||
})
|
||||
gp.coroarg = c
|
||||
gp.waitreason = waitReasonCoroutine
|
||||
casgstatus(gp, _Grunnable, _Gwaiting)
|
||||
c.gp.set(gp)
|
||||
return c
|
||||
}
|
||||
@ -94,18 +92,30 @@ func coroswitch(c *coro) {
|
||||
// It is important not to add more atomic operations or other
|
||||
// expensive operations to the fast path.
|
||||
func coroswitch_m(gp *g) {
|
||||
// TODO(rsc,mknyszek): add tracing support in a lightweight manner.
|
||||
// Probably the tracer will need a global bool (set and cleared during STW)
|
||||
// that this code can check to decide whether to use trace.gen.Load();
|
||||
// we do not want to do the atomic load all the time, especially when
|
||||
// tracer use is relatively rare.
|
||||
// TODO(go.dev/issue/65889): Something really nasty will happen if either
|
||||
// goroutine in this handoff tries to lock itself to an OS thread.
|
||||
// There's an explicit multiplexing going on here that needs to be
|
||||
// disabled if either the consumer or the iterator ends up in such
|
||||
// a state.
|
||||
c := gp.coroarg
|
||||
gp.coroarg = nil
|
||||
exit := gp.coroexit
|
||||
gp.coroexit = false
|
||||
mp := gp.m
|
||||
|
||||
// Acquire tracer for writing for the duration of this call.
|
||||
//
|
||||
// There's a lot of state manipulation performed with shortcuts
|
||||
// but we need to make sure the tracer can only observe the
|
||||
// start and end states to maintain a coherent model and avoid
|
||||
// emitting an event for every single transition.
|
||||
trace := traceAcquire()
|
||||
|
||||
if exit {
|
||||
// TODO(65889): If we're locked to the current OS thread and
|
||||
// we exit here while tracing is enabled, we're going to end up
|
||||
// in a really bad place (traceAcquire also calls acquirem; there's
|
||||
// no releasem before the thread exits).
|
||||
gdestroy(gp)
|
||||
gp = nil
|
||||
} else {
|
||||
@ -148,6 +158,13 @@ func coroswitch_m(gp *g) {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the trace event after getting gnext but before changing curg.
|
||||
// GoSwitch expects that the current G is running and that we haven't
|
||||
// switched yet for correct status emission.
|
||||
if trace.ok() {
|
||||
trace.GoSwitch(gnext, exit)
|
||||
}
|
||||
|
||||
// Start running next, without heavy scheduling machinery.
|
||||
// Set mp.curg and gnext.m and then update scheduling state
|
||||
// directly if possible.
|
||||
@ -160,6 +177,11 @@ func coroswitch_m(gp *g) {
|
||||
casgstatus(gnext, _Grunnable, _Grunning)
|
||||
}
|
||||
|
||||
// Release the trace locker. We've completed all the necessary transitions..
|
||||
if trace.ok() {
|
||||
traceRelease(trace)
|
||||
}
|
||||
|
||||
// Switch to gnext. Does not return.
|
||||
gogo(&gnext.sched)
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ func debugCallWrap(dispatch uintptr) {
|
||||
// closure and start the goroutine with that closure, but the compiler disallows
|
||||
// implicit closure allocation in the runtime.
|
||||
fn := debugCallWrap1
|
||||
newg := newproc1(*(**funcval)(unsafe.Pointer(&fn)), gp, callerpc)
|
||||
newg := newproc1(*(**funcval)(unsafe.Pointer(&fn)), gp, callerpc, false, waitReasonZero)
|
||||
args := &debugCallWrapArgs{
|
||||
dispatch: dispatch,
|
||||
callingG: gp,
|
||||
|
@ -4827,7 +4827,7 @@ func newproc(fn *funcval) {
|
||||
gp := getg()
|
||||
pc := getcallerpc()
|
||||
systemstack(func() {
|
||||
newg := newproc1(fn, gp, pc)
|
||||
newg := newproc1(fn, gp, pc, false, waitReasonZero)
|
||||
|
||||
pp := getg().m.p.ptr()
|
||||
runqput(pp, newg, true)
|
||||
@ -4838,10 +4838,10 @@ func newproc(fn *funcval) {
|
||||
})
|
||||
}
|
||||
|
||||
// Create a new g in state _Grunnable, starting at fn. callerpc is the
|
||||
// address of the go statement that created this. The caller is responsible
|
||||
// for adding the new g to the scheduler.
|
||||
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
|
||||
// Create a new g in state _Grunnable (or _Gwaiting if parked is true), starting at fn.
|
||||
// callerpc is the address of the go statement that created this. The caller is responsible
|
||||
// for adding the new g to the scheduler. If parked is true, waitreason must be non-zero.
|
||||
func newproc1(fn *funcval, callergp *g, callerpc uintptr, parked bool, waitreason waitReason) *g {
|
||||
if fn == nil {
|
||||
fatal("go of nil func value")
|
||||
}
|
||||
@ -4910,7 +4910,12 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
|
||||
|
||||
// Get a goid and switch to runnable. Make all this atomic to the tracer.
|
||||
trace := traceAcquire()
|
||||
casgstatus(newg, _Gdead, _Grunnable)
|
||||
var status uint32 = _Grunnable
|
||||
if parked {
|
||||
status = _Gwaiting
|
||||
newg.waitreason = waitreason
|
||||
}
|
||||
casgstatus(newg, _Gdead, status)
|
||||
if pp.goidcache == pp.goidcacheend {
|
||||
// Sched.goidgen is the last allocated id,
|
||||
// this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
|
||||
@ -4923,7 +4928,7 @@ func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
|
||||
pp.goidcache++
|
||||
newg.trace.reset()
|
||||
if trace.ok() {
|
||||
trace.GoCreate(newg, newg.startpc)
|
||||
trace.GoCreate(newg, newg.startpc, parked)
|
||||
traceRelease(trace)
|
||||
}
|
||||
|
||||
|
@ -1641,7 +1641,11 @@ func (_ traceLocker) GCMarkAssistDone() {
|
||||
traceEvent(traceEvGCMarkAssistDone, -1)
|
||||
}
|
||||
|
||||
func (_ traceLocker) GoCreate(newg *g, pc uintptr) {
|
||||
// N.B. the last argument is used only for iter.Pull.
|
||||
func (_ traceLocker) GoCreate(newg *g, pc uintptr, blocked bool) {
|
||||
if blocked {
|
||||
throw("tried to emit event for newly-created blocked goroutine: unsupported in the v1 tracer")
|
||||
}
|
||||
newg.trace.seq = 0
|
||||
newg.trace.lastP = getg().m.p
|
||||
// +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum.
|
||||
@ -1696,6 +1700,10 @@ func (_ traceLocker) GoUnpark(gp *g, skip int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (_ traceLocker) GoSwitch(_ *g, _ bool) {
|
||||
throw("tried to emit event for a direct goroutine switch: unsupported in the v1 tracer")
|
||||
}
|
||||
|
||||
func (_ traceLocker) GoSysCall() {
|
||||
var skip int
|
||||
switch {
|
||||
@ -1891,7 +1899,7 @@ func (tl traceLocker) OneNewExtraM(gp *g) {
|
||||
// Trigger two trace events for the locked g in the extra m,
|
||||
// since the next event of the g will be traceEvGoSysExit in exitsyscall,
|
||||
// while calling from C thread to Go.
|
||||
tl.GoCreate(gp, 0) // no start pc
|
||||
tl.GoCreate(gp, 0, false) // no start pc
|
||||
gp.trace.seq++
|
||||
traceEvent(traceEvGoInSyscall, -1, gp.goid)
|
||||
}
|
||||
|
@ -778,7 +778,7 @@ func readTrace0() (buf []byte, park bool) {
|
||||
if !trace.headerWritten {
|
||||
trace.headerWritten = true
|
||||
unlock(&trace.lock)
|
||||
return []byte("go 1.22 trace\x00\x00\x00"), false
|
||||
return []byte("go 1.23 trace\x00\x00\x00"), false
|
||||
}
|
||||
|
||||
// Read the next buffer.
|
||||
|
@ -81,6 +81,11 @@ const (
|
||||
traceEvUserRegionBegin // trace.{Start,With}Region [timestamp, internal task ID, name string ID, stack ID]
|
||||
traceEvUserRegionEnd // trace.{End,With}Region [timestamp, internal task ID, name string ID, stack ID]
|
||||
traceEvUserLog // trace.Log [timestamp, internal task ID, key string ID, stack, value string ID]
|
||||
|
||||
// Coroutines.
|
||||
traceEvGoSwitch // goroutine switch (coroswitch) [timestamp, goroutine ID, goroutine seq]
|
||||
traceEvGoSwitchDestroy // goroutine switch and destroy [timestamp, goroutine ID, goroutine seq]
|
||||
traceEvGoCreateBlocked // goroutine creation (starts blocked) [timestamp, new goroutine ID, new stack ID, stack ID]
|
||||
)
|
||||
|
||||
// traceArg is a simple wrapper type to help ensure that arguments passed
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user