...

Source file src/github.com/letsencrypt/boulder/grpc/internal/leakcheck/leakcheck.go

Documentation: github.com/letsencrypt/boulder/grpc/internal/leakcheck

     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 ...interface{})
   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  }
   125  

View as plain text