]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: deflake TestTracebackAncestors
authorAustin Clements <austin@google.com>
Mon, 5 Nov 2018 17:36:42 +0000 (12:36 -0500)
committerAustin Clements <austin@google.com>
Mon, 5 Nov 2018 22:37:13 +0000 (22:37 +0000)
TestTracebackAncestors has a ~0.1% chance of failing with more
goroutines in the traceback than expected. This happens because
there's a window between each goroutine starting its child and that
goroutine actually exiting. The test captures its own stack trace
after everything is "done", but if this happens during that window, it
will include the goroutine that's in the process of being torn down.
Here's an example of such a failure:
https://build.golang.org/log/fad10d0625295eb79fa879f53b8b32b9d0596af8

This CL fixes this by recording the goroutines that are expected to
exit and removing them from the stack trace. With this fix, this test
passed 15,000 times with no failures.

Change-Id: I71e7c6282987a15e8b74188b9c585aa2ca97cbcd
Reviewed-on: https://go-review.googlesource.com/c/147517
Run-TryBot: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/runtime/testdata/testprog/traceback_ancestors.go

index fe57c1c157e68350014505aa5e75e5e4e78139d3..0ee402c4bdc60837db2f12d20c53d001f9a51f5c 100644 (file)
@@ -5,8 +5,10 @@
 package main
 
 import (
+       "bytes"
        "fmt"
        "runtime"
+       "strings"
 )
 
 func init() {
@@ -18,25 +20,50 @@ const numFrames = 2
 
 func TracebackAncestors() {
        w := make(chan struct{})
-       recurseThenCallGo(w, numGoroutines, numFrames)
+       recurseThenCallGo(w, numGoroutines, numFrames, true)
        <-w
        printStack()
        close(w)
 }
 
+var ignoreGoroutines = make(map[string]bool)
+
 func printStack() {
        buf := make([]byte, 1024)
        for {
                n := runtime.Stack(buf, true)
                if n < len(buf) {
-                       fmt.Print(string(buf[:n]))
+                       tb := string(buf[:n])
+
+                       // Delete any ignored goroutines, if present.
+                       pos := 0
+                       for pos < len(tb) {
+                               next := pos + strings.Index(tb[pos:], "\n\n")
+                               if next < pos {
+                                       next = len(tb)
+                               } else {
+                                       next += len("\n\n")
+                               }
+
+                               if strings.HasPrefix(tb[pos:], "goroutine ") {
+                                       id := tb[pos+len("goroutine "):]
+                                       id = id[:strings.IndexByte(id, ' ')]
+                                       if ignoreGoroutines[id] {
+                                               tb = tb[:pos] + tb[next:]
+                                               next = pos
+                                       }
+                               }
+                               pos = next
+                       }
+
+                       fmt.Print(tb)
                        return
                }
                buf = make([]byte, 2*len(buf))
        }
 }
 
-func recurseThenCallGo(w chan struct{}, frames int, goroutines int) {
+func recurseThenCallGo(w chan struct{}, frames int, goroutines int, main bool) {
        if frames == 0 {
                // Signal to TracebackAncestors that we are done recursing and starting goroutines.
                w <- struct{}{}
@@ -44,10 +71,29 @@ func recurseThenCallGo(w chan struct{}, frames int, goroutines int) {
                return
        }
        if goroutines == 0 {
+               // Record which goroutine this is so we can ignore it
+               // in the traceback if it hasn't finished exiting by
+               // the time we printStack.
+               if !main {
+                       ignoreGoroutines[goroutineID()] = true
+               }
+
                // Start the next goroutine now that there are no more recursions left
                // for this current goroutine.
-               go recurseThenCallGo(w, frames-1, numFrames)
+               go recurseThenCallGo(w, frames-1, numFrames, false)
                return
        }
-       recurseThenCallGo(w, frames, goroutines-1)
+       recurseThenCallGo(w, frames, goroutines-1, main)
+}
+
+func goroutineID() string {
+       buf := make([]byte, 128)
+       runtime.Stack(buf, false)
+       const prefix = "goroutine "
+       if !bytes.HasPrefix(buf, []byte(prefix)) {
+               panic(fmt.Sprintf("expected %q at beginning of traceback:\n%s", prefix, buf))
+       }
+       buf = buf[len(prefix):]
+       n := bytes.IndexByte(buf, ' ')
+       return string(buf[:n])
 }