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  }