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  }