...

Source file src/github.com/sourcegraph/conc/panics/panics.go

Documentation: github.com/sourcegraph/conc/panics

     1  package panics
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"runtime/debug"
     7  	"sync/atomic"
     8  )
     9  
    10  // Catcher is used to catch panics. You can execute a function with Try,
    11  // which will catch any spawned panic. Try can be called any number of times,
    12  // from any number of goroutines. Once all calls to Try have completed, you can
    13  // get the value of the first panic (if any) with Recovered(), or you can just
    14  // propagate the panic (re-panic) with Repanic().
    15  type Catcher struct {
    16  	recovered atomic.Pointer[Recovered]
    17  }
    18  
    19  // Try executes f, catching any panic it might spawn. It is safe
    20  // to call from multiple goroutines simultaneously.
    21  func (p *Catcher) Try(f func()) {
    22  	defer p.tryRecover()
    23  	f()
    24  }
    25  
    26  func (p *Catcher) tryRecover() {
    27  	if val := recover(); val != nil {
    28  		rp := NewRecovered(1, val)
    29  		p.recovered.CompareAndSwap(nil, &rp)
    30  	}
    31  }
    32  
    33  // Repanic panics if any calls to Try caught a panic. It will panic with the
    34  // value of the first panic caught, wrapped in a panics.Recovered with caller
    35  // information.
    36  func (p *Catcher) Repanic() {
    37  	if val := p.Recovered(); val != nil {
    38  		panic(val)
    39  	}
    40  }
    41  
    42  // Recovered returns the value of the first panic caught by Try, or nil if
    43  // no calls to Try panicked.
    44  func (p *Catcher) Recovered() *Recovered {
    45  	return p.recovered.Load()
    46  }
    47  
    48  // NewRecovered creates a panics.Recovered from a panic value and a collected
    49  // stacktrace. The skip parameter allows the caller to skip stack frames when
    50  // collecting the stacktrace. Calling with a skip of 0 means include the call to
    51  // NewRecovered in the stacktrace.
    52  func NewRecovered(skip int, value any) Recovered {
    53  	// 64 frames should be plenty
    54  	var callers [64]uintptr
    55  	n := runtime.Callers(skip+1, callers[:])
    56  	return Recovered{
    57  		Value:   value,
    58  		Callers: callers[:n],
    59  		Stack:   debug.Stack(),
    60  	}
    61  }
    62  
    63  // Recovered is a panic that was caught with recover().
    64  type Recovered struct {
    65  	// The original value of the panic.
    66  	Value any
    67  	// The caller list as returned by runtime.Callers when the panic was
    68  	// recovered. Can be used to produce a more detailed stack information with
    69  	// runtime.CallersFrames.
    70  	Callers []uintptr
    71  	// The formatted stacktrace from the goroutine where the panic was recovered.
    72  	// Easier to use than Callers.
    73  	Stack []byte
    74  }
    75  
    76  // String renders a human-readable formatting of the panic.
    77  func (p *Recovered) String() string {
    78  	return fmt.Sprintf("panic: %v\nstacktrace:\n%s\n", p.Value, p.Stack)
    79  }
    80  
    81  // AsError casts the panic into an error implementation. The implementation
    82  // is unwrappable with the cause of the panic, if the panic was provided one.
    83  func (p *Recovered) AsError() error {
    84  	if p == nil {
    85  		return nil
    86  	}
    87  	return &ErrRecovered{*p}
    88  }
    89  
    90  // ErrRecovered wraps a panics.Recovered in an error implementation.
    91  type ErrRecovered struct{ Recovered }
    92  
    93  var _ error = (*ErrRecovered)(nil)
    94  
    95  func (p *ErrRecovered) Error() string { return p.String() }
    96  
    97  func (p *ErrRecovered) Unwrap() error {
    98  	if err, ok := p.Value.(error); ok {
    99  		return err
   100  	}
   101  	return nil
   102  }
   103  

View as plain text