| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package goroutines |
| |
| import ( |
| "bufio" |
| "bytes" |
| "fmt" |
| "io" |
| "regexp" |
| "runtime" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| var goroutineHeaderRE = regexp.MustCompile(`^goroutine (\d+) \[([^\]]+)\]:$`) |
| var stackFileRE = regexp.MustCompile(`^\s+([^:]+):(\d+)(?: \+0x([0-9A-Fa-f]+))?$`) |
| |
| type Goroutine struct { |
| ID int |
| State string |
| Stack []*Frame |
| Creator *Frame |
| } |
| |
| // Get gets a set of currently running goroutines and parses them into a |
| // structured representation. |
| func Get() ([]*Goroutine, error) { |
| bufsize, read := 1<<20, 0 |
| buf := make([]byte, bufsize) |
| for { |
| read = runtime.Stack(buf, true) |
| if read < bufsize { |
| buf = buf[:read] |
| break |
| } |
| bufsize *= 2 |
| buf = make([]byte, bufsize) |
| } |
| return Parse(buf) |
| } |
| |
| // Parse parses a stack trace into a structure representation. |
| func Parse(buf []byte) ([]*Goroutine, error) { |
| scanner := bufio.NewScanner(bytes.NewReader(buf)) |
| var out []*Goroutine |
| for scanner.Scan() { |
| if len(scanner.Bytes()) == 0 { |
| continue |
| } |
| g, err := parseGoroutine(scanner) |
| if err != nil { |
| return out, fmt.Errorf("Error %v parsing trace:\n%s", err, string(buf)) |
| } |
| out = append(out, g) |
| } |
| return out, scanner.Err() |
| } |
| |
| func parseGoroutine(scanner *bufio.Scanner) (*Goroutine, error) { |
| g := &Goroutine{} |
| matches := goroutineHeaderRE.FindSubmatch(scanner.Bytes()) |
| if len(matches) != 3 { |
| return nil, fmt.Errorf("Could not parse goroutine header from: %s", scanner.Text()) |
| } |
| id, err := strconv.ParseInt(string(matches[1]), 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| g.ID = int(id) |
| g.State = string(matches[2]) |
| |
| for scanner.Scan() { |
| if len(scanner.Bytes()) == 0 { |
| break |
| } |
| frame, err := parseFrame(scanner) |
| if err != nil { |
| return nil, err |
| } |
| if strings.HasPrefix(frame.Call, "created by ") { |
| frame.Call = frame.Call[len("created by "):] |
| g.Creator = frame |
| break |
| } |
| g.Stack = append(g.Stack, frame) |
| } |
| return g, nil |
| } |
| |
| func (g *Goroutine) writeTo(w io.Writer) { |
| fmt.Fprintf(w, "goroutine %d [%s]:\n", g.ID, g.State) |
| for _, f := range g.Stack { |
| f.writeTo(w) |
| } |
| if g.Creator != nil { |
| fmt.Fprint(w, "created by ") |
| g.Creator.writeTo(w) |
| } |
| } |
| |
| // Frame represents a single stack frame. |
| type Frame struct { |
| Call string |
| File string |
| Line int |
| Offset int |
| } |
| |
| func parseFrame(scanner *bufio.Scanner) (*Frame, error) { |
| f := &Frame{Call: scanner.Text()} |
| if !scanner.Scan() { |
| return nil, fmt.Errorf("Frame lacked a second line %s", f.Call) |
| } |
| matches := stackFileRE.FindSubmatch(scanner.Bytes()) |
| if len(matches) < 4 { |
| return nil, fmt.Errorf("Could not parse file reference from %s", scanner.Text()) |
| } |
| f.File = string(matches[1]) |
| line, err := strconv.ParseInt(string(matches[2]), 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| f.Line = int(line) |
| if len(matches[3]) > 0 { |
| offset, err := strconv.ParseInt(string(matches[3]), 16, 64) |
| if err != nil { |
| return nil, err |
| } |
| f.Offset = int(offset) |
| } |
| return f, nil |
| } |
| |
| func (f *Frame) writeTo(w io.Writer) { |
| fmt.Fprintln(w, f.Call) |
| if f.Offset != 0 { |
| fmt.Fprintf(w, "\t%s:%d +0x%x\n", f.File, f.Line, f.Offset) |
| } else { |
| fmt.Fprintf(w, "\t%s:%d\n", f.File, f.Line) |
| } |
| } |
| |
| // Format formats Goroutines back into the normal string representation. |
| func Format(gs ...*Goroutine) []byte { |
| var buf bytes.Buffer |
| for i, g := range gs { |
| if i != 0 { |
| buf.WriteRune('\n') |
| } |
| g.writeTo(&buf) |
| } |
| return buf.Bytes() |
| } |
| |
| // ErrorReporter is used by NoLeaks to report errors. testing.T implements |
| // this interface and is normally passed. |
| type ErrorReporter interface { |
| Errorf(format string, args ...interface{}) |
| } |
| |
| // NoLeaks helps test that a test isn't leaving extra goroutines after it finishes. |
| // |
| // The normal way to use it is: |
| // func TestFoo(t *testing.T) { |
| // defer goroutines.NoLeaks(t, time.Second)() |
| // |
| // ... Normal test code here ... |
| // |
| // } |
| // |
| // The test will fail if there are goroutines running at the end of the test |
| // that weren't running at the beginning. |
| // Since testing for goroutines being finished can be racy, the detector |
| // can wait the specified duration for the set of goroutines to return to the |
| // initial set. |
| func NoLeaks(t ErrorReporter, wait time.Duration) func() { |
| gs, err := Get() |
| if err != nil { |
| return func() {} // If we can't parse correctly we let the test pass. |
| } |
| bycreator := map[string]int{} |
| for _, g := range gs { |
| key := "" |
| if g.Creator != nil { |
| key = g.Creator.Call |
| } |
| bycreator[key]++ |
| } |
| return func() { |
| var left []*Goroutine |
| backoff := 10 * time.Millisecond |
| start := time.Now() |
| until := start.Add(wait) |
| for { |
| cgs, err := Get() |
| if err != nil { |
| return // If we can't parse correctly we let the test pass. |
| } |
| left = left[:0] |
| cbycreator := map[string]int{} |
| for _, g := range cgs { |
| key := "" |
| if g.Creator != nil { |
| key = g.Creator.Call |
| } |
| cbycreator[key]++ |
| if cbycreator[key] > bycreator[key] { |
| left = append(left, g) |
| } |
| } |
| if len(left) == 0 { |
| return |
| } |
| if time.Now().After(until) { |
| t.Errorf("%d extra Goroutines outstanding:\n %s", len(left), |
| string(Format(left...))) |
| return |
| } |
| time.Sleep(backoff) |
| if backoff = backoff * 2; backoff > time.Second { |
| backoff = time.Second |
| } |
| } |
| } |
| } |