github.com/cloudwego/kitex@v0.9.0/pkg/remote/trans/nphttp2/grpc/testutils/leakcheck/leakcheck.go (about) 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 * This file may have been modified by CloudWeGo authors. All CloudWeGo 18 * Modifications are Copyright 2021 CloudWeGo Authors. 19 */ 20 21 // Package leakcheck contains functions to check leaked goroutines. 22 // 23 // Call "defer leakcheck.Check(t)" at the beginning of tests. 24 package leakcheck 25 26 import ( 27 "runtime" 28 "sort" 29 "strings" 30 "time" 31 ) 32 33 var goroutinesToIgnore = []string{ 34 "testing.Main(", 35 "testing.tRunner(", 36 "testing.(*M).", 37 "runtime.goexit", 38 "created by runtime.gc", 39 "created by runtime/trace.Start", 40 "interestingGoroutines", 41 "runtime.MHeap_Scavenger", 42 "signal.signal_recv", 43 "sigterm.handler", 44 "runtime_mcall", 45 "(*loggingT).flushDaemon", 46 "goroutine in C code", 47 "httputil.DumpRequestOut", // TODO: Remove this once Go1.13 support is removed. https://github.com/golang/go/issues/37669. 48 } 49 50 // RegisterIgnoreGoroutine appends s into the ignore goroutine list. The 51 // goroutines whose stack trace contains s will not be identified as leaked 52 // goroutines. Not thread-safe, only call this function in init(). 53 func RegisterIgnoreGoroutine(s string) { 54 goroutinesToIgnore = append(goroutinesToIgnore, s) 55 } 56 57 func ignore(g string) bool { 58 sl := strings.SplitN(g, "\n", 2) 59 if len(sl) != 2 { 60 return true 61 } 62 stack := strings.TrimSpace(sl[1]) 63 if strings.HasPrefix(stack, "testing.RunTests") { 64 return true 65 } 66 67 if stack == "" { 68 return true 69 } 70 71 for _, s := range goroutinesToIgnore { 72 if strings.Contains(stack, s) { 73 return true 74 } 75 } 76 77 return false 78 } 79 80 // interestingGoroutines returns all goroutines we care about for the purpose of 81 // leak checking. It excludes testing or runtime ones. 82 func interestingGoroutines() (gs []string) { 83 buf := make([]byte, 2<<20) 84 buf = buf[:runtime.Stack(buf, true)] 85 for _, g := range strings.Split(string(buf), "\n\n") { 86 if !ignore(g) { 87 gs = append(gs, g) 88 } 89 } 90 sort.Strings(gs) 91 return 92 } 93 94 // Errorfer is the interface that wraps the Errorf method. It's a subset of 95 // testing.TB to make it easy to use Check. 96 type Errorfer interface { 97 Errorf(format string, args ...interface{}) 98 } 99 100 func check(efer Errorfer, timeout time.Duration) { 101 // Loop, waiting for goroutines to shut down. 102 // Wait up to timeout, but finish as quickly as possible. 103 deadline := time.Now().Add(timeout) 104 var leaked []string 105 for time.Now().Before(deadline) { 106 if leaked = interestingGoroutines(); len(leaked) == 0 { 107 return 108 } 109 time.Sleep(50 * time.Millisecond) 110 } 111 for _, g := range leaked { 112 efer.Errorf("Leaked goroutine: %v", g) 113 } 114 } 115 116 // Check looks at the currently-running goroutines and checks if there are any 117 // interesting (created by gRPC) goroutines leaked. It waits up to 10 seconds 118 // in the error cases. 119 func Check(efer Errorfer) { 120 check(efer, 10*time.Second) 121 }