Created
April 26, 2016 15:05
-
-
Save fortytw2/01ebee084bbc2d5b2044798488b9da06 to your computer and use it in GitHub Desktop.
Goroutine Leak Test Helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2013 The Go 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 leaktest provides tools to detect leaked goroutines in tests. | |
// To use it, call "defer util.LeakTest(t)()" at the beginning of each | |
// test that may use goroutines. | |
// copied out of the cockroachdb source tree | |
package util | |
import ( | |
"runtime" | |
"sort" | |
"strings" | |
"testing" | |
"time" | |
) | |
// interestingGoroutines returns all goroutines we care about for the purpose | |
// of leak checking. It excludes testing or runtime ones. | |
func interestingGoroutines() (gs []string) { | |
buf := make([]byte, 2<<20) | |
buf = buf[:runtime.Stack(buf, true)] | |
for _, g := range strings.Split(string(buf), "\n\n") { | |
sl := strings.SplitN(g, "\n", 2) | |
if len(sl) != 2 { | |
continue | |
} | |
stack := strings.TrimSpace(sl[1]) | |
if strings.HasPrefix(stack, "testing.RunTests") { | |
continue | |
} | |
if stack == "" || | |
// Below are the stacks ignored by the upstream leaktest code. | |
strings.Contains(stack, "testing.Main(") || | |
strings.Contains(stack, "runtime.goexit") || | |
strings.Contains(stack, "created by runtime.gc") || | |
strings.Contains(stack, "interestingGoroutines") || | |
strings.Contains(stack, "runtime.MHeap_Scavenger") || | |
strings.Contains(stack, "signal.signal_recv") || | |
strings.Contains(stack, "sigterm.handler") || | |
strings.Contains(stack, "runtime_mcall") || | |
strings.Contains(stack, "goroutine in C code") { | |
continue | |
} | |
gs = append(gs, g) | |
} | |
sort.Strings(gs) | |
return | |
} | |
// LeakTest snapshots the currently-running goroutines and returns a | |
// function to be run at the end of tests to see whether any | |
// goroutines leaked. | |
func LeakTest(t testing.TB) func() { | |
orig := map[string]bool{} | |
for _, g := range interestingGoroutines() { | |
orig[g] = true | |
} | |
return func() { | |
// Loop, waiting for goroutines to shut down. | |
// Wait up to 5 seconds, but finish as quickly as possible. | |
deadline := time.Now().Add(5 * time.Second) | |
for { | |
var leaked []string | |
for _, g := range interestingGoroutines() { | |
if !orig[g] { | |
leaked = append(leaked, g) | |
} | |
} | |
if len(leaked) == 0 { | |
return | |
} | |
if time.Now().Before(deadline) { | |
time.Sleep(50 * time.Millisecond) | |
continue | |
} | |
for _, g := range leaked { | |
t.Errorf("Leaked goroutine: %v", g) | |
} | |
return | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment