blob: 9921ca096d39f6f673c01cb598a1c166972b0e28 [file] [log] [blame]
khenaidooac637102019-01-14 15:44:34 -05001// 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
8package http2
9
10import (
11 "bytes"
12 "errors"
13 "fmt"
14 "os"
15 "runtime"
16 "strconv"
17 "sync"
Abhay Kumara2ae5992025-11-10 14:02:24 +000018 "sync/atomic"
khenaidooac637102019-01-14 15:44:34 -050019)
20
21var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
22
Abhay Kumara2ae5992025-11-10 14:02:24 +000023// 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.)
31var disableDebugGoroutines atomic.Bool
32
khenaidooac637102019-01-14 15:44:34 -050033type goroutineLock uint64
34
35func newGoroutineLock() goroutineLock {
Abhay Kumara2ae5992025-11-10 14:02:24 +000036 if !DebugGoroutines || disableDebugGoroutines.Load() {
khenaidooac637102019-01-14 15:44:34 -050037 return 0
38 }
39 return goroutineLock(curGoroutineID())
40}
41
42func (g goroutineLock) check() {
Abhay Kumara2ae5992025-11-10 14:02:24 +000043 if !DebugGoroutines || disableDebugGoroutines.Load() {
khenaidooac637102019-01-14 15:44:34 -050044 return
45 }
46 if curGoroutineID() != uint64(g) {
47 panic("running on the wrong goroutine")
48 }
49}
50
51func (g goroutineLock) checkNotOn() {
Abhay Kumara2ae5992025-11-10 14:02:24 +000052 if !DebugGoroutines || disableDebugGoroutines.Load() {
khenaidooac637102019-01-14 15:44:34 -050053 return
54 }
55 if curGoroutineID() == uint64(g) {
56 panic("running on the wrong goroutine")
57 }
58}
59
60var goroutineSpace = []byte("goroutine ")
61
62func 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
81var 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.
89func 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
171Error:
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.
176func cutoff64(base int) uint64 {
177 if base < 2 {
178 return 0
179 }
180 return (1<<64-1)/uint64(base) + 1
181}