github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/checked/debug.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package checked 22 23 import ( 24 "bytes" 25 "fmt" 26 "os" 27 "runtime" 28 "sync" 29 "time" 30 ) 31 32 const ( 33 defaultTraceback = false 34 defaultTracebackCycles = 3 35 defaultTracebackMaxDepth = 64 36 defaultLeakDetection = false 37 ) 38 39 var ( 40 traceback = defaultTraceback 41 tracebackCycles = defaultTracebackCycles 42 tracebackMaxDepth = defaultTracebackMaxDepth 43 panicFn = defaultPanic 44 leakDetectionFlag = defaultLeakDetection 45 ) 46 47 var tracebackCallersPool = sync.Pool{New: func() interface{} { 48 // Pools should generally only return pointer types, since a pointer 49 // can be put into the return interface value without an allocation. 50 // However, since this package is used just for debugging, we make the 51 // tradeoff of greater code clarity by putting slices directly into the 52 // pool at the cost of an additional allocation of the three words which 53 // comprise the slice on each put. 54 return make([]uintptr, tracebackMaxDepth) 55 }} 56 57 var tracebackEntryPool = sync.Pool{New: func() interface{} { 58 return &debuggerEntry{} 59 }} 60 61 var leaks struct { 62 sync.RWMutex 63 m map[string]uint64 64 } 65 66 // PanicFn is a panic function to call on invalid checked state 67 type PanicFn func(e error) 68 69 // SetPanicFn sets the panic function 70 func SetPanicFn(fn PanicFn) { 71 panicFn = fn 72 } 73 74 // Panic will execute the currently set panic function 75 func Panic(e error) { 76 panicFn(e) 77 } 78 79 // ResetPanicFn resets the panic function to the default runtime panic 80 func ResetPanicFn() { 81 panicFn = defaultPanic 82 } 83 84 // EnableTracebacks turns traceback collection for events on 85 func EnableTracebacks() { 86 traceback = true 87 } 88 89 // DisableTracebacks turns traceback collection for events off 90 func DisableTracebacks() { 91 traceback = false 92 } 93 94 // SetTracebackCycles sets the count of traceback cycles to keep if enabled 95 func SetTracebackCycles(value int) { 96 tracebackCycles = value 97 } 98 99 // SetTracebackMaxDepth sets the max amount of frames to capture for traceback 100 func SetTracebackMaxDepth(frames int) { 101 tracebackMaxDepth = frames 102 } 103 104 // EnableLeakDetection turns leak detection on. 105 func EnableLeakDetection() { 106 leakDetectionFlag = true 107 } 108 109 // DisableLeakDetection turns leak detection off. 110 func DisableLeakDetection() { 111 leakDetectionFlag = false 112 } 113 114 func leakDetectionEnabled() bool { 115 return leakDetectionFlag 116 } 117 118 // DumpLeaks returns all detected leaks so far. 119 func DumpLeaks() []string { 120 var r []string 121 122 leaks.RLock() 123 124 for k, v := range leaks.m { 125 r = append(r, fmt.Sprintf("leaked %d bytes, origin:\n%s", v, k)) 126 } 127 128 leaks.RUnlock() 129 130 return r 131 } 132 133 func defaultPanic(e error) { 134 panic(e) 135 } 136 137 func panicRef(c *RefCount, err error) { 138 if traceback { 139 trace := getDebuggerRef(c).String() 140 err = fmt.Errorf("%v, traceback:\n\n%s", err, trace) 141 } 142 143 panicFn(err) 144 } 145 146 type debuggerEvent int 147 148 const ( 149 incRefEvent debuggerEvent = iota 150 decRefEvent 151 moveRefEvent 152 finalizeEvent 153 incReadsEvent 154 decReadsEvent 155 incWritesEvent 156 decWritesEvent 157 ) 158 159 func (d debuggerEvent) String() string { 160 switch d { 161 case incRefEvent: 162 return "IncRef" 163 case decRefEvent: 164 return "DecRef" 165 case moveRefEvent: 166 return "MoveRef" 167 case finalizeEvent: 168 return "Finalize" 169 case incReadsEvent: 170 return "IncReads" 171 case decReadsEvent: 172 return "DecReads" 173 case incWritesEvent: 174 return "IncWrites" 175 case decWritesEvent: 176 return "DecWrites" 177 } 178 return "Unknown" 179 } 180 181 type debugger struct { 182 sync.Mutex 183 entries [][]*debuggerEntry 184 } 185 186 func (d *debugger) append(event debuggerEvent, ref int, pc []uintptr) { 187 d.Lock() 188 if len(d.entries) == 0 { 189 d.entries = make([][]*debuggerEntry, 1, tracebackCycles) 190 } 191 idx := len(d.entries) - 1 192 entry := tracebackEntryPool.Get().(*debuggerEntry) 193 entry.event = event 194 entry.ref = ref 195 entry.pc = pc 196 entry.t = time.Now() 197 d.entries[idx] = append(d.entries[idx], entry) 198 if event == finalizeEvent { 199 if len(d.entries) == tracebackCycles { 200 // Shift all tracebacks back one if at end of traceback cycles 201 slice := d.entries[0] 202 for i, entry := range slice { 203 tracebackCallersPool.Put(entry.pc) // nolint: megacheck 204 entry.pc = nil 205 tracebackEntryPool.Put(entry) 206 slice[i] = nil 207 } 208 for i := 1; i < len(d.entries); i++ { 209 d.entries[i-1] = d.entries[i] 210 } 211 d.entries[idx] = slice[:0] 212 } else { 213 // Begin writing new events to the next cycle 214 d.entries = d.entries[:len(d.entries)+1] 215 } 216 } 217 d.Unlock() 218 } 219 220 func (d *debugger) String() string { 221 buffer := bytes.NewBuffer(nil) 222 d.Lock() 223 // Reverse the entries for time descending 224 for i := len(d.entries) - 1; i >= 0; i-- { 225 for j := len(d.entries[i]) - 1; j >= 0; j-- { 226 buffer.WriteString(d.entries[i][j].String()) 227 } 228 } 229 d.Unlock() 230 return buffer.String() 231 } 232 233 type debuggerRef struct { 234 debugger 235 onFinalize OnFinalize 236 } 237 238 func (d *debuggerRef) OnFinalize() { 239 if d.onFinalize != nil { 240 d.onFinalize.OnFinalize() 241 } 242 } 243 244 type debuggerEntry struct { 245 event debuggerEvent 246 ref int 247 pc []uintptr 248 t time.Time 249 } 250 251 func (e *debuggerEntry) String() string { 252 buf := bytes.NewBuffer(nil) 253 frames := runtime.CallersFrames(e.pc) 254 for { 255 frame, more := frames.Next() 256 buf.WriteString(frame.Function) 257 buf.WriteString("(...)") 258 buf.WriteString("\n") 259 buf.WriteString("\t") 260 buf.WriteString(frame.File) 261 buf.WriteString(":") 262 buf.WriteString(fmt.Sprintf("%d", frame.Line)) 263 buf.WriteString(fmt.Sprintf(" +%x", frame.Entry)) 264 buf.WriteString("\n") 265 if !more { 266 break 267 } 268 } 269 return fmt.Sprintf("%s, ref=%d, unixnanos=%d:\n%s\n", 270 e.event.String(), e.ref, e.t.UnixNano(), buf.String()) 271 } 272 273 func getDebuggerRef(c *RefCount) *debuggerRef { 274 // Note: because finalizer is an atomic pointer not using 275 // CompareAndSwapPointer makes this code is racy, however 276 // it is safe due to using atomic load and stores. 277 // This is used primarily for debugging and the races will 278 // show up when inspecting the tracebacks. 279 onFinalize := c.OnFinalize() 280 if onFinalize == nil { 281 debugger := &debuggerRef{} 282 c.SetOnFinalize(debugger) 283 return debugger 284 } 285 286 debugger, ok := onFinalize.(*debuggerRef) 287 if !ok { 288 // Wrap the existing finalizer in a debuggerRef 289 debugger := &debuggerRef{onFinalize: onFinalize} 290 c.SetOnFinalize(debugger) 291 return debugger 292 } 293 294 return debugger 295 } 296 297 func tracebackEvent(c *RefCount, ref int, e debuggerEvent) { 298 if !traceback { 299 return 300 } 301 302 d := getDebuggerRef(c) 303 depth := tracebackMaxDepth 304 pc := tracebackCallersPool.Get().([]uintptr) 305 if capacity := cap(pc); capacity < depth { 306 // Defensive programming here in case someone changes 307 // the max depth during runtime 308 pc = make([]uintptr, depth) 309 } 310 pc = pc[:depth] 311 skipEntry := 2 312 n := runtime.Callers(skipEntry, pc) 313 d.append(e, ref, pc[:n]) 314 } 315 316 func init() { 317 leaks.m = make(map[string]uint64) 318 319 if os.Getenv("DEBUG_ENABLE_TRACEBACKS") == "true" { 320 EnableTracebacks() 321 } 322 }