proc,service,terminal: add ways to list goroutines waiting on a channel (#3481)
Adds -chan option to the goroutines command to list only the goroutines running on a specified channel. Also when printing a variable if it is a channel also print the list of goroutines that are waiting on it.
This commit is contained in:
parent
80e6c28ab2
commit
0b35fe6d42
@ -386,7 +386,7 @@ Aliases: gr
|
||||
## goroutines
|
||||
List program goroutines.
|
||||
|
||||
goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument] [-exec command]
|
||||
goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument] [-chan expr] [-exec command]
|
||||
|
||||
Print out info for every goroutine. The flag controls what information is shown along with each goroutine:
|
||||
|
||||
@ -437,6 +437,14 @@ To only display user (or runtime) goroutines, use:
|
||||
goroutines -with user
|
||||
goroutines -without user
|
||||
|
||||
CHANNELS
|
||||
|
||||
To only show goroutines waiting to send to or receive from a specific channel use:
|
||||
|
||||
goroutines -chan expr
|
||||
|
||||
Note that 'expr' must not contain spaces.
|
||||
|
||||
GROUPING
|
||||
|
||||
goroutines -group (userloc|curloc|goloc|startloc|running|user)
|
||||
|
@ -51,7 +51,7 @@ checkpoints() | Equivalent to API call [ListCheckpoints](https://godoc.org/githu
|
||||
dynamic_libraries() | Equivalent to API call [ListDynamicLibraries](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListDynamicLibraries)
|
||||
function_args(Scope, Cfg) | Equivalent to API call [ListFunctionArgs](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctionArgs)
|
||||
functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListFunctions)
|
||||
goroutines(Start, Count, Filters, GoroutineGroupingOptions) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
|
||||
goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines)
|
||||
local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars)
|
||||
package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars)
|
||||
packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo)
|
||||
|
26
_fixtures/changoroutines.go
Normal file
26
_fixtures/changoroutines.go
Normal file
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
blockingchan1 := make(chan int)
|
||||
blockingchan2 := make(chan int)
|
||||
|
||||
go sendToChan("one", blockingchan1)
|
||||
go sendToChan("two", blockingchan1)
|
||||
go recvFromChan(blockingchan2)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
runtime.Breakpoint()
|
||||
}
|
||||
|
||||
func sendToChan(name string, ch chan<- int) {
|
||||
ch <- 1
|
||||
}
|
||||
|
||||
func recvFromChan(ch <-chan int) {
|
||||
<-ch
|
||||
}
|
@ -214,6 +214,89 @@ func (scope *EvalScope) EvalExpression(expr string, cfg LoadConfig) (*Variable,
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
// ChanGoroutines returns the list of goroutines waiting to receive from or
|
||||
// send to the channel.
|
||||
func (scope *EvalScope) ChanGoroutines(expr string, start, count int) ([]int64, error) {
|
||||
t, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := scope.evalAST(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v.Kind != reflect.Chan {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
structMemberMulti := func(v *Variable, names ...string) *Variable {
|
||||
for _, name := range names {
|
||||
var err error
|
||||
v, err = v.structMember(name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
waitqFirst := func(qname string) *Variable {
|
||||
qvar := structMemberMulti(v, qname, "first")
|
||||
if qvar == nil {
|
||||
return nil
|
||||
}
|
||||
return qvar.maybeDereference()
|
||||
}
|
||||
|
||||
var goids []int64
|
||||
|
||||
waitqToGoIDSlice := func(qvar *Variable) error {
|
||||
if qvar == nil {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
if qvar.Addr == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(goids) > count {
|
||||
return nil
|
||||
}
|
||||
goidVar := structMemberMulti(qvar, "g", "goid")
|
||||
if goidVar == nil {
|
||||
return nil
|
||||
}
|
||||
goidVar.loadValue(loadSingleValue)
|
||||
if goidVar.Unreadable != nil {
|
||||
return goidVar.Unreadable
|
||||
}
|
||||
goid, _ := constant.Int64Val(goidVar.Value)
|
||||
if start > 0 {
|
||||
start--
|
||||
} else {
|
||||
goids = append(goids, goid)
|
||||
}
|
||||
|
||||
nextVar, err := qvar.structMember("next")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
qvar = nextVar.maybeDereference()
|
||||
}
|
||||
}
|
||||
|
||||
recvqVar := waitqFirst("recvq")
|
||||
err = waitqToGoIDSlice(recvqVar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sendqVar := waitqFirst("sendq")
|
||||
err = waitqToGoIDSlice(sendqVar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return goids, nil
|
||||
}
|
||||
|
||||
func isAssignment(err error) (int, bool) {
|
||||
el, isScannerErr := err.(scanner.ErrorList)
|
||||
if isScannerErr && el[0].Msg == "expected '==', found '='" {
|
||||
|
@ -228,7 +228,7 @@ If called with the locspec argument it will delete all the breakpoints matching
|
||||
toggle <breakpoint name or id>`},
|
||||
{aliases: []string{"goroutines", "grs"}, group: goroutineCmds, cmdFn: c.goroutines, helpMsg: `List program goroutines.
|
||||
|
||||
goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument] [-exec command]
|
||||
goroutines [-u|-r|-g|-s] [-t [depth]] [-l] [-with loc expr] [-without loc expr] [-group argument] [-chan expr] [-exec command]
|
||||
|
||||
Print out info for every goroutine. The flag controls what information is shown along with each goroutine:
|
||||
|
||||
@ -279,6 +279,14 @@ To only display user (or runtime) goroutines, use:
|
||||
goroutines -with user
|
||||
goroutines -without user
|
||||
|
||||
CHANNELS
|
||||
|
||||
To only show goroutines waiting to send to or receive from a specific channel use:
|
||||
|
||||
goroutines -chan expr
|
||||
|
||||
Note that 'expr' must not contain spaces.
|
||||
|
||||
GROUPING
|
||||
|
||||
goroutines -group (userloc|curloc|goloc|startloc|running|user)
|
||||
@ -318,7 +326,7 @@ Called with more arguments it will execute a command on the specified goroutine.
|
||||
breakpoints [-a]
|
||||
|
||||
Specifying -a prints all physical breakpoint, including internal breakpoints.`},
|
||||
{aliases: []string{"print", "p"}, group: dataCmds, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: printVar, helpMsg: `Evaluate an expression.
|
||||
{aliases: []string{"print", "p"}, group: dataCmds, allowedPrefixes: onPrefix | deferredPrefix, cmdFn: c.printVar, helpMsg: `Evaluate an expression.
|
||||
|
||||
[goroutine <n>] [frame <m>] print [%format] <expression>
|
||||
|
||||
@ -902,7 +910,7 @@ func (c *Commands) goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
fmt.Fprintf(t.stdout, "interrupted\n")
|
||||
return nil
|
||||
}
|
||||
gs, groups, start, tooManyGroups, err = t.client.ListGoroutinesWithFilter(start, batchSize, filters, &group)
|
||||
gs, groups, start, tooManyGroups, err = t.client.ListGoroutinesWithFilter(start, batchSize, filters, &group, &api.EvalScope{GoroutineID: -1, Frame: c.frame})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -2090,7 +2098,9 @@ func parseFormatArg(args string) (fmtstr, argsOut string) {
|
||||
return v[0], v[1]
|
||||
}
|
||||
|
||||
func printVar(t *Term, ctx callContext, args string) error {
|
||||
const maxPrintVarChanGoroutines = 100
|
||||
|
||||
func (c *Commands) printVar(t *Term, ctx callContext, args string) error {
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("not enough arguments")
|
||||
}
|
||||
@ -2105,6 +2115,22 @@ func printVar(t *Term, ctx callContext, args string) error {
|
||||
}
|
||||
|
||||
fmt.Fprintln(t.stdout, val.MultilineString("", fmtstr))
|
||||
|
||||
if val.Kind == reflect.Chan {
|
||||
fmt.Fprintln(t.stdout)
|
||||
gs, _, _, _, err := t.client.ListGoroutinesWithFilter(0, maxPrintVarChanGoroutines, []api.ListGoroutinesFilter{{Kind: api.GoroutineWaitingOnChannel, Arg: fmt.Sprintf("*(*%q)(%#x)", val.Type, val.Addr)}}, nil, &ctx.Scope)
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.stdout, "Error reading channel wait queue: %v", err)
|
||||
} else {
|
||||
fmt.Fprintln(t.stdout, "Goroutines waiting on this channel:")
|
||||
state, err := t.client.GetState()
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.stdout, "Error printing channel wait queue: %v", err)
|
||||
}
|
||||
var done bool
|
||||
c.printGoroutines(t, ctx, "", gs, api.FglUserCurrent, 0, 0, "", &done, state)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1143,6 +1143,15 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
if len(args) > 4 && args[4] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[4], &rpcArgs.EvalScope, "EvalScope")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
} else {
|
||||
scope := env.ctx.Scope()
|
||||
rpcArgs.EvalScope = &scope
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
@ -1154,6 +1163,8 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Filters, "Filters")
|
||||
case "GoroutineGroupingOptions":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.GoroutineGroupingOptions, "GoroutineGroupingOptions")
|
||||
case "EvalScope":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.EvalScope, "EvalScope")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
@ -1167,7 +1178,7 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
doc["goroutines"] = "builtin goroutines(Start, Count, Filters, GoroutineGroupingOptions)\n\ngoroutines lists all goroutines.\nIf Count is specified ListGoroutines will return at the first Count\ngoroutines and an index in Nextg, that can be passed as the Start\nparameter, to get more goroutines from ListGoroutines.\nPassing a value of Start that wasn't returned by ListGoroutines will skip\nan undefined number of goroutines.\n\nIf arg.Filters are specified the list of returned goroutines is filtered\napplying the specified filters.\nFor example:\n\n\tListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: \"afile.go\" }\n\nwill only return goroutines whose UserLoc contains \"afile.go\" as a substring.\nMore specifically a goroutine matches a location filter if the specified\nlocation, formatted like this:\n\n\tfilename:lineno in function\n\ncontains Arg[0] as a substring.\n\nFilters can also be applied to goroutine labels:\n\n\tListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: \"key=value\" }\n\nthis filter will only return goroutines that have a key=value label.\n\nIf arg.GroupBy is not GoroutineFieldNone then the goroutines will\nbe grouped with the specified criterion.\nIf the value of arg.GroupBy is GoroutineLabel goroutines will\nbe grouped by the value of the label with key GroupByKey.\nFor each group a maximum of MaxGroupMembers example goroutines are\nreturned, as well as the total number of goroutines in the group."
|
||||
doc["goroutines"] = "builtin goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope)\n\ngoroutines lists all goroutines.\nIf Count is specified ListGoroutines will return at the first Count\ngoroutines and an index in Nextg, that can be passed as the Start\nparameter, to get more goroutines from ListGoroutines.\nPassing a value of Start that wasn't returned by ListGoroutines will skip\nan undefined number of goroutines.\n\nIf arg.Filters are specified the list of returned goroutines is filtered\napplying the specified filters.\nFor example:\n\n\tListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: \"afile.go\" }\n\nwill only return goroutines whose UserLoc contains \"afile.go\" as a substring.\nMore specifically a goroutine matches a location filter if the specified\nlocation, formatted like this:\n\n\tfilename:lineno in function\n\ncontains Arg[0] as a substring.\n\nFilters can also be applied to goroutine labels:\n\n\tListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: \"key=value\" }\n\nthis filter will only return goroutines that have a key=value label.\n\nIf arg.GroupBy is not GoroutineFieldNone then the goroutines will\nbe grouped with the specified criterion.\nIf the value of arg.GroupBy is GoroutineLabel goroutines will\nbe grouped by the value of the label with key GroupByKey.\nFor each group a maximum of MaxGroupMembers example goroutines are\nreturned, as well as the total number of goroutines in the group."
|
||||
r["local_vars"] = starlark.NewBuiltin("local_vars", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -99,6 +100,13 @@ func ParseGoroutineArgs(argstr string) ([]ListGoroutinesFilter, GoroutineGroupin
|
||||
}
|
||||
batchSize = 0 // grouping only works well if run on all goroutines
|
||||
|
||||
case "-chan":
|
||||
i++
|
||||
if i >= len(args) {
|
||||
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", errors.New("not enough arguments after -chan")
|
||||
}
|
||||
filters = append(filters, ListGoroutinesFilter{Kind: GoroutineWaitingOnChannel, Arg: args[i]})
|
||||
|
||||
case "-exec":
|
||||
flags |= PrintGoroutinesExec
|
||||
cmd = strings.Join(args[i+1:], " ")
|
||||
|
@ -635,14 +635,15 @@ type ListGoroutinesFilter struct {
|
||||
type GoroutineField uint8
|
||||
|
||||
const (
|
||||
GoroutineFieldNone GoroutineField = iota
|
||||
GoroutineCurrentLoc // the goroutine's CurrentLoc
|
||||
GoroutineUserLoc // the goroutine's UserLoc
|
||||
GoroutineGoLoc // the goroutine's GoStatementLoc
|
||||
GoroutineStartLoc // the goroutine's StartLoc
|
||||
GoroutineLabel // the goroutine's label
|
||||
GoroutineRunning // the goroutine is running
|
||||
GoroutineUser // the goroutine is a user goroutine
|
||||
GoroutineFieldNone GoroutineField = iota
|
||||
GoroutineCurrentLoc // the goroutine's CurrentLoc
|
||||
GoroutineUserLoc // the goroutine's UserLoc
|
||||
GoroutineGoLoc // the goroutine's GoStatementLoc
|
||||
GoroutineStartLoc // the goroutine's StartLoc
|
||||
GoroutineLabel // the goroutine's label
|
||||
GoroutineRunning // the goroutine is running
|
||||
GoroutineUser // the goroutine is a user goroutine
|
||||
GoroutineWaitingOnChannel // the goroutine is waiting on the channel specified by the argument
|
||||
)
|
||||
|
||||
// GoroutineGroup represents a group of goroutines in the return value of
|
||||
|
@ -120,7 +120,7 @@ type Client interface {
|
||||
// ListGoroutines lists all goroutines.
|
||||
ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
|
||||
// ListGoroutinesWithFilter lists goroutines matching the filters
|
||||
ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error)
|
||||
ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error)
|
||||
|
||||
// Stacktrace returns stacktrace
|
||||
Stacktrace(goroutineID int64, depth int, opts api.StacktraceOptions, cfg *api.LoadConfig) ([]api.Stackframe, error)
|
||||
|
@ -1688,6 +1688,8 @@ func matchGoroutineFilter(tgt *proc.Target, g *proc.G, filter *api.ListGoroutine
|
||||
val = g.Thread != nil
|
||||
case api.GoroutineUser:
|
||||
val = !g.System(tgt)
|
||||
case api.GoroutineWaitingOnChannel:
|
||||
val = true // handled elsewhere
|
||||
}
|
||||
if filter.Negated {
|
||||
val = !val
|
||||
@ -2325,6 +2327,31 @@ func (d *Debugger) DebugInfoDirectories() []string {
|
||||
return d.target.Selected.BinInfo().DebugInfoDirectories
|
||||
}
|
||||
|
||||
// ChanGoroutines returns the list of goroutines waiting on the channel specified by expr.
|
||||
func (d *Debugger) ChanGoroutines(goid int64, frame, deferredCall int, expr string, start, count int) ([]*proc.G, error) {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
s, err := proc.ConvertEvalScope(d.target.Selected, goid, frame, deferredCall)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goids, err := s.ChanGoroutines(expr, start, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gs := make([]*proc.G, len(goids))
|
||||
for i := range goids {
|
||||
g, err := proc.FindGoroutine(d.target.Selected, goids[i])
|
||||
if g == nil {
|
||||
g = &proc.G{Unreadable: err}
|
||||
}
|
||||
gs[i] = g
|
||||
}
|
||||
return gs, nil
|
||||
}
|
||||
|
||||
func go11DecodeErrorCheck(err error) error {
|
||||
if _, isdecodeerr := err.(dwarf.DecodeError); !isdecodeerr {
|
||||
return err
|
||||
|
@ -383,16 +383,16 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([
|
||||
|
||||
func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) {
|
||||
var out ListGoroutinesOut
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}}, &out)
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}, nil}, &out)
|
||||
return out.Goroutines, out.Nextg, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
|
||||
func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
|
||||
if group == nil {
|
||||
group = &api.GoroutineGroupingOptions{}
|
||||
}
|
||||
var out ListGoroutinesOut
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group}, &out)
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group, scope}, &out)
|
||||
return out.Goroutines, out.Groups, out.Nextg, out.TooManyGroups, err
|
||||
}
|
||||
|
||||
|
@ -534,7 +534,8 @@ func (s *RPCServer) Eval(arg EvalIn, out *EvalOut) error {
|
||||
if cfg == nil {
|
||||
cfg = &api.LoadConfig{FollowPointers: true, MaxVariableRecurse: 1, MaxStringLen: 64, MaxArrayValues: 64, MaxStructFields: -1}
|
||||
}
|
||||
v, err := s.debugger.EvalVariableInScope(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, *api.LoadConfigToProc(cfg))
|
||||
pcfg := *api.LoadConfigToProc(cfg)
|
||||
v, err := s.debugger.EvalVariableInScope(arg.Scope.GoroutineID, arg.Scope.Frame, arg.Scope.DeferredCall, arg.Expr, pcfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -617,6 +618,8 @@ type ListGoroutinesIn struct {
|
||||
|
||||
Filters []api.ListGoroutinesFilter
|
||||
api.GoroutineGroupingOptions
|
||||
|
||||
EvalScope *api.EvalScope
|
||||
}
|
||||
|
||||
type ListGoroutinesOut struct {
|
||||
@ -663,7 +666,37 @@ func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut)
|
||||
//TODO(aarzilli): if arg contains a running goroutines filter (not negated)
|
||||
// and start == 0 and count == 0 then we can optimize this by just looking
|
||||
// at threads directly.
|
||||
gs, nextg, err := s.debugger.Goroutines(arg.Start, arg.Count)
|
||||
|
||||
var gs []*proc.G
|
||||
var nextg int
|
||||
var err error
|
||||
var gsLoaded bool
|
||||
|
||||
for _, filter := range arg.Filters {
|
||||
if filter.Kind == api.GoroutineWaitingOnChannel {
|
||||
if filter.Negated {
|
||||
return errors.New("channel filter can not be negated")
|
||||
}
|
||||
if arg.Count == 0 {
|
||||
return errors.New("count == 0 not allowed with a channel filter")
|
||||
}
|
||||
if arg.EvalScope == nil {
|
||||
return errors.New("channel filter without eval scope")
|
||||
}
|
||||
gs, err = s.debugger.ChanGoroutines(arg.EvalScope.GoroutineID, arg.EvalScope.Frame, arg.EvalScope.DeferredCall, filter.Arg, arg.Start, arg.Count)
|
||||
if len(gs) == arg.Count {
|
||||
nextg = arg.Start + len(gs)
|
||||
} else {
|
||||
nextg = -1
|
||||
}
|
||||
gsLoaded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !gsLoaded {
|
||||
gs, nextg, err = s.debugger.Goroutines(arg.Start, arg.Count)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -2577,7 +2577,7 @@ func TestGoroutinesGrouping(t *testing.T) {
|
||||
withTestClient2("goroutinegroup", t, func(c service.Client) {
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue")
|
||||
_, ggrp, _, _, err := c.ListGoroutinesWithFilter(0, 0, nil, &api.GoroutineGroupingOptions{GroupBy: api.GoroutineLabel, GroupByKey: "name", MaxGroupMembers: 5, MaxGroups: 10})
|
||||
_, ggrp, _, _, err := c.ListGoroutinesWithFilter(0, 0, nil, &api.GoroutineGroupingOptions{GroupBy: api.GoroutineLabel, GroupByKey: "name", MaxGroupMembers: 5, MaxGroups: 10}, nil)
|
||||
assertNoError(err, t, "ListGoroutinesWithFilter (group by label)")
|
||||
t.Logf("%#v\n", ggrp)
|
||||
if len(ggrp) < 5 {
|
||||
@ -2590,7 +2590,7 @@ func TestGoroutinesGrouping(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
gs, _, _, _, err := c.ListGoroutinesWithFilter(0, 0, []api.ListGoroutinesFilter{{Kind: api.GoroutineLabel, Arg: "name="}}, nil)
|
||||
gs, _, _, _, err := c.ListGoroutinesWithFilter(0, 0, []api.ListGoroutinesFilter{{Kind: api.GoroutineLabel, Arg: "name="}}, nil, nil)
|
||||
assertNoError(err, t, "ListGoroutinesWithFilter (filter unnamed)")
|
||||
if len(gs) != unnamedCount {
|
||||
t.Errorf("wrong number of goroutines returned by filter: %d (expected %d)\n", len(gs), unnamedCount)
|
||||
@ -3031,3 +3031,70 @@ func TestClientServer_breakpointOnFuncWithABIWrapper(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var waitReasonStrings = [...]string{
|
||||
"",
|
||||
"GC assist marking",
|
||||
"IO wait",
|
||||
"chan receive (nil chan)",
|
||||
"chan send (nil chan)",
|
||||
"dumping heap",
|
||||
"garbage collection",
|
||||
"garbage collection scan",
|
||||
"panicwait",
|
||||
"select",
|
||||
"select (no cases)",
|
||||
"GC assist wait",
|
||||
"GC sweep wait",
|
||||
"GC scavenge wait",
|
||||
"chan receive",
|
||||
"chan send",
|
||||
"finalizer wait",
|
||||
"force gc (idle)",
|
||||
"semacquire",
|
||||
"sleep",
|
||||
"sync.Cond.Wait",
|
||||
"timer goroutine (idle)",
|
||||
"trace reader (blocked)",
|
||||
"wait for GC cycle",
|
||||
"GC worker (idle)",
|
||||
"preempted",
|
||||
"debug call",
|
||||
}
|
||||
|
||||
func TestClientServer_chanGoroutines(t *testing.T) {
|
||||
protest.AllowRecording(t)
|
||||
withTestClient2("changoroutines", t, func(c service.Client) {
|
||||
state := <-c.Continue()
|
||||
assertNoError(state.Err, t, "Continue()")
|
||||
|
||||
countRecvSend := func(gs []*api.Goroutine) (recvq, sendq int) {
|
||||
for _, g := range gs {
|
||||
t.Logf("\tID: %d WaitReason: %s\n", g.ID, waitReasonStrings[g.WaitReason])
|
||||
switch waitReasonStrings[g.WaitReason] {
|
||||
case "chan send":
|
||||
sendq++
|
||||
case "chan receive":
|
||||
recvq++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
gs, _, _, _, err := c.ListGoroutinesWithFilter(0, 100, []api.ListGoroutinesFilter{{Kind: api.GoroutineWaitingOnChannel, Arg: "blockingchan1"}}, nil, &api.EvalScope{GoroutineID: -1})
|
||||
assertNoError(err, t, "ListGoroutinesWithFilter(blockingchan1)")
|
||||
t.Logf("blockingchan1 gs:")
|
||||
recvq, sendq := countRecvSend(gs)
|
||||
if len(gs) != 2 || recvq != 0 || sendq != 2 {
|
||||
t.Error("wrong number of goroutines for blockingchan1")
|
||||
}
|
||||
|
||||
gs, _, _, _, err = c.ListGoroutinesWithFilter(0, 100, []api.ListGoroutinesFilter{{Kind: api.GoroutineWaitingOnChannel, Arg: "blockingchan2"}}, nil, &api.EvalScope{GoroutineID: -1})
|
||||
assertNoError(err, t, "ListGoroutinesWithFilter(blockingchan2)")
|
||||
t.Logf("blockingchan2 gs:")
|
||||
recvq, sendq = countRecvSend(gs)
|
||||
if len(gs) != 1 || recvq != 1 || sendq != 0 {
|
||||
t.Error("wrong number of goroutines for blockingchan2")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user