1/*
2 *
3 * Copyright 2017 gRPC authors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19// Package leakcheck contains functions to check leaked goroutines.
20//
21// Call "defer leakcheck.Check(t)" at the beginning of tests.
22package leakcheck
23
24import (
25	"runtime"
26	"sort"
27	"strings"
28	"time"
29)
30
31var goroutinesToIgnore = []string{
32	"testing.Main(",
33	"testing.tRunner(",
34	"testing.(*M).",
35	"runtime.goexit",
36	"created by runtime.gc",
37	"created by runtime/trace.Start",
38	"interestingGoroutines",
39	"runtime.MHeap_Scavenger",
40	"signal.signal_recv",
41	"sigterm.handler",
42	"runtime_mcall",
43	"(*loggingT).flushDaemon",
44	"goroutine in C code",
45}
46
47// RegisterIgnoreGoroutine appends s into the ignore goroutine list. The
48// goroutines whose stack trace contains s will not be identified as leaked
49// goroutines. Not thread-safe, only call this function in init().
50func RegisterIgnoreGoroutine(s string) {
51	goroutinesToIgnore = append(goroutinesToIgnore, s)
52}
53
54func ignore(g string) bool {
55	sl := strings.SplitN(g, "\n", 2)
56	if len(sl) != 2 {
57		return true
58	}
59	stack := strings.TrimSpace(sl[1])
60	if strings.HasPrefix(stack, "testing.RunTests") {
61		return true
62	}
63
64	if stack == "" {
65		return true
66	}
67
68	for _, s := range goroutinesToIgnore {
69		if strings.Contains(stack, s) {
70			return true
71		}
72	}
73
74	return false
75}
76
77// interestingGoroutines returns all goroutines we care about for the purpose of
78// leak checking. It excludes testing or runtime ones.
79func interestingGoroutines() (gs []string) {
80	buf := make([]byte, 2<<20)
81	buf = buf[:runtime.Stack(buf, true)]
82	for _, g := range strings.Split(string(buf), "\n\n") {
83		if !ignore(g) {
84			gs = append(gs, g)
85		}
86	}
87	sort.Strings(gs)
88	return
89}
90
91// Errorfer is the interface that wraps the Errorf method. It's a subset of
92// testing.TB to make it easy to use Check.
93type Errorfer interface {
94	Errorf(format string, args ...interface{})
95}
96
97func check(efer Errorfer, timeout time.Duration) {
98	// Loop, waiting for goroutines to shut down.
99	// Wait up to timeout, but finish as quickly as possible.
100	deadline := time.Now().Add(timeout)
101	var leaked []string
102	for time.Now().Before(deadline) {
103		if leaked = interestingGoroutines(); len(leaked) == 0 {
104			return
105		}
106		time.Sleep(50 * time.Millisecond)
107	}
108	for _, g := range leaked {
109		efer.Errorf("Leaked goroutine: %v", g)
110	}
111}
112
113// Check looks at the currently-running goroutines and checks if there are any
114// interesting (created by gRPC) goroutines leaked. It waits up to 10 seconds
115// in the error cases.
116func Check(efer Errorfer) {
117	check(efer, 10*time.Second)
118}
119