github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/pkg/util/testleak/leaktest.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Copyright 2020 PingCAP, Inc. 6 // 7 // Licensed under the Apache License, Version 2.0 (the "License"); 8 // you may not use this file except in compliance with the License. 9 // You may obtain a copy of the License at 10 // 11 // http://www.apache.org/licenses/LICENSE-2.0 12 // 13 // Unless required by applicable law or agreed to in writing, software 14 // distributed under the License is distributed on an "AS IS" BASIS, 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 // +build leak 18 19 package testleak 20 21 import ( 22 "runtime" 23 "sort" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/pingcap/check" 29 ) 30 31 func interestingGoroutines() (gs []string) { 32 buf := make([]byte, 2<<20) 33 buf = buf[:runtime.Stack(buf, true)] 34 ignoreList := []string{ 35 "testing.RunTests", 36 "check.(*resultTracker).start", 37 "check.(*suiteRunner).runFunc", 38 "check.(*suiteRunner).parallelRun", 39 "localstore.(*dbStore).scheduler", 40 "testing.(*T).Run", 41 "testing.Main(", 42 "runtime.goexit", 43 "interestingGoroutines", 44 "runtime.MHeap_Scavenger", 45 "created by os/signal.init", 46 // these go routines are async terminated, so they may still alive after test end, thus cause 47 // false positive leak failures 48 "google.golang.org/grpc.(*addrConn).resetTransport", 49 "google.golang.org/grpc.(*ccBalancerWrapper).watcher", 50 "go.etcd.io/etcd/pkg/logutil.(*MergeLogger).outputLoop", 51 "go.etcd.io/etcd/v3/pkg/logutil.(*MergeLogger).outputLoop", 52 // library used by sarama, ref: https://github.com/rcrowley/go-metrics/pull/266 53 "github.com/rcrowley/go-metrics.(*meterArbiter).tick", 54 // TODO: remove these two lines after unified sorter is fixed 55 "github.com/pingcap/ticdc/cdc/puller/sorter.newBackEndPool", 56 "github.com/pingcap/ticdc/cdc/puller/sorter.(*heapSorter).flush", 57 // kv client region worker pool 58 "github.com/pingcap/ticdc/cdc/kv.RunWorkerPool", 59 "github.com/pingcap/ticdc/pkg/workerpool.(*defaultPoolImpl).Run", 60 } 61 shouldIgnore := func(stack string) bool { 62 if stack == "" { 63 return true 64 } 65 for _, ident := range ignoreList { 66 if strings.Contains(stack, ident) { 67 return true 68 } 69 } 70 return false 71 } 72 for _, g := range strings.Split(string(buf), "\n\n") { 73 sl := strings.SplitN(g, "\n", 2) 74 if len(sl) != 2 { 75 continue 76 } 77 stack := strings.TrimSpace(sl[1]) 78 if shouldIgnore(stack) { 79 continue 80 } 81 gs = append(gs, stack) 82 } 83 sort.Strings(gs) 84 return 85 } 86 87 var ( 88 beforeTestGoroutines = map[string]bool{} 89 testGoroutinesInited bool 90 ) 91 92 // BeforeTest gets the current goroutines. 93 // It's used for check.Suite.SetUpSuite() function. 94 // Now it's only used in the tidb_test.go. 95 // Note: it's not accurate, consider the following function: 96 // func loop() { 97 // for { 98 // select { 99 // case <-ticker.C: 100 // DoSomething() 101 // } 102 // } 103 // } 104 // If this loop step into DoSomething() during BeforeTest(), the stack for this goroutine will contain DoSomething(). 105 // Then if this loop jumps out of DoSomething during AfterTest(), the stack for this goroutine will not contain DoSomething(). 106 // Resulting in false-positive leak reports. 107 func BeforeTest() { 108 for _, g := range interestingGoroutines() { 109 beforeTestGoroutines[g] = true 110 } 111 testGoroutinesInited = true 112 } 113 114 const defaultCheckCnt = 50 115 116 func checkLeakAfterTest(errorFunc func(cnt int, g string)) func() { 117 // After `BeforeTest`, `beforeTestGoroutines` may still be empty, in this case, 118 // we shouldn't init it again. 119 if !testGoroutinesInited && len(beforeTestGoroutines) == 0 { 120 for _, g := range interestingGoroutines() { 121 beforeTestGoroutines[g] = true 122 } 123 } 124 125 cnt := defaultCheckCnt 126 return func() { 127 defer func() { 128 beforeTestGoroutines = map[string]bool{} 129 testGoroutinesInited = false 130 }() 131 132 var leaked []string 133 for i := 0; i < cnt; i++ { 134 leaked = leaked[:0] 135 for _, g := range interestingGoroutines() { 136 if !beforeTestGoroutines[g] { 137 leaked = append(leaked, g) 138 } 139 } 140 // Bad stuff found, but goroutines might just still be 141 // shutting down, so give it some time. 142 if len(leaked) != 0 { 143 time.Sleep(50 * time.Millisecond) 144 continue 145 } 146 147 return 148 } 149 for _, g := range leaked { 150 errorFunc(cnt, g) 151 } 152 } 153 } 154 155 // AfterTest gets the current goroutines and runs the returned function to 156 // get the goroutines at that time to contrast whether any goroutines leaked. 157 // Usage: defer testleak.AfterTest(c)() 158 // It can call with BeforeTest() at the beginning of check.Suite.TearDownSuite() or 159 // call alone at the beginning of each test. 160 func AfterTest(c *check.C) func() { 161 errorFunc := func(cnt int, g string) { 162 c.Errorf("Test %s check-count %d appears to have leaked: %v", c.TestName(), cnt, g) 163 } 164 return checkLeakAfterTest(errorFunc) 165 } 166 167 // AfterTestT is used after all the test cases is finished. 168 func AfterTestT(t *testing.T) func() { 169 errorFunc := func(cnt int, g string) { 170 t.Errorf("Test %s check-count %d appears to have leaked: %v", t.Name(), cnt, g) 171 } 172 return checkLeakAfterTest(errorFunc) 173 }