storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/leak-detect_test.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 package cmd 6 7 import ( 8 "runtime/debug" 9 "sort" 10 "strings" 11 "time" 12 ) 13 14 const ( 15 // deadline (in seconds) up to which the go routine leak detection has to be retried. 16 leakDetectDeadline = 5 17 // pause time (in milliseconds) between each snapshot at the end of the go routine leak detection. 18 leakDetectPauseTimeMs = 50 19 ) 20 21 // LeakDetect - type with methods for go routine leak detection. 22 type LeakDetect struct { 23 relevantRoutines map[string]bool 24 } 25 26 // NewLeakDetect - Initialize a LeakDetector with the snapshot of relevant Go routines. 27 func NewLeakDetect() LeakDetect { 28 snapshot := LeakDetect{ 29 relevantRoutines: make(map[string]bool), 30 } 31 for _, g := range pickRelevantGoroutines() { 32 snapshot.relevantRoutines[g] = true 33 } 34 return snapshot 35 } 36 37 // CompareCurrentSnapshot - Compares the initial relevant stack trace with the current one (during the time of invocation). 38 func (initialSnapShot LeakDetect) CompareCurrentSnapshot() []string { 39 var stackDiff []string 40 for _, g := range pickRelevantGoroutines() { 41 // Identify the Go routines those were not present in the initial snapshot. 42 // In other words a stack diff. 43 if !initialSnapShot.relevantRoutines[g] { 44 stackDiff = append(stackDiff, g) 45 } 46 } 47 return stackDiff 48 } 49 50 // DetectLeak - Creates a snapshot of runtime stack and compares it with the initial stack snapshot. 51 func (initialSnapShot LeakDetect) DetectLeak(t TestErrHandler) { 52 if t.Failed() { 53 return 54 } 55 // Loop, waiting for goroutines to shut down. 56 // Wait up to 5 seconds, but finish as quickly as possible. 57 deadline := UTCNow().Add(leakDetectDeadline * time.Second) 58 for { 59 // get sack snapshot of relevant go routines. 60 leaked := initialSnapShot.CompareCurrentSnapshot() 61 // current stack snapshot matches the initial one, no leaks, return. 62 if len(leaked) == 0 { 63 return 64 } 65 // wait a test again will deadline. 66 if UTCNow().Before(deadline) { 67 time.Sleep(leakDetectPauseTimeMs * time.Millisecond) 68 continue 69 } 70 // after the deadline time report all the difference in the latest snapshot compared with the initial one. 71 for _, g := range leaked { 72 t.Errorf("Leaked goroutine: %v", g) 73 } 74 return 75 } 76 } 77 78 // DetectTestLeak - snapshots the currently running goroutines and returns a 79 // function to be run at the end of tests to see whether any 80 // goroutines leaked. 81 // Usage: `defer DetectTestLeak(t)()` in beginning line of benchmarks or unit tests. 82 func DetectTestLeak(t TestErrHandler) func() { 83 initialStackSnapShot := NewLeakDetect() 84 return func() { 85 initialStackSnapShot.DetectLeak(t) 86 } 87 } 88 89 // list of functions to be ignored from the stack trace. 90 // Leak detection is done when tests are run, should ignore the tests related functions, 91 // and other runtime functions while identifying leaks. 92 var ignoredStackFns = []string{ 93 "", 94 // Below are the stacks ignored by the upstream leaktest code. 95 "testing.Main(", 96 "testing.tRunner(", 97 "testing.tRunner(", 98 "runtime.goexit", 99 "created by runtime.gc", 100 // ignore the snapshot function. 101 // since the snapshot is taken here the entry will have the current function too. 102 "pickRelevantGoroutines", 103 "runtime.MHeap_Scavenger", 104 "signal.signal_recv", 105 "sigterm.handler", 106 "runtime_mcall", 107 "goroutine in C code", 108 } 109 110 // Identify whether the stack trace entry is part of ignoredStackFn . 111 func isIgnoredStackFn(stack string) (ok bool) { 112 ok = true 113 for _, stackFn := range ignoredStackFns { 114 if !strings.Contains(stack, stackFn) { 115 ok = false 116 continue 117 } 118 break 119 } 120 return ok 121 } 122 123 // pickRelevantGoroutines returns all goroutines we care about for the purpose 124 // of leak checking. It excludes testing or runtime ones. 125 func pickRelevantGoroutines() (gs []string) { 126 // get runtime stack buffer. 127 buf := debug.Stack() 128 // runtime stack of go routines will be listed with 2 blank spaces between each of them, so split on "\n\n" . 129 for _, g := range strings.Split(string(buf), "\n\n") { 130 // Again split on a new line, the first line of the second half contaisn the info about the go routine. 131 sl := strings.SplitN(g, "\n", 2) 132 if len(sl) != 2 { 133 continue 134 } 135 stack := strings.TrimSpace(sl[1]) 136 // ignore the testing go routine. 137 // since the tests will be invoking the leaktest it would contain the test go routine. 138 if strings.HasPrefix(stack, "testing.RunTests") { 139 continue 140 } 141 // Ignore the following go routines. 142 // testing and run time go routines should be ignored, only the application generated go routines should be taken into account. 143 if isIgnoredStackFn(stack) { 144 continue 145 } 146 gs = append(gs, g) 147 } 148 sort.Strings(gs) 149 return 150 }