github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/testleak/leaktest.go (about)

     1  // Copyright 2020 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 WHTCORPS INC, 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/whtcorpsinc/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  		"created by github.com/whtcorpsinc/milevadb.init",
    36  		"testing.RunTests",
    37  		"check.(*resultTracker).start",
    38  		"check.(*suiteRunner).runFunc",
    39  		"check.(*suiteRunner).parallelRun",
    40  		"localstore.(*dbStore).scheduler",
    41  		"einsteindb.(*noGCHandler).Start",
    42  		"dbs.(*dbs).start",
    43  		"dbs.(*delRange).startEmulator",
    44  		"petri.NewPetri",
    45  		"testing.(*T).Run",
    46  		"petri.(*Petri).LoadPrivilegeLoop",
    47  		"petri.(*Petri).UFIDelateBlockStatsLoop",
    48  		"testing.Main(",
    49  		"runtime.goexit",
    50  		"created by runtime.gc",
    51  		"interestingGoroutines",
    52  		"runtime.MHeap_Scavenger",
    53  		"created by os/signal.init",
    54  		// these go routines are async terminated, so they may still alive after test end, thus cause
    55  		// false positive leak failures
    56  		"google.golang.org/grpc.(*addrConn).resetTransport",
    57  		"google.golang.org/grpc.(*ccBalancerWrapper).watcher",
    58  		"github.com/whtcorpsinc/goleveldb/leveldb/soliton.(*BufferPool).drain",
    59  		"github.com/whtcorpsinc/goleveldb/leveldb.(*EDB).compactionError",
    60  		"github.com/whtcorpsinc/goleveldb/leveldb.(*EDB).mpoolDrain",
    61  		"go.etcd.io/etcd/pkg/logutil.(*MergeLogger).outputLoop",
    62  		"go.etcd.io/etcd/v3/pkg/logutil.(*MergeLogger).outputLoop",
    63  		"oracles.(*FIDelOracle).uFIDelateTS",
    64  		"einsteindb.(*einsteindbStore).runSafePointChecker",
    65  		"einsteindb.(*RegionCache).asyncCheckAndResolveLoop",
    66  		"github.com/whtcorpsinc/badger",
    67  		"github.com/ngaut/entangledstore/einsteindb.(*MVCCStore).runUFIDelateSafePointLoop",
    68  	}
    69  	shouldIgnore := func(stack string) bool {
    70  		if stack == "" {
    71  			return true
    72  		}
    73  		for _, ident := range ignoreList {
    74  			if strings.Contains(stack, ident) {
    75  				return true
    76  			}
    77  		}
    78  		return false
    79  	}
    80  	for _, g := range strings.Split(string(buf), "\n\n") {
    81  		sl := strings.SplitN(g, "\n", 2)
    82  		if len(sl) != 2 {
    83  			continue
    84  		}
    85  		stack := strings.TrimSpace(sl[1])
    86  		if shouldIgnore(stack) {
    87  			continue
    88  		}
    89  		gs = append(gs, stack)
    90  	}
    91  	sort.Strings(gs)
    92  	return
    93  }
    94  
    95  var beforeTestGoroutines = map[string]bool{}
    96  var testGoroutinesInited bool
    97  
    98  // BeforeTest gets the current goroutines.
    99  // It's used for check.Suite.SetUpSuite() function.
   100  // Now it's only used in the milevadb_test.go.
   101  // Note: it's not accurate, consider the following function:
   102  // func loop() {
   103  //   for {
   104  //     select {
   105  //       case <-ticker.C:
   106  //         DoSomething()
   107  //     }
   108  //   }
   109  // }
   110  // If this loop step into DoSomething() during BeforeTest(), the stack for this goroutine will contain DoSomething().
   111  // Then if this loop jumps out of DoSomething during AfterTest(), the stack for this goroutine will not contain DoSomething().
   112  // Resulting in false-positive leak reports.
   113  func BeforeTest() {
   114  	for _, g := range interestingGoroutines() {
   115  		beforeTestGoroutines[g] = true
   116  	}
   117  	testGoroutinesInited = true
   118  }
   119  
   120  const defaultCheckCnt = 50
   121  
   122  func checkLeakAfterTest(errorFunc func(cnt int, g string)) func() {
   123  	// After `BeforeTest`, `beforeTestGoroutines` may still be empty, in this case,
   124  	// we shouldn't init it again.
   125  	if !testGoroutinesInited && len(beforeTestGoroutines) == 0 {
   126  		for _, g := range interestingGoroutines() {
   127  			beforeTestGoroutines[g] = true
   128  		}
   129  	}
   130  
   131  	cnt := defaultCheckCnt
   132  	return func() {
   133  		defer func() {
   134  			beforeTestGoroutines = map[string]bool{}
   135  			testGoroutinesInited = false
   136  		}()
   137  
   138  		var leaked []string
   139  		for i := 0; i < cnt; i++ {
   140  			leaked = leaked[:0]
   141  			for _, g := range interestingGoroutines() {
   142  				if !beforeTestGoroutines[g] {
   143  					leaked = append(leaked, g)
   144  				}
   145  			}
   146  			// Bad stuff found, but goroutines might just still be
   147  			// shutting down, so give it some time.
   148  			if len(leaked) != 0 {
   149  				time.Sleep(50 * time.Millisecond)
   150  				continue
   151  			}
   152  
   153  			return
   154  		}
   155  		for _, g := range leaked {
   156  			errorFunc(cnt, g)
   157  		}
   158  	}
   159  }
   160  
   161  // AfterTest gets the current goroutines and runs the returned function to
   162  // get the goroutines at that time to contrast whether any goroutines leaked.
   163  // Usage: defer testleak.AfterTest(c)()
   164  // It can call with BeforeTest() at the beginning of check.Suite.TearDownSuite() or
   165  // call alone at the beginning of each test.
   166  func AfterTest(c *check.C) func() {
   167  	errorFunc := func(cnt int, g string) {
   168  		c.Errorf("Test %s check-count %d appears to have leaked: %v", c.TestName(), cnt, g)
   169  	}
   170  	return checkLeakAfterTest(errorFunc)
   171  }
   172  
   173  // AfterTestT is used after all the test cases is finished.
   174  func AfterTestT(t *testing.T) func() {
   175  	errorFunc := func(cnt int, g string) {
   176  		t.Errorf("Test %s check-count %d appears to have leaked: %v", t.Name(), cnt, g)
   177  	}
   178  	return checkLeakAfterTest(errorFunc)
   179  }