|
10 | 10 | package leaktest |
11 | 11 |
|
12 | 12 | import ( |
| 13 | + "context" |
13 | 14 | "runtime" |
14 | 15 | "sort" |
15 | 16 | "strings" |
@@ -59,34 +60,52 @@ type ErrorReporter interface { |
59 | 60 |
|
60 | 61 | // Check snapshots the currently-running goroutines and returns a |
61 | 62 | // function to be run at the end of tests to see whether any |
62 | | -// goroutines leaked. |
| 63 | +// goroutines leaked, waiting up to 5 seconds in error conditions |
63 | 64 | func Check(t ErrorReporter) func() { |
| 65 | + return CheckTimeout(t, 5*time.Second) |
| 66 | +} |
| 67 | + |
| 68 | +// CheckTimeout is the same as Check, but with a configurable timeout |
| 69 | +func CheckTimeout(t ErrorReporter, dur time.Duration) func() { |
| 70 | + ctx, cancel := context.WithTimeout(context.Background(), dur) |
| 71 | + fn := CheckContext(ctx, t) |
| 72 | + return func() { |
| 73 | + fn() |
| 74 | + cancel() |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// CheckContext is the same as Check, but uses a context.Context for |
| 79 | +// cancellation and timeout control |
| 80 | +func CheckContext(ctx context.Context, t ErrorReporter) func() { |
64 | 81 | orig := map[string]bool{} |
65 | 82 | for _, g := range interestingGoroutines() { |
66 | 83 | orig[g] = true |
67 | 84 | } |
68 | 85 | return func() { |
69 | | - // Loop, waiting for goroutines to shut down. |
70 | | - // Wait up to 5 seconds, but finish as quickly as possible. |
71 | | - deadline := time.Now().Add(5 * time.Second) |
| 86 | + var leaked []string |
72 | 87 | for { |
73 | | - var leaked []string |
74 | | - for _, g := range interestingGoroutines() { |
75 | | - if !orig[g] { |
76 | | - leaked = append(leaked, g) |
| 88 | + select { |
| 89 | + case <-ctx.Done(): |
| 90 | + t.Errorf("leaktest: timed out checking goroutines") |
| 91 | + default: |
| 92 | + leaked = make([]string, 0) |
| 93 | + for _, g := range interestingGoroutines() { |
| 94 | + if !orig[g] { |
| 95 | + leaked = append(leaked, g) |
| 96 | + } |
77 | 97 | } |
78 | | - } |
79 | | - if len(leaked) == 0 { |
80 | | - return |
81 | | - } |
82 | | - if time.Now().Before(deadline) { |
83 | | - time.Sleep(50 * time.Millisecond) |
| 98 | + if len(leaked) == 0 { |
| 99 | + return |
| 100 | + } |
| 101 | + // don't spin needlessly |
| 102 | + time.Sleep(time.Millisecond * 50) |
84 | 103 | continue |
85 | 104 | } |
86 | | - for _, g := range leaked { |
87 | | - t.Errorf("Leaked goroutine: %v", g) |
88 | | - } |
89 | | - return |
| 105 | + break |
| 106 | + } |
| 107 | + for _, g := range leaked { |
| 108 | + t.Errorf("leaktest: leaked goroutine: %v", g) |
90 | 109 | } |
91 | 110 | } |
92 | 111 | } |
0 commit comments