github.com/koko1123/flow-go-1@v0.29.6/module/irrecoverable/irrecoverable.go (about) 1 package irrecoverable 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "runtime" 9 10 "go.uber.org/atomic" 11 ) 12 13 // Signaler sends the error out. 14 type Signaler struct { 15 errChan chan error 16 errThrown *atomic.Bool 17 } 18 19 func NewSignaler() (*Signaler, <-chan error) { 20 errChan := make(chan error, 1) 21 return &Signaler{ 22 errChan: errChan, 23 errThrown: atomic.NewBool(false), 24 }, errChan 25 } 26 27 // Throw is a narrow drop-in replacement for panic, log.Fatal, log.Panic, etc 28 // anywhere there's something connected to the error channel. It only sends 29 // the first error it is called with to the error channel, and logs subsequent 30 // errors as unhandled. 31 func (s *Signaler) Throw(err error) { 32 defer runtime.Goexit() 33 if s.errThrown.CompareAndSwap(false, true) { 34 s.errChan <- err 35 close(s.errChan) 36 } else { 37 // TODO: we simply log the unhandled irrecoverable to stderr for now, but we should probably 38 // allow the user to customize the logger / logging format used 39 log.New(os.Stderr, "", log.LstdFlags).Println(fmt.Errorf("unhandled irrecoverable: %w", err)) 40 } 41 } 42 43 // We define a constrained interface to provide a drop-in replacement for context.Context 44 // including in interfaces that compose it. 45 type SignalerContext interface { 46 context.Context 47 Throw(err error) // delegates to the signaler 48 sealed() // private, to constrain builder to using WithSignaler 49 } 50 51 // private, to force context derivation / WithSignaler 52 type signalerCtx struct { 53 context.Context 54 *Signaler 55 } 56 57 func (sc signalerCtx) sealed() {} 58 59 // the One True Way of getting a SignalerContext 60 func WithSignaler(parent context.Context) (SignalerContext, <-chan error) { 61 sig, errChan := NewSignaler() 62 return &signalerCtx{parent, sig}, errChan 63 } 64 65 // If we have an SignalerContext, we can directly ctx.Throw. 66 // 67 // But a lot of library methods expect context.Context, & we want to pass the same w/o boilerplate 68 // Moreover, we could have built with: context.WithCancel(irrecoverable.WithSignaler(ctx, sig)), 69 // "downcasting" to context.Context. Yet, we can still type-assert and recover. 70 // 71 // Throw can be a drop-in replacement anywhere we have a context.Context likely 72 // to support Irrecoverables. Note: this is not a method 73 func Throw(ctx context.Context, err error) { 74 signalerAbleContext, ok := ctx.(SignalerContext) 75 if ok { 76 signalerAbleContext.Throw(err) 77 } 78 // Be spectacular on how this does not -but should- handle irrecoverables: 79 log.Fatalf("irrecoverable error signaler not found for context, please implement! Unhandled irrecoverable error %v", err) 80 }