gotrack.go raw

   1  // Copyright 2014 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  // Defensive debug-only utility to track that functions run on the
   6  // goroutine that they're supposed to.
   7  
   8  package http2
   9  
  10  import (
  11  	"bytes"
  12  	"errors"
  13  	"fmt"
  14  	"os"
  15  	"runtime"
  16  	"strconv"
  17  	"sync"
  18  	"sync/atomic"
  19  )
  20  
  21  var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
  22  
  23  // Setting DebugGoroutines to false during a test to disable goroutine debugging
  24  // results in race detector complaints when a test leaves goroutines running before
  25  // returning. Tests shouldn't do this, of course, but when they do it generally shows
  26  // up as infrequent, hard-to-debug flakes. (See #66519.)
  27  //
  28  // Disable goroutine debugging during individual tests with an atomic bool.
  29  // (Note that it's safe to enable/disable debugging mid-test, so the actual race condition
  30  // here is harmless.)
  31  var disableDebugGoroutines atomic.Bool
  32  
  33  type goroutineLock uint64
  34  
  35  func newGoroutineLock() goroutineLock {
  36  	if !DebugGoroutines || disableDebugGoroutines.Load() {
  37  		return 0
  38  	}
  39  	return goroutineLock(curGoroutineID())
  40  }
  41  
  42  func (g goroutineLock) check() {
  43  	if !DebugGoroutines || disableDebugGoroutines.Load() {
  44  		return
  45  	}
  46  	if curGoroutineID() != uint64(g) {
  47  		panic("running on the wrong goroutine")
  48  	}
  49  }
  50  
  51  func (g goroutineLock) checkNotOn() {
  52  	if !DebugGoroutines || disableDebugGoroutines.Load() {
  53  		return
  54  	}
  55  	if curGoroutineID() == uint64(g) {
  56  		panic("running on the wrong goroutine")
  57  	}
  58  }
  59  
  60  var goroutineSpace = []byte("goroutine ")
  61  
  62  func curGoroutineID() uint64 {
  63  	bp := littleBuf.Get().(*[]byte)
  64  	defer littleBuf.Put(bp)
  65  	b := *bp
  66  	b = b[:runtime.Stack(b, false)]
  67  	// Parse the 4707 out of "goroutine 4707 ["
  68  	b = bytes.TrimPrefix(b, goroutineSpace)
  69  	i := bytes.IndexByte(b, ' ')
  70  	if i < 0 {
  71  		panic(fmt.Sprintf("No space found in %q", b))
  72  	}
  73  	b = b[:i]
  74  	n, err := parseUintBytes(b, 10, 64)
  75  	if err != nil {
  76  		panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
  77  	}
  78  	return n
  79  }
  80  
  81  var littleBuf = sync.Pool{
  82  	New: func() interface{} {
  83  		buf := make([]byte, 64)
  84  		return &buf
  85  	},
  86  }
  87  
  88  // parseUintBytes is like strconv.ParseUint, but using a []byte.
  89  func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
  90  	var cutoff, maxVal uint64
  91  
  92  	if bitSize == 0 {
  93  		bitSize = int(strconv.IntSize)
  94  	}
  95  
  96  	s0 := s
  97  	switch {
  98  	case len(s) < 1:
  99  		err = strconv.ErrSyntax
 100  		goto Error
 101  
 102  	case 2 <= base && base <= 36:
 103  		// valid base; nothing to do
 104  
 105  	case base == 0:
 106  		// Look for octal, hex prefix.
 107  		switch {
 108  		case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
 109  			base = 16
 110  			s = s[2:]
 111  			if len(s) < 1 {
 112  				err = strconv.ErrSyntax
 113  				goto Error
 114  			}
 115  		case s[0] == '0':
 116  			base = 8
 117  		default:
 118  			base = 10
 119  		}
 120  
 121  	default:
 122  		err = errors.New("invalid base " + strconv.Itoa(base))
 123  		goto Error
 124  	}
 125  
 126  	n = 0
 127  	cutoff = cutoff64(base)
 128  	maxVal = 1<<uint(bitSize) - 1
 129  
 130  	for i := 0; i < len(s); i++ {
 131  		var v byte
 132  		d := s[i]
 133  		switch {
 134  		case '0' <= d && d <= '9':
 135  			v = d - '0'
 136  		case 'a' <= d && d <= 'z':
 137  			v = d - 'a' + 10
 138  		case 'A' <= d && d <= 'Z':
 139  			v = d - 'A' + 10
 140  		default:
 141  			n = 0
 142  			err = strconv.ErrSyntax
 143  			goto Error
 144  		}
 145  		if int(v) >= base {
 146  			n = 0
 147  			err = strconv.ErrSyntax
 148  			goto Error
 149  		}
 150  
 151  		if n >= cutoff {
 152  			// n*base overflows
 153  			n = 1<<64 - 1
 154  			err = strconv.ErrRange
 155  			goto Error
 156  		}
 157  		n *= uint64(base)
 158  
 159  		n1 := n + uint64(v)
 160  		if n1 < n || n1 > maxVal {
 161  			// n+v overflows
 162  			n = 1<<64 - 1
 163  			err = strconv.ErrRange
 164  			goto Error
 165  		}
 166  		n = n1
 167  	}
 168  
 169  	return n, nil
 170  
 171  Error:
 172  	return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
 173  }
 174  
 175  // Return the first number n such that n*base >= 1<<64.
 176  func cutoff64(base int) uint64 {
 177  	if base < 2 {
 178  		return 0
 179  	}
 180  	return (1<<64-1)/uint64(base) + 1
 181  }
 182