1 package goroutine 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "strconv" 9 "strings" 10 ) 11 12 // Goroutine represents information about a single goroutine, such as its unique 13 // ID, state, backtrace, creator, and more. 14 // 15 // Go's runtime assigns unique IDs to goroutines, also called "goid" in Go 16 // runtime parlance. These IDs start with 1 for the main goroutine and only 17 // increase (unless you manage to create 2**64-2 goroutines during the lifetime 18 // of your tests so that the goids wrap around). Due to runtime-internal 19 // optimizations, not all IDs might be used, so that there might be gaps. But 20 // IDs are never reused, so they're fine as unique goroutine identities. 21 // 22 // The size of a goidsis always 64bits, even on 32bit architectures (if you 23 // like, you might want to double-check for yourself in runtime/runtime2.go and 24 // runtime/proc.go). 25 // 26 // A Goroutine's State field starts with one of the following strings: 27 // - "idle" 28 // - "runnable" 29 // - "running" 30 // - "syscall" 31 // - ("waiting" ... see below) 32 // - ("dead" ... these goroutines should never appear in dumps) 33 // - "copystack" 34 // - "preempted" 35 // - ("???" ... something IS severely broken.) 36 // 37 // In case a goroutine is in waiting state, the State field instead starts with 38 // one of the following strings, never showing a lonely "waiting" string, but 39 // rather one of the reasons for waiting: 40 // - "chan receive" 41 // - "chan send" 42 // - "select" 43 // - "sleep" 44 // - "finalizer wait" 45 // - ...quite some more waiting states. 46 // 47 // The State description may next contain "(scan)", separated by a single blank 48 // from the preceding goroutine state text. 49 // 50 // If a goroutine is blocked from more than at least a minute, then the state 51 // description next contains the string "X minutes", where X is the number of 52 // minutes blocked. This text is separated by a "," and a blank from the 53 // preceding information. 54 // 55 // Finally, OS thread-locked goroutines finally contain "locked to thread" in 56 // their State description, again separated by a "," and a blank from the 57 // preceding information. 58 // 59 // Please note that the State field never contains the opening and closing 60 // square brackets as used in plain stack dumps. 61 type Goroutine struct { 62 ID uint64 // unique goroutine ID ("goid" in Go's runtime parlance) 63 State string // goroutine state, such as "running" 64 TopFunction string // topmost function on goroutine's stack 65 CreatorFunction string // name of function creating this goroutine, if any 66 BornAt string // location where the goroutine was started from, if any; format "file-path:line-number" 67 Backtrace string // goroutine's backtrace (of the stack) 68 } 69 70 // String returns a short textual description of this goroutine, but without the 71 // potentially lengthy and ugly backtrace details. 72 func (g Goroutine) String() string { 73 s := fmt.Sprintf("Goroutine ID: %d, state: %s, top function: %s", 74 g.ID, g.State, g.TopFunction) 75 if g.CreatorFunction == "" { 76 return s 77 } 78 s += fmt.Sprintf(", created by: %s, at: %s", 79 g.CreatorFunction, g.BornAt) 80 return s 81 } 82 83 // GomegaString returns the Gomega struct representation of a Goroutine, but 84 // without a potentially rather lengthy backtrace. This Gomega object value 85 // dumps getting happily truncated as to become more or less useless. 86 func (g Goroutine) GomegaString() string { 87 return fmt.Sprintf( 88 "{ID: %d, State: %q, TopFunction: %q, CreatorFunction: %q, BornAt: %q}", 89 g.ID, g.State, g.TopFunction, g.CreatorFunction, g.BornAt) 90 } 91 92 // Goroutines returns information about all goroutines. 93 func Goroutines() []Goroutine { 94 return goroutines(true) 95 } 96 97 // Current returns information about the current goroutine in which it is 98 // called. Please note that the topmost function name will always be 99 // runtime.Stack. 100 func Current() Goroutine { 101 return goroutines(false)[0] 102 } 103 104 // goroutines is an internal wrapper around dumping either only the stack of the 105 // current goroutine of the caller or dumping the stacks of all goroutines, and 106 // then parsing the dump into separate Goroutine descriptions. 107 func goroutines(all bool) []Goroutine { 108 return parseStack(stacks(all)) 109 } 110 111 // parseStack parses the stack dump of one or multiple goroutines, as returned 112 // by runtime.Stack() and then returns a list of Goroutine descriptions based on 113 // the dump. 114 func parseStack(stacks []byte) []Goroutine { 115 gs := []Goroutine{} 116 r := bufio.NewReader(bytes.NewReader(stacks)) 117 for { 118 // We expect a line describing a new "goroutine", everything else is a 119 // failure. And yes, if we get an EOF already with this line, bail out. 120 line, err := r.ReadString('\n') 121 if err == io.EOF { 122 break 123 } 124 g := new(line) 125 // Read the rest ... that is, the backtrace for this goroutine. 126 g.TopFunction, g.Backtrace = parseGoroutineBacktrace(r) 127 if strings.HasSuffix(g.Backtrace, "\n\n") { 128 g.Backtrace = g.Backtrace[:len(g.Backtrace)-1] 129 } 130 g.CreatorFunction, g.BornAt = findCreator(g.Backtrace) 131 gs = append(gs, g) 132 } 133 return gs 134 } 135 136 // new takes a goroutine line from a stack dump and returns a Goroutine object 137 // based on the information contained in the dump. 138 func new(s string) Goroutine { 139 s = strings.TrimSuffix(s, ":\n") 140 fields := strings.SplitN(s, " ", 3) 141 if len(fields) != 3 { 142 panic(fmt.Sprintf("invalid stack header: %q", s)) 143 } 144 id, err := strconv.ParseUint(fields[1], 10, 64) 145 if err != nil { 146 panic(fmt.Sprintf("invalid stack header ID: %q, header: %q", fields[1], s)) 147 } 148 state := strings.TrimSuffix(strings.TrimPrefix(fields[2], "["), "]") 149 return Goroutine{ID: id, State: state} 150 } 151 152 // Beginning of line indicating the creator of a Goroutine, if any. This 153 // indication is missing for the main goroutine as it appeared in a big bang or 154 // something similar. 155 const backtraceGoroutineCreator = "created by " 156 157 // findCreator solves the great mystery of Gokind, answering the question of who 158 // created this goroutine? Given a backtrace, that is. 159 func findCreator(backtrace string) (creator, location string) { 160 pos := strings.LastIndex(backtrace, backtraceGoroutineCreator) 161 if pos < 0 { 162 return 163 } 164 // Split the "created by ..." line from the following line giving us the 165 // (indented) file name:line number and the hex offset of the call location 166 // within the function. 167 details := strings.SplitN(backtrace[pos+len(backtraceGoroutineCreator):], "\n", 3) 168 if len(details) < 2 { 169 return 170 } 171 // Split off the call location hex offset which is of no use to us, and only 172 // keep the file path and line number information. This will be useful for 173 // diagnosis, when dumping leaked goroutines. 174 offsetpos := strings.LastIndex(details[1], " +0x") 175 if offsetpos < 0 { 176 return 177 } 178 location = strings.TrimSpace(details[1][:offsetpos]) 179 creator = details[0] 180 if offsetpos := strings.LastIndex(creator, " in goroutine "); offsetpos >= 0 { 181 creator = creator[:offsetpos] 182 } 183 return 184 } 185 186 // Beginning of header line introducing a (new) goroutine in a backtrace. 187 const backtraceGoroutineHeader = "goroutine " 188 189 // Length of the header line prefix introducing a (new) goroutine in a 190 // backtrace. 191 const backtraceGoroutineHeaderLen = len(backtraceGoroutineHeader) 192 193 // parseGoroutineBacktrace reads from reader r the backtrace information until 194 // the end or until the next goroutine header is seen. This next goroutine 195 // header is NOT consumed so that callers can still read the next header from 196 // the reader. 197 func parseGoroutineBacktrace(r *bufio.Reader) (topFn string, backtrace string) { 198 bt := bytes.Buffer{} 199 // Read backtrace information belonging to this goroutine until we meet 200 // another goroutine header. 201 for { 202 header, err := r.Peek(backtraceGoroutineHeaderLen) 203 if string(header) == backtraceGoroutineHeader { 204 // next goroutine header is up for read, so we're done with parsing 205 // the backtrace of this goroutine. 206 break 207 } 208 if err != nil && err != io.EOF { 209 // There is some serious problem with the stack dump, so we 210 // decidedly panic now. 211 panic("parsing backtrace failed: " + err.Error()) 212 } 213 line, err := r.ReadString('\n') 214 if err != nil && err != io.EOF { 215 // There is some serious problem with the stack dump, so we 216 // decidedly panic now. 217 panic("parsing backtrace failed: " + err.Error()) 218 } 219 // The first line after a goroutine header lists the "topmost" function. 220 if topFn == "" { 221 line := /*sic!*/ strings.TrimSpace(line) 222 idx := strings.LastIndex(line, "(") 223 if idx <= 0 { 224 panic(fmt.Sprintf("invalid function call stack entry: %q", line)) 225 } 226 topFn = line[:idx] 227 } 228 // Always append the line read to the goroutine's backtrace. 229 bt.WriteString(line) 230 if err == io.EOF { 231 // we're reached the end of the stack dump, so that's it. 232 break 233 } 234 } 235 return topFn, bt.String() 236 } 237