]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: add tracing for iter.Pull
authorMichael Anthony Knyszek <mknyszek@google.com>
Tue, 20 Feb 2024 21:16:33 +0000 (21:16 +0000)
committerGopher Robot <gobot@golang.org>
Fri, 22 Mar 2024 16:12:01 +0000 (16:12 +0000)
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>

14 files changed:
src/internal/trace/v2/event.go
src/internal/trace/v2/event/go122/event.go
src/internal/trace/v2/order.go
src/internal/trace/v2/reader.go
src/internal/trace/v2/testdata/testprog/iter-pull.go [new file with mode: 0644]
src/internal/trace/v2/trace_test.go
src/internal/trace/v2/version/version.go
src/runtime/coro.go
src/runtime/debugcall.go
src/runtime/proc.go
src/runtime/trace.go
src/runtime/trace2.go
src/runtime/trace2event.go
src/runtime/trace2runtime.go

index ec5e27e57a9e90375ff1f21aab58ab49cb791078..55bc88455c9f7f37a579676917db36751ff9b708 100644 (file)
@@ -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,
 }
 
index f28e91823168020494daaee35072478f8f0defc5..ace11be462cc6a068ff30a207e06d7dbe9a18da6 100644 (file)
@@ -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
index 258f3a3f63d3ca91eb57ace888d5e8f7f7f47137..f3fb1fb9a780f4392cfd271cdb9e8ed99d2c3641 100644 (file)
@@ -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
+}
index 94a522ce6664acd85ae92608f7b7d6dcd5b25a64..45f0d137876bfcda891bc57ee0dcc5d17fb512ce 100644 (file)
@@ -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{
diff --git a/src/internal/trace/v2/testdata/testprog/iter-pull.go b/src/internal/trace/v2/testdata/testprog/iter-pull.go
new file mode 100644 (file)
index 0000000..ba8f413
--- /dev/null
@@ -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()
+}
index 2514f796c86862d06995c8a49b8168a731da979c..18971ffd48fb2e660b3849b857abc9801fc0cbff 100644 (file)
@@ -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")
index f42dbb9eefef5c3ae0b1b5ef902767eef6c4cba3..e3354eb0c1ef009aacd3b9d3b3525ba99b78106b 100644 (file)
@@ -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.
index 0d6666e343b520105fe03f7e110a3277414802db..98e789f133a45c305f6f7899cbc0d89a76c7a63c 100644 (file)
@@ -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)
 }
index 5dd83063ff9cb8bb88f6e34d42ab162c9f106812..dcd7a6e2a5c88d62753a64ee269d56128b51ec20 100644 (file)
@@ -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,
index 8e92a5ee8ed71eb139f99bb18877c9fae5d0dbaa..7f70100538910c9f664b1200e55a84a8b96f5bcd 100644 (file)
@@ -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)
        }
 
index 948a8da0cab560661747ef75ab516aa5b93f22ef..8d7bf088dd93a9ed114bc4cdf536e72f345db2f2 100644 (file)
@@ -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)
 }
index 94f15dffd5b1727a0e6685a0f0b884cbafadd4e0..48f969129adfc371473a1f7036f51d196c9d7e50 100644 (file)
@@ -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.
index 1f2a9f754be1f701a398220897b6f9d47a7ed6cb..c56887482177c0d87d27ed3915e2e39f1134ade4 100644 (file)
@@ -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
index 7b88c258bacddbf606a77e22bdcc06d5459d7b26..b391fd79ffeedc44d6fc1b00ac035d50cd65f9a1 100644 (file)
@@ -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.