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