trace: regenerate trace from go@c2b1463
[git-generate]
cd trace
./gen.bash $HOME/work/go
Change-Id: I7db5c75d3ca5d86918a3cac1a1fbeb1fa97b9881
Reviewed-on: https://go-review.googlesource.com/c/exp/+/574135
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Stapelberg <stapelberg@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
diff --git a/trace/event.go b/trace/event.go
index a73e624..962a63c 100644
--- a/trace/event.go
+++ b/trace/event.go
@@ -570,8 +570,12 @@
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)
@@ -590,7 +594,10 @@
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)
@@ -650,6 +657,9 @@
go122.EvUserRegionBegin: EventRegionBegin,
go122.EvUserRegionEnd: EventRegionEnd,
go122.EvUserLog: EventLog,
+ go122.EvGoSwitch: EventStateTransition,
+ go122.EvGoSwitchDestroy: EventStateTransition,
+ go122.EvGoCreateBlocked: EventStateTransition,
evSync: EventSync,
}
diff --git a/trace/internal/event/go122/event.go b/trace/internal/event/go122/event.go
index a77fb8b..2baf9ce 100644
--- a/trace/internal/event/go122/event.go
+++ b/trace/internal/event/go122/event.go
@@ -71,6 +71,11 @@
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.
@@ -336,6 +341,22 @@
StackIDs: []int{4},
StringIDs: []int{2, 3},
},
+ EvGoSwitch: {
+ Name: "GoSwitch",
+ Args: []string{"dt", "g", "g_seq"},
+ IsTimedEvent: true,
+ },
+ EvGoSwitchDestroy: {
+ Name: "GoSwitchDestroy",
+ Args: []string{"dt", "g", "g_seq"},
+ IsTimedEvent: true,
+ },
+ EvGoCreateBlocked: {
+ Name: "GoCreateBlocked",
+ Args: []string{"dt", "new_g", "new_stack", "stack"},
+ IsTimedEvent: true,
+ StackIDs: []int{3, 2},
+ },
}
type GoStatus uint8
diff --git a/trace/internal/version/version.go b/trace/internal/version/version.go
index 047ca0c..1dc0dcd 100644
--- a/trace/internal/version/version.go
+++ b/trace/internal/version/version.go
@@ -24,7 +24,8 @@
Go119 Version = 19
Go121 Version = 21
Go122 Version = 22
- Current = Go122
+ Go123 Version = 23
+ Current = Go123
)
var versions = map[Version][]event.Spec{
@@ -35,6 +36,10 @@
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.
diff --git a/trace/order.go b/trace/order.go
index 0e2f600..089d623 100644
--- a/trace/order.go
+++ b/trace/order.go
@@ -78,524 +78,693 @@
newCtx = curCtx
}
- // Generates an event from the current context.
- currentEvent := func() Event {
- return Event{table: evt, ctx: curCtx, base: *ev}
+ f := orderingDispatch[ev.typ]
+ if f == nil {
+ return false, fmt.Errorf("bad event type found while ordering: %v", ev.typ)
+ }
+ newCtx, ok, err := f(o, ev, evt, m, gen, curCtx)
+ if err == nil && ok && ms != nil {
+ // Update the mState for this event.
+ ms.p = newCtx.P
+ ms.g = newCtx.G
+ }
+ return ok, err
+}
+
+type orderingHandleFunc func(o *ordering, ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error)
+
+var orderingDispatch = [256]orderingHandleFunc{
+ // Procs.
+ go122.EvProcsChange: (*ordering).advanceAnnotation,
+ go122.EvProcStart: (*ordering).advanceProcStart,
+ go122.EvProcStop: (*ordering).advanceProcStop,
+ go122.EvProcSteal: (*ordering).advanceProcSteal,
+ go122.EvProcStatus: (*ordering).advanceProcStatus,
+
+ // Goroutines.
+ go122.EvGoCreate: (*ordering).advanceGoCreate,
+ go122.EvGoCreateSyscall: (*ordering).advanceGoCreateSyscall,
+ go122.EvGoStart: (*ordering).advanceGoStart,
+ go122.EvGoDestroy: (*ordering).advanceGoStopExec,
+ go122.EvGoDestroySyscall: (*ordering).advanceGoDestroySyscall,
+ go122.EvGoStop: (*ordering).advanceGoStopExec,
+ go122.EvGoBlock: (*ordering).advanceGoStopExec,
+ go122.EvGoUnblock: (*ordering).advanceGoUnblock,
+ go122.EvGoSyscallBegin: (*ordering).advanceGoSyscallBegin,
+ go122.EvGoSyscallEnd: (*ordering).advanceGoSyscallEnd,
+ go122.EvGoSyscallEndBlocked: (*ordering).advanceGoSyscallEndBlocked,
+ go122.EvGoStatus: (*ordering).advanceGoStatus,
+
+ // STW.
+ go122.EvSTWBegin: (*ordering).advanceGoRangeBegin,
+ go122.EvSTWEnd: (*ordering).advanceGoRangeEnd,
+
+ // GC events.
+ go122.EvGCActive: (*ordering).advanceGCActive,
+ go122.EvGCBegin: (*ordering).advanceGCBegin,
+ go122.EvGCEnd: (*ordering).advanceGCEnd,
+ go122.EvGCSweepActive: (*ordering).advanceGCSweepActive,
+ go122.EvGCSweepBegin: (*ordering).advanceGCSweepBegin,
+ go122.EvGCSweepEnd: (*ordering).advanceGCSweepEnd,
+ go122.EvGCMarkAssistActive: (*ordering).advanceGoRangeActive,
+ go122.EvGCMarkAssistBegin: (*ordering).advanceGoRangeBegin,
+ go122.EvGCMarkAssistEnd: (*ordering).advanceGoRangeEnd,
+ go122.EvHeapAlloc: (*ordering).advanceHeapMetric,
+ go122.EvHeapGoal: (*ordering).advanceHeapMetric,
+
+ // Annotations.
+ go122.EvGoLabel: (*ordering).advanceAnnotation,
+ go122.EvUserTaskBegin: (*ordering).advanceUserTaskBegin,
+ go122.EvUserTaskEnd: (*ordering).advanceUserTaskEnd,
+ go122.EvUserRegionBegin: (*ordering).advanceUserRegionBegin,
+ go122.EvUserRegionEnd: (*ordering).advanceUserRegionEnd,
+ go122.EvUserLog: (*ordering).advanceAnnotation,
+
+ // Coroutines. Added in Go 1.23.
+ go122.EvGoSwitch: (*ordering).advanceGoSwitch,
+ go122.EvGoSwitchDestroy: (*ordering).advanceGoSwitch,
+ go122.EvGoCreateBlocked: (*ordering).advanceGoCreate,
+}
+
+func (o *ordering) advanceProcStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ pid := ProcID(ev.args[0])
+ status := go122.ProcStatus(ev.args[1])
+ if int(status) >= len(go122ProcStatus2ProcState) {
+ return curCtx, false, fmt.Errorf("invalid status for proc %d: %d", pid, status)
+ }
+ oldState := go122ProcStatus2ProcState[status]
+ if s, ok := o.pStates[pid]; ok {
+ if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscall {
+ // ProcSyscallAbandoned is a special case of ProcSyscall. It indicates a
+ // potential loss of information, but if we're already in ProcSyscall,
+ // we haven't lost the relevant information. Promote the status and advance.
+ oldState = ProcRunning
+ ev.args[1] = uint64(go122.ProcSyscall)
+ } else if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscallAbandoned {
+ // If we're passing through ProcSyscallAbandoned, then there's no promotion
+ // to do. We've lost the M that this P is associated with. However it got there,
+ // it's going to appear as idle in the API, so pass through as idle.
+ oldState = ProcIdle
+ ev.args[1] = uint64(go122.ProcSyscallAbandoned)
+ } else if s.status != status {
+ return curCtx, false, fmt.Errorf("inconsistent status for proc %d: old %v vs. new %v", pid, s.status, status)
+ }
+ s.seq = makeSeq(gen, 0) // Reset seq.
+ } else {
+ o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)}
+ if gen == o.initialGen {
+ oldState = ProcUndetermined
+ } else {
+ oldState = ProcNotExist
+ }
+ }
+ ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
+
+ // Bind the proc to the new context, if it's running.
+ newCtx := curCtx
+ if status == go122.ProcRunning || status == go122.ProcSyscall {
+ newCtx.P = pid
+ }
+ // If we're advancing through ProcSyscallAbandoned *but* oldState is running then we've
+ // promoted it to ProcSyscall. However, because it's ProcSyscallAbandoned, we know this
+ // P is about to get stolen and its status very likely isn't being emitted by the same
+ // thread it was bound to. Since this status is Running -> Running and Running is binding,
+ // we need to make sure we emit it in the right context: the context to which it is bound.
+ // Find it, and set our current context to it.
+ if status == go122.ProcSyscallAbandoned && oldState == ProcRunning {
+ // N.B. This is slow but it should be fairly rare.
+ found := false
+ for mid, ms := range o.mStates {
+ if ms.p == pid {
+ curCtx.M = mid
+ curCtx.P = pid
+ curCtx.G = ms.g
+ found = true
+ }
+ }
+ if !found {
+ return curCtx, false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid)
+ }
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ pid := ProcID(ev.args[0])
+ seq := makeSeq(gen, ev.args[1])
+
+ // Try to advance. We might fail here due to sequencing, because the P hasn't
+ // had a status emitted, or because we already have a P and we're in a syscall,
+ // and we haven't observed that it was stolen from us yet.
+ state, ok := o.pStates[pid]
+ if !ok || state.status != go122.ProcIdle || !seq.succeeds(state.seq) || curCtx.P != NoProc {
+ // We can't make an inference as to whether this is bad. We could just be seeing
+ // a ProcStart on a different M before the proc's state was emitted, or before we
+ // got to the right point in the trace.
+ //
+ // Note that we also don't advance here if we have a P and we're in a syscall.
+ return curCtx, false, nil
+ }
+ // We can advance this P. Check some invariants.
+ //
+ // We might have a goroutine if a goroutine is exiting a syscall.
+ reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustNotHave, Goroutine: event.MayHave}
+ if err := validateCtx(curCtx, reqs); err != nil {
+ return curCtx, false, err
+ }
+ state.status = go122.ProcRunning
+ state.seq = seq
+ newCtx := curCtx
+ newCtx.P = pid
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcStop(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // We must be able to advance this P.
+ //
+ // There are 2 ways a P can stop: ProcStop and ProcSteal. ProcStop is used when the P
+ // is stopped by the same M that started it, while ProcSteal is used when another M
+ // steals the P by stopping it from a distance.
+ //
+ // Since a P is bound to an M, and we're stopping on the same M we started, it must
+ // always be possible to advance the current M's P from a ProcStop. This is also why
+ // ProcStop doesn't need a sequence number.
+ state, ok := o.pStates[curCtx.P]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for proc (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.P)
+ }
+ if state.status != go122.ProcRunning && state.status != go122.ProcSyscall {
+ return curCtx, false, fmt.Errorf("%s event for proc that's not %s or %s", go122.EventString(ev.typ), go122.ProcRunning, go122.ProcSyscall)
+ }
+ reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
+ if err := validateCtx(curCtx, reqs); err != nil {
+ return curCtx, false, err
+ }
+ state.status = go122.ProcIdle
+ newCtx := curCtx
+ newCtx.P = NoProc
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceProcSteal(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ pid := ProcID(ev.args[0])
+ seq := makeSeq(gen, ev.args[1])
+ state, ok := o.pStates[pid]
+ if !ok || (state.status != go122.ProcSyscall && state.status != go122.ProcSyscallAbandoned) || !seq.succeeds(state.seq) {
+ // We can't make an inference as to whether this is bad. We could just be seeing
+ // a ProcStart on a different M before the proc's state was emitted, or before we
+ // got to the right point in the trace.
+ return curCtx, false, nil
+ }
+ // We can advance this P. Check some invariants.
+ reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MayHave}
+ if err := validateCtx(curCtx, reqs); err != nil {
+ return curCtx, false, err
+ }
+ // Smuggle in the P state that let us advance so we can surface information to the event.
+ // Specifically, we need to make sure that the event is interpreted not as a transition of
+ // ProcRunning -> ProcIdle but ProcIdle -> ProcIdle instead.
+ //
+ // ProcRunning is binding, but we may be running with a P on the current M and we can't
+ // bind another P. This P is about to go ProcIdle anyway.
+ oldStatus := state.status
+ ev.extra(version.Go122)[0] = uint64(oldStatus)
+
+ // Update the P's status and sequence number.
+ state.status = go122.ProcIdle
+ state.seq = seq
+
+ // If we've lost information then don't try to do anything with the M.
+ // It may have moved on and we can't be sure.
+ if oldStatus == go122.ProcSyscallAbandoned {
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
}
- switch typ := ev.typ; typ {
- // Handle procs.
- case go122.EvProcStatus:
- pid := ProcID(ev.args[0])
- status := go122.ProcStatus(ev.args[1])
- if int(status) >= len(go122ProcStatus2ProcState) {
- return false, fmt.Errorf("invalid status for proc %d: %d", pid, status)
- }
- oldState := go122ProcStatus2ProcState[status]
- if s, ok := o.pStates[pid]; ok {
- if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscall {
- // ProcSyscallAbandoned is a special case of ProcSyscall. It indicates a
- // potential loss of information, but if we're already in ProcSyscall,
- // we haven't lost the relevant information. Promote the status and advance.
- oldState = ProcRunning
- ev.args[1] = uint64(go122.ProcSyscall)
- } else if status == go122.ProcSyscallAbandoned && s.status == go122.ProcSyscallAbandoned {
- // If we're passing through ProcSyscallAbandoned, then there's no promotion
- // to do. We've lost the M that this P is associated with. However it got there,
- // it's going to appear as idle in the API, so pass through as idle.
- oldState = ProcIdle
- ev.args[1] = uint64(go122.ProcSyscallAbandoned)
- } else if s.status != status {
- return false, fmt.Errorf("inconsistent status for proc %d: old %v vs. new %v", pid, s.status, status)
- }
- s.seq = makeSeq(gen, 0) // Reset seq.
- } else {
- o.pStates[pid] = &pState{id: pid, status: status, seq: makeSeq(gen, 0)}
- if gen == o.initialGen {
- oldState = ProcUndetermined
- } else {
- oldState = ProcNotExist
- }
- }
- ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
+ // Validate that the M we're stealing from is what we expect.
+ mid := ThreadID(ev.args[2]) // The M we're stealing from.
- // Bind the proc to the new context, if it's running.
- if status == go122.ProcRunning || status == go122.ProcSyscall {
- newCtx.P = pid
+ newCtx := curCtx
+ if mid == curCtx.M {
+ // We're stealing from ourselves. This behaves like a ProcStop.
+ if curCtx.P != pid {
+ return curCtx, false, fmt.Errorf("tried to self-steal proc %d (thread %d), but got proc %d instead", pid, mid, curCtx.P)
}
- // If we're advancing through ProcSyscallAbandoned *but* oldState is running then we've
- // promoted it to ProcSyscall. However, because it's ProcSyscallAbandoned, we know this
- // P is about to get stolen and its status very likely isn't being emitted by the same
- // thread it was bound to. Since this status is Running -> Running and Running is binding,
- // we need to make sure we emit it in the right context: the context to which it is bound.
- // Find it, and set our current context to it.
- if status == go122.ProcSyscallAbandoned && oldState == ProcRunning {
- // N.B. This is slow but it should be fairly rare.
- found := false
- for mid, ms := range o.mStates {
- if ms.p == pid {
- curCtx.M = mid
- curCtx.P = pid
- curCtx.G = ms.g
- found = true
- }
- }
- if !found {
- return false, fmt.Errorf("failed to find sched context for proc %d that's about to be stolen", pid)
- }
- }
- o.queue.push(currentEvent())
- case go122.EvProcStart:
- pid := ProcID(ev.args[0])
- seq := makeSeq(gen, ev.args[1])
-
- // Try to advance. We might fail here due to sequencing, because the P hasn't
- // had a status emitted, or because we already have a P and we're in a syscall,
- // and we haven't observed that it was stolen from us yet.
- state, ok := o.pStates[pid]
- if !ok || state.status != go122.ProcIdle || !seq.succeeds(state.seq) || curCtx.P != NoProc {
- // We can't make an inference as to whether this is bad. We could just be seeing
- // a ProcStart on a different M before the proc's state was emitted, or before we
- // got to the right point in the trace.
- //
- // Note that we also don't advance here if we have a P and we're in a syscall.
- return false, nil
- }
- // We can advance this P. Check some invariants.
- //
- // We might have a goroutine if a goroutine is exiting a syscall.
- reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustNotHave, Goroutine: event.MayHave}
- if err := validateCtx(curCtx, reqs); err != nil {
- return false, err
- }
- state.status = go122.ProcRunning
- state.seq = seq
- newCtx.P = pid
- o.queue.push(currentEvent())
- case go122.EvProcStop:
- // We must be able to advance this P.
- //
- // There are 2 ways a P can stop: ProcStop and ProcSteal. ProcStop is used when the P
- // is stopped by the same M that started it, while ProcSteal is used when another M
- // steals the P by stopping it from a distance.
- //
- // Since a P is bound to an M, and we're stopping on the same M we started, it must
- // always be possible to advance the current M's P from a ProcStop. This is also why
- // ProcStop doesn't need a sequence number.
- state, ok := o.pStates[curCtx.P]
- if !ok {
- return false, fmt.Errorf("event %s for proc (%v) that doesn't exist", go122.EventString(typ), curCtx.P)
- }
- if state.status != go122.ProcRunning && state.status != go122.ProcSyscall {
- return false, fmt.Errorf("%s event for proc that's not %s or %s", go122.EventString(typ), go122.ProcRunning, go122.ProcSyscall)
- }
- reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}
- if err := validateCtx(curCtx, reqs); err != nil {
- return false, err
- }
- state.status = go122.ProcIdle
newCtx.P = NoProc
- o.queue.push(currentEvent())
- case go122.EvProcSteal:
- pid := ProcID(ev.args[0])
- seq := makeSeq(gen, ev.args[1])
- state, ok := o.pStates[pid]
- if !ok || (state.status != go122.ProcSyscall && state.status != go122.ProcSyscallAbandoned) || !seq.succeeds(state.seq) {
- // We can't make an inference as to whether this is bad. We could just be seeing
- // a ProcStart on a different M before the proc's state was emitted, or before we
- // got to the right point in the trace.
- return false, nil
- }
- // We can advance this P. Check some invariants.
- reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MayHave}
- if err := validateCtx(curCtx, reqs); err != nil {
- return false, err
- }
- // Smuggle in the P state that let us advance so we can surface information to the event.
- // Specifically, we need to make sure that the event is interpreted not as a transition of
- // ProcRunning -> ProcIdle but ProcIdle -> ProcIdle instead.
- //
- // ProcRunning is binding, but we may be running with a P on the current M and we can't
- // bind another P. This P is about to go ProcIdle anyway.
- oldStatus := state.status
- ev.extra(version.Go122)[0] = uint64(oldStatus)
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+ }
- // Update the P's status and sequence number.
- state.status = go122.ProcIdle
- state.seq = seq
+ // We're stealing from some other M.
+ mState, ok := o.mStates[mid]
+ if !ok {
+ return curCtx, false, fmt.Errorf("stole proc from non-existent thread %d", mid)
+ }
- // If we've lost information then don't try to do anything with the M.
- // It may have moved on and we can't be sure.
- if oldStatus == go122.ProcSyscallAbandoned {
- o.queue.push(currentEvent())
- break
- }
+ // Make sure we're actually stealing the right P.
+ if mState.p != pid {
+ return curCtx, false, fmt.Errorf("tried to steal proc %d from thread %d, but got proc %d instead", pid, mid, mState.p)
+ }
- // Validate that the M we're stealing from is what we expect.
- mid := ThreadID(ev.args[2]) // The M we're stealing from.
+ // Tell the M it has no P so it can proceed.
+ //
+ // This is safe because we know the P was in a syscall and
+ // the other M must be trying to get out of the syscall.
+ // GoSyscallEndBlocked cannot advance until the corresponding
+ // M loses its P.
+ mState.p = NoProc
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
- if mid == curCtx.M {
- // We're stealing from ourselves. This behaves like a ProcStop.
- if curCtx.P != pid {
- return false, fmt.Errorf("tried to self-steal proc %d (thread %d), but got proc %d instead", pid, mid, curCtx.P)
- }
- newCtx.P = NoProc
- o.queue.push(currentEvent())
- break
- }
+func (o *ordering) advanceGoStatus(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ gid := GoID(ev.args[0])
+ mid := ThreadID(ev.args[1])
+ status := go122.GoStatus(ev.args[2])
- // We're stealing from some other M.
- mState, ok := o.mStates[mid]
- if !ok {
- return false, fmt.Errorf("stole proc from non-existent thread %d", mid)
+ if int(status) >= len(go122GoStatus2GoState) {
+ return curCtx, false, fmt.Errorf("invalid status for goroutine %d: %d", gid, status)
+ }
+ oldState := go122GoStatus2GoState[status]
+ if s, ok := o.gStates[gid]; ok {
+ if s.status != status {
+ return curCtx, false, fmt.Errorf("inconsistent status for goroutine %d: old %v vs. new %v", gid, s.status, status)
}
+ s.seq = makeSeq(gen, 0) // Reset seq.
+ } else if gen == o.initialGen {
+ // Set the state.
+ o.gStates[gid] = &gState{id: gid, status: status, seq: makeSeq(gen, 0)}
+ oldState = GoUndetermined
+ } else {
+ return curCtx, false, fmt.Errorf("found goroutine status for new goroutine after the first generation: id=%v status=%v", gid, status)
+ }
+ ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
- // Make sure we're actually stealing the right P.
- if mState.p != pid {
- return false, fmt.Errorf("tried to steal proc %d from thread %d, but got proc %d instead", pid, mid, mState.p)
- }
-
- // Tell the M it has no P so it can proceed.
- //
- // This is safe because we know the P was in a syscall and
- // the other M must be trying to get out of the syscall.
- // GoSyscallEndBlocked cannot advance until the corresponding
- // M loses its P.
- mState.p = NoProc
- o.queue.push(currentEvent())
-
- // Handle goroutines.
- case go122.EvGoStatus:
- gid := GoID(ev.args[0])
- mid := ThreadID(ev.args[1])
- status := go122.GoStatus(ev.args[2])
-
- if int(status) >= len(go122GoStatus2GoState) {
- return false, fmt.Errorf("invalid status for goroutine %d: %d", gid, status)
- }
- oldState := go122GoStatus2GoState[status]
- if s, ok := o.gStates[gid]; ok {
- if s.status != status {
- return false, fmt.Errorf("inconsistent status for goroutine %d: old %v vs. new %v", gid, s.status, status)
- }
- s.seq = makeSeq(gen, 0) // Reset seq.
- } else if gen == o.initialGen {
- // Set the state.
- o.gStates[gid] = &gState{id: gid, status: status, seq: makeSeq(gen, 0)}
- oldState = GoUndetermined
- } else {
- return false, fmt.Errorf("found goroutine status for new goroutine after the first generation: id=%v status=%v", gid, status)
- }
- ev.extra(version.Go122)[0] = uint64(oldState) // Smuggle in the old state for StateTransition.
-
- switch status {
- case go122.GoRunning:
- // Bind the goroutine to the new context, since it's running.
- newCtx.G = gid
- case go122.GoSyscall:
- if mid == NoThread {
- return false, fmt.Errorf("found goroutine %d in syscall without a thread", gid)
- }
- // Is the syscall on this thread? If so, bind it to the context.
- // Otherwise, we're talking about a G sitting in a syscall on an M.
- // Validate the named M.
- if mid == curCtx.M {
- if gen != o.initialGen && curCtx.G != gid {
- // If this isn't the first generation, we *must* have seen this
- // binding occur already. Even if the G was blocked in a syscall
- // for multiple generations since trace start, we would have seen
- // a previous GoStatus event that bound the goroutine to an M.
- return false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, curCtx.G)
- }
- newCtx.G = gid
- break
- }
- // Now we're talking about a thread and goroutine that have been
- // blocked on a syscall for the entire generation. This case must
- // not have a P; the runtime makes sure that all Ps are traced at
- // the beginning of a generation, which involves taking a P back
- // from every thread.
- ms, ok := o.mStates[mid]
- if ok {
- // This M has been seen. That means we must have seen this
- // goroutine go into a syscall on this thread at some point.
- if ms.g != gid {
- // But the G on the M doesn't match. Something's wrong.
- return false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, ms.g)
- }
- // This case is just a Syscall->Syscall event, which needs to
- // appear as having the G currently bound to this M.
- curCtx.G = ms.g
- } else if !ok {
- // The M hasn't been seen yet. That means this goroutine
- // has just been sitting in a syscall on this M. Create
- // a state for it.
- o.mStates[mid] = &mState{g: gid, p: NoProc}
- // Don't set curCtx.G in this case because this event is the
- // binding event (and curCtx represents the "before" state).
- }
- // Update the current context to the M we're talking about.
- curCtx.M = mid
- }
- o.queue.push(currentEvent())
- case go122.EvGoCreate:
- // 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}
- if err := validateCtx(curCtx, reqs); err != nil {
- return false, err
- }
- // If we have a goroutine, it must be running.
- if state, ok := o.gStates[curCtx.G]; ok && state.status != go122.GoRunning {
- return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
- }
- // This goroutine created another. Add a state for it.
- newgid := GoID(ev.args[0])
- 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)}
- o.queue.push(currentEvent())
- case go122.EvGoDestroy, go122.EvGoStop, go122.EvGoBlock:
- // These are goroutine events that all require an active running
- // goroutine on some thread. They must *always* be advance-able,
- // since running goroutines are bound to their M.
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- state, 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 state.status != go122.GoRunning {
- return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
- }
- // Handle each case slightly differently; we just group them together
- // because they have shared preconditions.
- switch typ {
- case go122.EvGoDestroy:
- // This goroutine is exiting itself.
- delete(o.gStates, curCtx.G)
- newCtx.G = NoGoroutine
- case go122.EvGoStop:
- // Goroutine stopped (yielded). It's runnable but not running on this M.
- state.status = go122.GoRunnable
- newCtx.G = NoGoroutine
- case go122.EvGoBlock:
- // Goroutine blocked. It's waiting now and not running on this M.
- state.status = go122.GoWaiting
- newCtx.G = NoGoroutine
- }
- o.queue.push(currentEvent())
- case go122.EvGoStart:
- gid := GoID(ev.args[0])
- seq := makeSeq(gen, ev.args[1])
- state, ok := o.gStates[gid]
- if !ok || state.status != go122.GoRunnable || !seq.succeeds(state.seq) {
- // We can't make an inference as to whether this is bad. We could just be seeing
- // a GoStart 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
- }
- // We can advance this goroutine. Check some invariants.
- reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MustNotHave}
- if err := validateCtx(curCtx, reqs); err != nil {
- return false, err
- }
- state.status = go122.GoRunning
- state.seq = seq
+ newCtx := curCtx
+ switch status {
+ case go122.GoRunning:
+ // Bind the goroutine to the new context, since it's running.
newCtx.G = gid
- o.queue.push(currentEvent())
- case go122.EvGoUnblock:
- // N.B. These both reference the goroutine to unblock, not the current goroutine.
- gid := GoID(ev.args[0])
- seq := makeSeq(gen, ev.args[1])
- state, ok := o.gStates[gid]
- if !ok || state.status != go122.GoWaiting || !seq.succeeds(state.seq) {
- // We can't make an inference as to whether this is bad. We could just be seeing
- // a GoUnblock on a different M before the goroutine was created and blocked itself,
- // before it had its state emitted, or before we got to the right point in the trace yet.
- return false, nil
+ case go122.GoSyscall:
+ if mid == NoThread {
+ return curCtx, false, fmt.Errorf("found goroutine %d in syscall without a thread", gid)
}
- state.status = go122.GoRunnable
- state.seq = seq
- // N.B. No context to validate. Basically anything can unblock
- // a goroutine (e.g. sysmon).
- o.queue.push(currentEvent())
- case go122.EvGoSyscallBegin:
- // Entering a syscall requires an active running goroutine with a
- // proc on some thread. It is always advancable.
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
+ // Is the syscall on this thread? If so, bind it to the context.
+ // Otherwise, we're talking about a G sitting in a syscall on an M.
+ // Validate the named M.
+ if mid == curCtx.M {
+ if gen != o.initialGen && curCtx.G != gid {
+ // If this isn't the first generation, we *must* have seen this
+ // binding occur already. Even if the G was blocked in a syscall
+ // for multiple generations since trace start, we would have seen
+ // a previous GoStatus event that bound the goroutine to an M.
+ return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, curCtx.G)
+ }
+ newCtx.G = gid
+ break
}
- state, 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)
+ // Now we're talking about a thread and goroutine that have been
+ // blocked on a syscall for the entire generation. This case must
+ // not have a P; the runtime makes sure that all Ps are traced at
+ // the beginning of a generation, which involves taking a P back
+ // from every thread.
+ ms, ok := o.mStates[mid]
+ if ok {
+ // This M has been seen. That means we must have seen this
+ // goroutine go into a syscall on this thread at some point.
+ if ms.g != gid {
+ // But the G on the M doesn't match. Something's wrong.
+ return curCtx, false, fmt.Errorf("inconsistent thread for syscalling goroutine %d: thread has goroutine %d", gid, ms.g)
+ }
+ // This case is just a Syscall->Syscall event, which needs to
+ // appear as having the G currently bound to this M.
+ curCtx.G = ms.g
+ } else if !ok {
+ // The M hasn't been seen yet. That means this goroutine
+ // has just been sitting in a syscall on this M. Create
+ // a state for it.
+ o.mStates[mid] = &mState{g: gid, p: NoProc}
+ // Don't set curCtx.G in this case because this event is the
+ // binding event (and curCtx represents the "before" state).
}
- if state.status != go122.GoRunning {
- return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
- }
- // Goroutine entered a syscall. It's still running on this P and M.
- state.status = go122.GoSyscall
- pState, ok := o.pStates[curCtx.P]
- if !ok {
- return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
- }
- pState.status = go122.ProcSyscall
- // Validate the P sequence number on the event and advance it.
- //
- // We have a P sequence number for what is supposed to be a goroutine event
- // so that we can correctly model P stealing. Without this sequence number here,
- // the syscall from which a ProcSteal event is stealing can be ambiguous in the
- // face of broken timestamps. See the go122-syscall-steal-proc-ambiguous test for
- // more details.
- //
- // Note that because this sequence number only exists as a tool for disambiguation,
- // we can enforce that we have the right sequence number at this point; we don't need
- // to back off and see if any other events will advance. This is a running P.
- pSeq := makeSeq(gen, ev.args[0])
- if !pSeq.succeeds(pState.seq) {
- return false, fmt.Errorf("failed to advance %s: can't make sequence: %s -> %s", go122.EventString(typ), pState.seq, pSeq)
- }
- pState.seq = pSeq
- o.queue.push(currentEvent())
- case go122.EvGoSyscallEnd:
- // This event is always advance-able because it happens on the same
- // thread that EvGoSyscallStart happened, and the goroutine can't leave
- // that thread until its done.
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- state, 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 state.status != go122.GoSyscall {
- return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
- }
- state.status = go122.GoRunning
+ // Update the current context to the M we're talking about.
+ curCtx.M = mid
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
- // Transfer the P back to running from syscall.
- pState, ok := o.pStates[curCtx.P]
- if !ok {
- return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
- }
- if pState.status != go122.ProcSyscall {
- return false, fmt.Errorf("expected proc %d in state %v, but got %v instead", curCtx.P, go122.ProcSyscall, pState.status)
- }
- pState.status = go122.ProcRunning
- o.queue.push(currentEvent())
- case go122.EvGoSyscallEndBlocked:
- // This event becomes advanceable when its P is not in a syscall state
- // (lack of a P altogether is also acceptable for advancing).
- // The transfer out of ProcSyscall can happen either voluntarily via
- // ProcStop or involuntarily via ProcSteal. We may also acquire a new P
- // before we get here (after the transfer out) but that's OK: that new
- // P won't be in the ProcSyscall state anymore.
- //
- // Basically: while we have a preemptible P, don't advance, because we
- // *know* from the event that we're going to lose it at some point during
- // the syscall. We shouldn't advance until that happens.
- if curCtx.P != NoProc {
- pState, ok := o.pStates[curCtx.P]
- if !ok {
- return false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(typ))
- }
- if pState.status == go122.ProcSyscall {
- return false, nil
- }
- }
- // As mentioned above, we may have a P here if we ProcStart
- // before this event.
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
- return false, err
- }
- state, 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 state.status != go122.GoSyscall {
- return false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(typ), GoRunning)
- }
- newCtx.G = NoGoroutine
- state.status = go122.GoRunnable
- o.queue.push(currentEvent())
- case go122.EvGoCreateSyscall:
- // This event indicates that a goroutine is effectively
- // being created out of a cgo callback. Such a goroutine
- // is 'created' in the syscall state.
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustNotHave}); err != nil {
- return false, err
- }
- // This goroutine is effectively being created. Add a state for it.
- newgid := GoID(ev.args[0])
- if _, ok := o.gStates[newgid]; ok {
- return false, fmt.Errorf("tried to create goroutine (%v) in syscall that already exists", newgid)
- }
- o.gStates[newgid] = &gState{id: newgid, status: go122.GoSyscall, seq: makeSeq(gen, 0)}
- // Goroutine is executing. Bind it to the context.
- newCtx.G = newgid
- o.queue.push(currentEvent())
- case go122.EvGoDestroySyscall:
- // This event indicates that a goroutine created for a
- // cgo callback is disappearing, either because the callback
- // ending or the C thread that called it is being destroyed.
- //
- // Also, treat this as if we lost our P too.
- // The thread ID may be reused by the platform and we'll get
- // really confused if we try to steal the P is this is running
- // with later. The new M with the same ID could even try to
- // steal back this P from itself!
- //
- // The runtime is careful to make sure that any GoCreateSyscall
- // event will enter the runtime emitting events for reacquiring a P.
- //
- // Note: we might have a P here. The P might not be released
- // eagerly by the runtime, and it might get stolen back later
- // (or never again, if the program is going to exit).
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
- return false, err
- }
- // Check to make sure the goroutine exists in the right state.
- state, 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 state.status != go122.GoSyscall {
- return false, fmt.Errorf("%s event for goroutine that's not %v", go122.EventString(typ), GoSyscall)
- }
+func (o *ordering) advanceGoCreate(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // 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}
+ if err := validateCtx(curCtx, reqs); err != nil {
+ return curCtx, false, err
+ }
+ // If we have a goroutine, it must be running.
+ if state, ok := o.gStates[curCtx.G]; ok && state.status != go122.GoRunning {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+ }
+ // This goroutine created another. Add a state for it.
+ newgid := GoID(ev.args[0])
+ if _, ok := o.gStates[newgid]; ok {
+ return curCtx, false, fmt.Errorf("tried to create goroutine (%v) that already exists", newgid)
+ }
+ status := go122.GoRunnable
+ if ev.typ == go122.EvGoCreateBlocked {
+ status = go122.GoWaiting
+ }
+ o.gStates[newgid] = &gState{id: newgid, status: status, seq: makeSeq(gen, 0)}
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoStopExec(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // These are goroutine events that all require an active running
+ // goroutine on some thread. They must *always* be advance-able,
+ // since running goroutines are bound to their M.
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ state, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if state.status != go122.GoRunning {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+ }
+ // Handle each case slightly differently; we just group them together
+ // because they have shared preconditions.
+ newCtx := curCtx
+ switch ev.typ {
+ case go122.EvGoDestroy:
// This goroutine is exiting itself.
delete(o.gStates, curCtx.G)
newCtx.G = NoGoroutine
+ case go122.EvGoStop:
+ // Goroutine stopped (yielded). It's runnable but not running on this M.
+ state.status = go122.GoRunnable
+ newCtx.G = NoGoroutine
+ case go122.EvGoBlock:
+ // Goroutine blocked. It's waiting now and not running on this M.
+ state.status = go122.GoWaiting
+ newCtx.G = NoGoroutine
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
- // If we have a proc, then we're dissociating from it now. See the comment at the top of the case.
- if curCtx.P != NoProc {
- pState, ok := o.pStates[curCtx.P]
- if !ok {
- return false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(typ))
- }
- if pState.status != go122.ProcSyscall {
- return false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(typ))
- }
- // See the go122-create-syscall-reuse-thread-id test case for more details.
- pState.status = go122.ProcSyscallAbandoned
- newCtx.P = NoProc
+func (o *ordering) advanceGoStart(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ gid := GoID(ev.args[0])
+ seq := makeSeq(gen, ev.args[1])
+ state, ok := o.gStates[gid]
+ if !ok || state.status != go122.GoRunnable || !seq.succeeds(state.seq) {
+ // We can't make an inference as to whether this is bad. We could just be seeing
+ // a GoStart 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 curCtx, false, nil
+ }
+ // We can advance this goroutine. Check some invariants.
+ reqs := event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MustNotHave}
+ if err := validateCtx(curCtx, reqs); err != nil {
+ return curCtx, false, err
+ }
+ state.status = go122.GoRunning
+ state.seq = seq
+ newCtx := curCtx
+ newCtx.G = gid
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
- // 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.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
- o.queue.push(extra)
+func (o *ordering) advanceGoUnblock(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // N.B. These both reference the goroutine to unblock, not the current goroutine.
+ gid := GoID(ev.args[0])
+ seq := makeSeq(gen, ev.args[1])
+ state, ok := o.gStates[gid]
+ if !ok || state.status != go122.GoWaiting || !seq.succeeds(state.seq) {
+ // We can't make an inference as to whether this is bad. We could just be seeing
+ // a GoUnblock on a different M before the goroutine was created and blocked itself,
+ // before it had its state emitted, or before we got to the right point in the trace yet.
+ return curCtx, false, nil
+ }
+ state.status = go122.GoRunnable
+ state.seq = seq
+ // N.B. No context to validate. Basically anything can unblock
+ // a goroutine (e.g. sysmon).
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSwitch(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // 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 curCtx, false, err
+ }
+ curGState, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if curGState.status != go122.GoRunning {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.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 curCtx, false, nil
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+
+ // 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 ev.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 := curCtx
+ 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]))
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // Entering a syscall requires an active running goroutine with a
+ // proc on some thread. It is always advancable.
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ state, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if state.status != go122.GoRunning {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+ }
+ // Goroutine entered a syscall. It's still running on this P and M.
+ state.status = go122.GoSyscall
+ pState, ok := o.pStates[curCtx.P]
+ if !ok {
+ return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
+ }
+ pState.status = go122.ProcSyscall
+ // Validate the P sequence number on the event and advance it.
+ //
+ // We have a P sequence number for what is supposed to be a goroutine event
+ // so that we can correctly model P stealing. Without this sequence number here,
+ // the syscall from which a ProcSteal event is stealing can be ambiguous in the
+ // face of broken timestamps. See the go122-syscall-steal-proc-ambiguous test for
+ // more details.
+ //
+ // Note that because this sequence number only exists as a tool for disambiguation,
+ // we can enforce that we have the right sequence number at this point; we don't need
+ // to back off and see if any other events will advance. This is a running P.
+ pSeq := makeSeq(gen, ev.args[0])
+ if !pSeq.succeeds(pState.seq) {
+ return curCtx, false, fmt.Errorf("failed to advance %s: can't make sequence: %s -> %s", go122.EventString(ev.typ), pState.seq, pSeq)
+ }
+ pState.seq = pSeq
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // This event is always advance-able because it happens on the same
+ // thread that EvGoSyscallStart happened, and the goroutine can't leave
+ // that thread until its done.
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ state, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if state.status != go122.GoSyscall {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+ }
+ state.status = go122.GoRunning
+
+ // Transfer the P back to running from syscall.
+ pState, ok := o.pStates[curCtx.P]
+ if !ok {
+ return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
+ }
+ if pState.status != go122.ProcSyscall {
+ return curCtx, false, fmt.Errorf("expected proc %d in state %v, but got %v instead", curCtx.P, go122.ProcSyscall, pState.status)
+ }
+ pState.status = go122.ProcRunning
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoSyscallEndBlocked(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // This event becomes advanceable when its P is not in a syscall state
+ // (lack of a P altogether is also acceptable for advancing).
+ // The transfer out of ProcSyscall can happen either voluntarily via
+ // ProcStop or involuntarily via ProcSteal. We may also acquire a new P
+ // before we get here (after the transfer out) but that's OK: that new
+ // P won't be in the ProcSyscall state anymore.
+ //
+ // Basically: while we have a preemptible P, don't advance, because we
+ // *know* from the event that we're going to lose it at some point during
+ // the syscall. We shouldn't advance until that happens.
+ if curCtx.P != NoProc {
+ pState, ok := o.pStates[curCtx.P]
+ if !ok {
+ return curCtx, false, fmt.Errorf("uninitialized proc %d found during %s", curCtx.P, go122.EventString(ev.typ))
}
- o.queue.push(currentEvent())
+ if pState.status == go122.ProcSyscall {
+ return curCtx, false, nil
+ }
+ }
+ // As mentioned above, we may have a P here if we ProcStart
+ // before this event.
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
+ return curCtx, false, err
+ }
+ state, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if state.status != go122.GoSyscall {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %s", go122.EventString(ev.typ), GoRunning)
+ }
+ newCtx := curCtx
+ newCtx.G = NoGoroutine
+ state.status = go122.GoRunnable
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+func (o *ordering) advanceGoCreateSyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // This event indicates that a goroutine is effectively
+ // being created out of a cgo callback. Such a goroutine
+ // is 'created' in the syscall state.
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustNotHave}); err != nil {
+ return curCtx, false, err
+ }
+ // This goroutine is effectively being created. Add a state for it.
+ newgid := GoID(ev.args[0])
+ if _, ok := o.gStates[newgid]; ok {
+ return curCtx, false, fmt.Errorf("tried to create goroutine (%v) in syscall that already exists", newgid)
+ }
+ o.gStates[newgid] = &gState{id: newgid, status: go122.GoSyscall, seq: makeSeq(gen, 0)}
+ // Goroutine is executing. Bind it to the context.
+ newCtx := curCtx
+ newCtx.G = newgid
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceGoDestroySyscall(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // This event indicates that a goroutine created for a
+ // cgo callback is disappearing, either because the callback
+ // ending or the C thread that called it is being destroyed.
+ //
+ // Also, treat this as if we lost our P too.
+ // The thread ID may be reused by the platform and we'll get
+ // really confused if we try to steal the P is this is running
+ // with later. The new M with the same ID could even try to
+ // steal back this P from itself!
+ //
+ // The runtime is careful to make sure that any GoCreateSyscall
+ // event will enter the runtime emitting events for reacquiring a P.
+ //
+ // Note: we might have a P here. The P might not be released
+ // eagerly by the runtime, and it might get stolen back later
+ // (or never again, if the program is going to exit).
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MayHave, Goroutine: event.MustHave}); err != nil {
+ return curCtx, false, err
+ }
+ // Check to make sure the goroutine exists in the right state.
+ state, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("event %s for goroutine (%v) that doesn't exist", go122.EventString(ev.typ), curCtx.G)
+ }
+ if state.status != go122.GoSyscall {
+ return curCtx, false, fmt.Errorf("%s event for goroutine that's not %v", go122.EventString(ev.typ), GoSyscall)
+ }
+ // This goroutine is exiting itself.
+ delete(o.gStates, curCtx.G)
+ newCtx := curCtx
+ newCtx.G = NoGoroutine
+
+ // If we have a proc, then we're dissociating from it now. See the comment at the top of the case.
+ if curCtx.P != NoProc {
+ pState, ok := o.pStates[curCtx.P]
+ if !ok {
+ return curCtx, false, fmt.Errorf("found invalid proc %d during %s", curCtx.P, go122.EventString(ev.typ))
+ }
+ if pState.status != go122.ProcSyscall {
+ return curCtx, false, fmt.Errorf("proc %d in unexpected state %s during %s", curCtx.P, pState.status, go122.EventString(ev.typ))
+ }
+ // See the go122-create-syscall-reuse-thread-id test case for more details.
+ pState.status = go122.ProcSyscallAbandoned
+ newCtx.P = NoProc
+
+ // Queue an extra self-ProcSteal event.
+ extra := makeEvent(evt, curCtx, go122.EvProcSteal, ev.time, uint64(curCtx.P))
+ extra.base.extra(version.Go122)[0] = uint64(go122.ProcSyscall)
+ o.queue.push(extra)
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return newCtx, true, nil
+}
+
+func (o *ordering) advanceUserTaskBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle tasks. Tasks are interesting because:
// - There's no Begin event required to reference a task.
// - End for a particular task ID can appear multiple times.
@@ -603,265 +772,291 @@
// thing we have to be sure of is that a task didn't begin
// after it had already begun. Task IDs are allowed to be
// reused, so we don't care about a Begin after an End.
- case go122.EvUserTaskBegin:
- id := TaskID(ev.args[0])
- if _, ok := o.activeTasks[id]; ok {
- return false, fmt.Errorf("task ID conflict: %d", id)
- }
- // Get the parent ID, but don't validate it. There's no guarantee
- // we actually have information on whether it's active.
- parentID := TaskID(ev.args[1])
- if parentID == BackgroundTask {
- // Note: a value of 0 here actually means no parent, *not* the
- // background task. Automatic background task attachment only
- // applies to regions.
- parentID = NoTask
- ev.args[1] = uint64(NoTask)
- }
+ id := TaskID(ev.args[0])
+ if _, ok := o.activeTasks[id]; ok {
+ return curCtx, false, fmt.Errorf("task ID conflict: %d", id)
+ }
+ // Get the parent ID, but don't validate it. There's no guarantee
+ // we actually have information on whether it's active.
+ parentID := TaskID(ev.args[1])
+ if parentID == BackgroundTask {
+ // Note: a value of 0 here actually means no parent, *not* the
+ // background task. Automatic background task attachment only
+ // applies to regions.
+ parentID = NoTask
+ ev.args[1] = uint64(NoTask)
+ }
- // Validate the name and record it. We'll need to pass it through to
- // EvUserTaskEnd.
- nameID := stringID(ev.args[2])
- name, ok := evt.strings.get(nameID)
- if !ok {
- return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
- }
- o.activeTasks[id] = taskState{name: name, parentID: parentID}
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvUserTaskEnd:
- id := TaskID(ev.args[0])
- if ts, ok := o.activeTasks[id]; ok {
- // Smuggle the task info. This may happen in a different generation,
- // which may not have the name in its string table. Add it to the extra
- // strings table so we can look it up later.
- ev.extra(version.Go122)[0] = uint64(ts.parentID)
- ev.extra(version.Go122)[1] = uint64(evt.addExtraString(ts.name))
- delete(o.activeTasks, id)
- } else {
- // Explicitly clear the task info.
- ev.extra(version.Go122)[0] = uint64(NoTask)
- ev.extra(version.Go122)[1] = uint64(evt.addExtraString(""))
- }
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+ // Validate the name and record it. We'll need to pass it through to
+ // EvUserTaskEnd.
+ nameID := stringID(ev.args[2])
+ name, ok := evt.strings.get(nameID)
+ if !ok {
+ return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+ }
+ o.activeTasks[id] = taskState{name: name, parentID: parentID}
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
- // Handle user regions.
- case go122.EvUserRegionBegin:
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- tid := TaskID(ev.args[0])
- nameID := stringID(ev.args[1])
- name, ok := evt.strings.get(nameID)
- if !ok {
- return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
- }
- gState, ok := o.gStates[curCtx.G]
- if !ok {
- return false, fmt.Errorf("encountered EvUserRegionBegin without known state for current goroutine %d", curCtx.G)
- }
- if err := gState.beginRegion(userRegion{tid, name}); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvUserRegionEnd:
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- tid := TaskID(ev.args[0])
- nameID := stringID(ev.args[1])
- name, ok := evt.strings.get(nameID)
- if !ok {
- return false, fmt.Errorf("invalid string ID %v for %v event", nameID, typ)
- }
- gState, ok := o.gStates[curCtx.G]
- if !ok {
- return false, fmt.Errorf("encountered EvUserRegionEnd without known state for current goroutine %d", curCtx.G)
- }
- if err := gState.endRegion(userRegion{tid, name}); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+func (o *ordering) advanceUserTaskEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ id := TaskID(ev.args[0])
+ if ts, ok := o.activeTasks[id]; ok {
+ // Smuggle the task info. This may happen in a different generation,
+ // which may not have the name in its string table. Add it to the extra
+ // strings table so we can look it up later.
+ ev.extra(version.Go122)[0] = uint64(ts.parentID)
+ ev.extra(version.Go122)[1] = uint64(evt.addExtraString(ts.name))
+ delete(o.activeTasks, id)
+ } else {
+ // Explicitly clear the task info.
+ ev.extra(version.Go122)[0] = uint64(NoTask)
+ ev.extra(version.Go122)[1] = uint64(evt.addExtraString(""))
+ }
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
- // Handle the GC mark phase.
- //
- // We have sequence numbers for both start and end because they
- // can happen on completely different threads. We want an explicit
- // partial order edge between start and end here, otherwise we're
- // relying entirely on timestamps to make sure we don't advance a
- // GCEnd for a _different_ GC cycle if timestamps are wildly broken.
- case go122.EvGCActive:
- seq := ev.args[0]
- if gen == o.initialGen {
- if o.gcState != gcUndetermined {
- return false, fmt.Errorf("GCActive in the first generation isn't first GC event")
- }
- o.gcSeq = seq
- o.gcState = gcRunning
- o.queue.push(currentEvent())
- break
- }
- if seq != o.gcSeq+1 {
- // This is not the right GC cycle.
- return false, nil
- }
- if o.gcState != gcRunning {
- return false, fmt.Errorf("encountered GCActive while GC was not in progress")
- }
- o.gcSeq = seq
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvGCBegin:
- seq := ev.args[0]
- if o.gcState == gcUndetermined {
- o.gcSeq = seq
- o.gcState = gcRunning
- o.queue.push(currentEvent())
- break
- }
- if seq != o.gcSeq+1 {
- // This is not the right GC cycle.
- return false, nil
- }
- if o.gcState == gcRunning {
- return false, fmt.Errorf("encountered GCBegin while GC was already in progress")
+func (o *ordering) advanceUserRegionBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ tid := TaskID(ev.args[0])
+ nameID := stringID(ev.args[1])
+ name, ok := evt.strings.get(nameID)
+ if !ok {
+ return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+ }
+ gState, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("encountered EvUserRegionBegin without known state for current goroutine %d", curCtx.G)
+ }
+ if err := gState.beginRegion(userRegion{tid, name}); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceUserRegionEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ tid := TaskID(ev.args[0])
+ nameID := stringID(ev.args[1])
+ name, ok := evt.strings.get(nameID)
+ if !ok {
+ return curCtx, false, fmt.Errorf("invalid string ID %v for %v event", nameID, ev.typ)
+ }
+ gState, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("encountered EvUserRegionEnd without known state for current goroutine %d", curCtx.G)
+ }
+ if err := gState.endRegion(userRegion{tid, name}); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+// Handle the GC mark phase.
+//
+// We have sequence numbers for both start and end because they
+// can happen on completely different threads. We want an explicit
+// partial order edge between start and end here, otherwise we're
+// relying entirely on timestamps to make sure we don't advance a
+// GCEnd for a _different_ GC cycle if timestamps are wildly broken.
+func (o *ordering) advanceGCActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ seq := ev.args[0]
+ if gen == o.initialGen {
+ if o.gcState != gcUndetermined {
+ return curCtx, false, fmt.Errorf("GCActive in the first generation isn't first GC event")
}
o.gcSeq = seq
o.gcState = gcRunning
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvGCEnd:
- seq := ev.args[0]
- if seq != o.gcSeq+1 {
- // This is not the right GC cycle.
- return false, nil
- }
- if o.gcState == gcNotRunning {
- return false, fmt.Errorf("encountered GCEnd when GC was not in progress")
- }
- if o.gcState == gcUndetermined {
- return false, fmt.Errorf("encountered GCEnd when GC was in an undetermined state")
- }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+ }
+ if seq != o.gcSeq+1 {
+ // This is not the right GC cycle.
+ return curCtx, false, nil
+ }
+ if o.gcState != gcRunning {
+ return curCtx, false, fmt.Errorf("encountered GCActive while GC was not in progress")
+ }
+ o.gcSeq = seq
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGCBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ seq := ev.args[0]
+ if o.gcState == gcUndetermined {
o.gcSeq = seq
- o.gcState = gcNotRunning
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+ o.gcState = gcRunning
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+ }
+ if seq != o.gcSeq+1 {
+ // This is not the right GC cycle.
+ return curCtx, false, nil
+ }
+ if o.gcState == gcRunning {
+ return curCtx, false, fmt.Errorf("encountered GCBegin while GC was already in progress")
+ }
+ o.gcSeq = seq
+ o.gcState = gcRunning
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+func (o *ordering) advanceGCEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ seq := ev.args[0]
+ if seq != o.gcSeq+1 {
+ // This is not the right GC cycle.
+ return curCtx, false, nil
+ }
+ if o.gcState == gcNotRunning {
+ return curCtx, false, fmt.Errorf("encountered GCEnd when GC was not in progress")
+ }
+ if o.gcState == gcUndetermined {
+ return curCtx, false, fmt.Errorf("encountered GCEnd when GC was in an undetermined state")
+ }
+ o.gcSeq = seq
+ o.gcState = gcNotRunning
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceAnnotation(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle simple instantaneous events that require a G.
- case go122.EvGoLabel, go122.EvProcsChange, go122.EvUserLog:
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
- // Handle allocation states, which don't require a G.
- case go122.EvHeapAlloc, go122.EvHeapGoal:
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+func (o *ordering) advanceHeapMetric(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ // Handle allocation metrics, which don't require a G.
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+func (o *ordering) advanceGCSweepBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle sweep, which is bound to a P and doesn't require a G.
- case go122.EvGCSweepBegin:
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
- return false, err
- }
- if err := o.pStates[curCtx.P].beginRange(makeRangeType(typ, 0)); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvGCSweepActive:
- pid := ProcID(ev.args[0])
- // N.B. In practice Ps can't block while they're sweeping, so this can only
- // ever reference curCtx.P. However, be lenient about this like we are with
- // GCMarkAssistActive; there's no reason the runtime couldn't change to block
- // in the middle of a sweep.
- pState, ok := o.pStates[pid]
- if !ok {
- return false, fmt.Errorf("encountered GCSweepActive for unknown proc %d", pid)
- }
- if err := pState.activeRange(makeRangeType(typ, 0), gen == o.initialGen); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvGCSweepEnd:
- if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
- return false, err
- }
- _, err := o.pStates[curCtx.P].endRange(typ)
- if err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+ return curCtx, false, err
+ }
+ if err := o.pStates[curCtx.P].beginRange(makeRangeType(ev.typ, 0)); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+func (o *ordering) advanceGCSweepActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ pid := ProcID(ev.args[0])
+ // N.B. In practice Ps can't block while they're sweeping, so this can only
+ // ever reference curCtx.P. However, be lenient about this like we are with
+ // GCMarkAssistActive; there's no reason the runtime couldn't change to block
+ // in the middle of a sweep.
+ pState, ok := o.pStates[pid]
+ if !ok {
+ return curCtx, false, fmt.Errorf("encountered GCSweepActive for unknown proc %d", pid)
+ }
+ if err := pState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGCSweepEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ if err := validateCtx(curCtx, event.SchedReqs{Thread: event.MustHave, Proc: event.MustHave, Goroutine: event.MayHave}); err != nil {
+ return curCtx, false, err
+ }
+ _, err := o.pStates[curCtx.P].endRange(ev.typ)
+ if err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeBegin(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
// Handle special goroutine-bound event ranges.
- case go122.EvSTWBegin, go122.EvGCMarkAssistBegin:
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- desc := stringID(0)
- if typ == go122.EvSTWBegin {
- desc = stringID(ev.args[0])
- }
- gState, ok := o.gStates[curCtx.G]
- if !ok {
- return false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", typ, curCtx.G)
- }
- if err := gState.beginRange(makeRangeType(typ, desc)); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvGCMarkAssistActive:
- gid := GoID(ev.args[0])
- // N.B. Like GoStatus, this can happen at any time, because it can
- // reference a non-running goroutine. Don't check anything about the
- // current scheduler context.
- gState, ok := o.gStates[gid]
- if !ok {
- return false, fmt.Errorf("uninitialized goroutine %d found during %s", gid, go122.EventString(typ))
- }
- if err := gState.activeRange(makeRangeType(typ, 0), gen == o.initialGen); err != nil {
- return false, err
- }
- o.queue.push(currentEvent())
- case go122.EvSTWEnd, go122.EvGCMarkAssistEnd:
- if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
- return false, err
- }
- gState, ok := o.gStates[curCtx.G]
- if !ok {
- return false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", typ, curCtx.G)
- }
- desc, err := gState.endRange(typ)
- if err != nil {
- return false, err
- }
- if typ == go122.EvSTWEnd {
- // Smuggle the kind into the event.
- // Don't use ev.extra here so we have symmetry with STWBegin.
- ev.args[0] = uint64(desc)
- }
- o.queue.push(currentEvent())
- default:
- return false, fmt.Errorf("bad event type found while ordering: %v", ev.typ)
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
}
- if ms != nil {
- // Update the mState for this event.
- ms.p = newCtx.P
- ms.g = newCtx.G
+ desc := stringID(0)
+ if ev.typ == go122.EvSTWBegin {
+ desc = stringID(ev.args[0])
}
- return true, nil
+ gState, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
+ }
+ if err := gState.beginRange(makeRangeType(ev.typ, desc)); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeActive(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ gid := GoID(ev.args[0])
+ // N.B. Like GoStatus, this can happen at any time, because it can
+ // reference a non-running goroutine. Don't check anything about the
+ // current scheduler context.
+ gState, ok := o.gStates[gid]
+ if !ok {
+ return curCtx, false, fmt.Errorf("uninitialized goroutine %d found during %s", gid, go122.EventString(ev.typ))
+ }
+ if err := gState.activeRange(makeRangeType(ev.typ, 0), gen == o.initialGen); err != nil {
+ return curCtx, false, err
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
+}
+
+func (o *ordering) advanceGoRangeEnd(ev *baseEvent, evt *evTable, m ThreadID, gen uint64, curCtx schedCtx) (schedCtx, bool, error) {
+ if err := validateCtx(curCtx, event.UserGoReqs); err != nil {
+ return curCtx, false, err
+ }
+ gState, ok := o.gStates[curCtx.G]
+ if !ok {
+ return curCtx, false, fmt.Errorf("encountered event of type %d without known state for current goroutine %d", ev.typ, curCtx.G)
+ }
+ desc, err := gState.endRange(ev.typ)
+ if err != nil {
+ return curCtx, false, err
+ }
+ if ev.typ == go122.EvSTWEnd {
+ // Smuggle the kind into the event.
+ // Don't use ev.extra here so we have symmetry with STWBegin.
+ ev.args[0] = uint64(desc)
+ }
+ o.queue.push(Event{table: evt, ctx: curCtx, base: *ev})
+ return curCtx, true, nil
}
// Next returns the next event in the ordering.
@@ -1159,3 +1354,21 @@
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
+}
diff --git a/trace/reader.go b/trace/reader.go
index 088565e..315c5b2 100644
--- a/trace/reader.go
+++ b/trace/reader.go
@@ -50,7 +50,7 @@
return &Reader{
go121Events: convertOldFormat(tr),
}, nil
- case version.Go122:
+ case version.Go122, version.Go123:
return &Reader{
r: br,
order: ordering{