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  }