diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 0ecbd0ab5ec8de09569516c0f5807d3712e04f3b..117669342abd8586260168cdb065e09b0fa292c5 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -190,7 +190,7 @@ func TestCore(t *testing.T) { } p := withCoreFile(t, "panic", "") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) } @@ -260,7 +260,7 @@ func TestCoreFpRegisters(t *testing.T) { p := withCoreFile(t, "fputest/", "panic") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) } @@ -337,7 +337,7 @@ func TestCoreWithEmptyString(t *testing.T) { } p := withCoreFile(t, "coreemptystring", "") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") var mainFrame *proc.Stackframe diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 0fe9db56244ea726510f1137001f0ee458be8a6f..a845187d8d857613a2f9c53d292c4768dc9db3a9 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -488,14 +488,22 @@ func StepOut(dbp Process) error { return Continue(dbp) } -// GoroutinesInfo returns an array of G structures representing the information -// Delve cares about from the internal runtime G structure. -func GoroutinesInfo(dbp Process) ([]*G, error) { +// GoroutinesInfo searches for goroutines starting at index 'start', and +// returns an array of up to 'count' (or all found elements, if 'count' is 0) +// G structures representing the information Delve care about from the internal +// runtime G structure. +// GoroutinesInfo also returns the next index to be used as 'start' argument +// while scanning for all available goroutines, or -1 if there was an error +// or if the index already reached the last possible value. +func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) { if _, err := dbp.Valid(); err != nil { - return nil, err + return nil, -1, err } if dbp.Common().allGCache != nil { - return dbp.Common().allGCache, nil + // We can't use the cached array to fulfill a subrange request + if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) { + return dbp.Common().allGCache, -1, nil + } } var ( @@ -517,12 +525,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase) if err != nil { - return nil, err + return nil, -1, err } allglenBytes := make([]byte, 8) _, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr)) if err != nil { - return nil, err + return nil, -1, err } allglen := binary.LittleEndian.Uint64(allglenBytes) @@ -532,17 +540,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { // try old name (pre Go 1.6) allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase) if err != nil { - return nil, err + return nil, -1, err } } faddr := make([]byte, dbp.BinInfo().Arch.PtrSize()) _, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr)) if err != nil { - return nil, err + return nil, -1, err } allgptr := binary.LittleEndian.Uint64(faddr) - for i := uint64(0); i < allglen; i++ { + for i := uint64(start); i < allglen; i++ { + if count != 0 && len(allg) >= count { + return allg, int(i), nil + } gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true) if err != nil { allg = append(allg, &G{Unreadable: err}) @@ -556,7 +567,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { if thg, allocated := threadg[g.ID]; allocated { loc, err := thg.Thread.Location() if err != nil { - return nil, err + return nil, -1, err } g.Thread = thg.Thread // Prefer actual thread location information. @@ -569,7 +580,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { } dbp.Common().allGCache = allg - return allg, nil + return allg, -1, nil } // FindGoroutine returns a G struct representing the goroutine @@ -579,7 +590,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) { return dbp.SelectedGoroutine(), nil } - gs, err := GoroutinesInfo(dbp) + gs, _, err := GoroutinesInfo(dbp, 0, 0) if err != nil { return nil, err } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 3177bebfd4005c2f79083ccf3be52e84249f10d4..4a5ce8d9d0a2efb2a99b93419fc3badb84b9724a 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) { assertNoError(proc.Continue(p), t, "Continue()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") agoroutineCount := 0 @@ -1239,7 +1239,7 @@ func TestFrameEvaluation(t *testing.T) { t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine()) // Testing evaluation on goroutines - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") found := make([]bool, 10) for _, g := range gs { @@ -1523,7 +1523,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) { assertNoError(proc.Continue(p), b, "Continue()") for i := 0; i < b.N; i++ { p.Common().ClearAllGCache() - _, err := proc.GoroutinesInfo(p) + _, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, b, "GoroutinesInfo") } }) @@ -1953,7 +1953,7 @@ func TestNextParked(t *testing.T) { } assertNoError(err, t, "Continue()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo()") // Search for a parked goroutine that we know for sure will have to be @@ -2005,7 +2005,7 @@ func TestStepParked(t *testing.T) { } assertNoError(err, t, "Continue()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo()") for _, g := range gs { @@ -2729,7 +2729,7 @@ func TestStacktraceWithBarriers(t *testing.T) { return } assertNoError(err, t, "Continue()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo()") for _, th := range p.ThreadList() { if bp := th.Breakpoint(); bp.Breakpoint == nil { @@ -2760,7 +2760,7 @@ func TestStacktraceWithBarriers(t *testing.T) { assertNoError(proc.StepOut(p), t, "StepOut()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo()") for _, goid := range stackBarrierGoids { diff --git a/pkg/proc/variable_test.go b/pkg/proc/variable_test.go index 631f5b75df07c58ed3ea825d720442f40d97a556..3718afe1471f5ff61119a2fae1437ce0d0f30d53 100644 --- a/pkg/proc/variable_test.go +++ b/pkg/proc/variable_test.go @@ -15,7 +15,7 @@ func TestGoroutineCreationLocation(t *testing.T) { assertNoError(err, t, "BreakByLocation()") assertNoError(proc.Continue(p), t, "Continue()") - gs, err := proc.GoroutinesInfo(p) + gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") for _, g := range gs { diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 3c702c4c5a4c6fe0c0102e2e7af8de70171d135d..be1ce43daf25acf90a14653a1239b8741a305ba8 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -570,6 +570,27 @@ func (a byGoroutineID) Len() int { return len(a) } func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID } +// The number of goroutines we're going to request on each RPC call +const goroutineBatchSize = 10000 + +func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, bPrintStack bool, state *api.DebuggerState) error { + for _, g := range gs { + prefix := " " + if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { + prefix = "* " + } + fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) + if bPrintStack { + stack, err := t.client.Stacktrace(g.ID, 10, false, nil) + if err != nil { + return err + } + printStack(stack, "\t", false) + } + } + return nil +} + func goroutines(t *Term, ctx callContext, argstr string) error { args := strings.Split(argstr, " ") var fgl = fglUserCurrent @@ -604,26 +625,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error { if err != nil { return err } - gs, err := t.client.ListGoroutines() - if err != nil { - return err - } - sort.Sort(byGoroutineID(gs)) - fmt.Printf("[%d goroutines]\n", len(gs)) - for _, g := range gs { - prefix := " " - if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { - prefix = "* " + var ( + start = 0 + gslen = 0 + gs []*api.Goroutine + ) + for start >= 0 { + gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize) + if err != nil { + return err } - fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) - if bPrintStack { - stack, err := t.client.Stacktrace(g.ID, 10, false, nil) - if err != nil { - return err - } - printStack(stack, "\t", false) + sort.Sort(byGoroutineID(gs)) + err = printGoroutines(t, gs, fgl, bPrintStack, state) + if err != nil { + return err } + gslen += len(gs) } + fmt.Printf("[%d goroutines]\n", gslen) return nil } diff --git a/service/client.go b/service/client.go index f4211c3aec167e4ad07ee20c06c176ca3bda0423..d0f9a04af048c306645cc91fc32e1012656596e5 100644 --- a/service/client.go +++ b/service/client.go @@ -95,7 +95,7 @@ type Client interface { ListRegisters(threadID int, includeFp bool) (api.Registers, error) // ListGoroutines lists all goroutines. - ListGoroutines() ([]*api.Goroutine, error) + ListGoroutines(start, count int) ([]*api.Goroutine, int, error) // Returns stacktrace Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 0fffa13e253dcbd2c55a401a7ba552c754fec2f1..fefc4a2f208389570290ae5315de1fc9d9e7f5ce 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -907,19 +907,19 @@ func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string) } // Goroutines will return a list of goroutines in the target process. -func (d *Debugger) Goroutines() ([]*api.Goroutine, error) { +func (d *Debugger) Goroutines(start, count int) ([]*api.Goroutine, int, error) { d.processMutex.Lock() defer d.processMutex.Unlock() goroutines := []*api.Goroutine{} - gs, err := proc.GoroutinesInfo(d.target) + gs, nextg, err := proc.GoroutinesInfo(d.target, start, count) if err != nil { - return nil, err + return nil, 0, err } for _, g := range gs { goroutines = append(goroutines, api.ConvertGoroutine(g)) } - return goroutines, err + return goroutines, nextg, err } // Stacktrace returns a list of Stackframes for the given goroutine. The diff --git a/service/rpc1/server.go b/service/rpc1/server.go index eae6d87a904c60fdeb7c3ca622b6803a71b1545f..e71e1348a8c907aedcdd55a995c52134cc629aa7 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -283,7 +283,7 @@ func (s *RPCServer) ListTypes(filter string, types *[]string) error { } func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) error { - gs, err := s.debugger.Goroutines() + gs, _, err := s.debugger.Goroutines(0, 0) if err != nil { return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 5ada18bcef5a01e8bbdea16bf944d41db234ba69..fc4501cbbbb9839b28b4bd600446e549a80e7027 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -298,10 +298,10 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([ return out.Args, err } -func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) { +func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) { var out ListGoroutinesOut - err := c.call("ListGoroutines", ListGoroutinesIn{}, &out) - return out.Goroutines, err + err := c.call("ListGoroutines", ListGoroutinesIn{start, count}, &out) + return out.Goroutines, out.Nextg, err } func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) { diff --git a/service/rpc2/server.go b/service/rpc2/server.go index e9193cf538b720b51a28ba1039b1b8bbb8a8a6a1..62edb30eace5d76b9bbdcf981d90b7d65abfab70 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -508,19 +508,23 @@ func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error { } type ListGoroutinesIn struct { + Start int + Count int } type ListGoroutinesOut struct { Goroutines []*api.Goroutine + Nextg int } // ListGoroutines lists all goroutines. func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error { - gs, err := s.debugger.Goroutines() + gs, nextg, err := s.debugger.Goroutines(arg.Start, arg.Count) if err != nil { return err } out.Goroutines = gs + out.Nextg = nextg return nil } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 654c524ee7510dbe78310bdfc2db6fdc78da52a7..50230ae72b66d91216ca9d7fdbd9a89e3398f0e3 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -801,7 +801,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { t.Fatalf("Continue(): %v\n", state.Err) } - gs, err := c.ListGoroutines() + gs, _, err := c.ListGoroutines(0, 0) assertNoError(err, t, "GoroutinesInfo()") found := make([]bool, 10) for _, g := range gs { @@ -916,7 +916,7 @@ func TestIssue355(t *testing.T) { assertError(err, t, "ListFunctionArgs()") _, err = c.ListRegisters(0, false) assertError(err, t, "ListRegisters()") - _, err = c.ListGoroutines() + _, _, err = c.ListGoroutines(0, 0) assertError(err, t, "ListGoroutines()") _, err = c.Stacktrace(gid, 10, false, &normalLoadConfig) assertError(err, t, "Stacktrace()")