github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/irrecoverable/irrecoverable_example_test.go (about)

     1  package irrecoverable_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/onflow/flow-go/module/component"
    11  	"github.com/onflow/flow-go/module/irrecoverable"
    12  )
    13  
    14  var ErrTriggerRestart = errors.New("restart me")
    15  var ErrNoRestart = errors.New("fatal, no restarts")
    16  
    17  func Example() {
    18  	// a context is mandatory in order to call RunComponent
    19  	ctx, cancel := context.WithCancel(context.Background())
    20  	defer cancel()
    21  
    22  	// component.ComponentFactory encapsulates all of the component building logic
    23  	// required before running Start()
    24  	starts := 0
    25  	componentFactory := func() (component.Component, error) {
    26  		starts++
    27  		return NewExampleComponent(starts), nil
    28  	}
    29  
    30  	// this is the place to inspect the encountered error and implement the appropriate error
    31  	// handling behaviors, e.g. restarting the component, firing an alert to pagerduty, etc ...
    32  	// the shutdown of the component is handled for you by RunComponent, but you may consider
    33  	// performing additional cleanup here
    34  	onError := func(err error) component.ErrorHandlingResult {
    35  		// check the error type to decide whether to restart or shutdown
    36  		if errors.Is(err, ErrTriggerRestart) {
    37  			fmt.Printf("Restarting component after fatal error: %v\n", err)
    38  			return component.ErrorHandlingRestart
    39  		} else {
    40  			fmt.Printf("An irrecoverable error occurred: %v\n", err)
    41  			// shutdown other components. it might also make sense to just panic here
    42  			// depending on the circumstances
    43  			return component.ErrorHandlingStop
    44  		}
    45  	}
    46  
    47  	// run the component. this is a blocking call, and will return with an error if the
    48  	// first startup or any subsequent restart attempts fails or the context is canceled
    49  	err := component.RunComponent(ctx, componentFactory, onError)
    50  	if err != nil {
    51  		fmt.Printf("Error returned from RunComponent: %v\n", err)
    52  	}
    53  
    54  	// Output:
    55  	// [Component 1] Starting up
    56  	// [Component 1] Shutting down
    57  	// Restarting component after fatal error: restart me
    58  	// [Component 2] Starting up
    59  	// [Component 2] Shutting down
    60  	// An irrecoverable error occurred: fatal, no restarts
    61  	// Error returned from RunComponent: fatal, no restarts
    62  }
    63  
    64  // ExampleComponent is an example of a typical component
    65  type ExampleComponent struct {
    66  	id      int
    67  	started chan struct{}
    68  	ready   sync.WaitGroup
    69  	done    sync.WaitGroup
    70  }
    71  
    72  func NewExampleComponent(id int) *ExampleComponent {
    73  	return &ExampleComponent{
    74  		id:      id,
    75  		started: make(chan struct{}),
    76  	}
    77  }
    78  
    79  // start the component and register its shutdown handler
    80  // this component will throw an error after 20ms to demonstrate the error handling
    81  func (c *ExampleComponent) Start(ctx irrecoverable.SignalerContext) {
    82  	c.printMsg("Starting up")
    83  
    84  	// do some setup...
    85  
    86  	c.ready.Add(2)
    87  	c.done.Add(2)
    88  
    89  	go func() {
    90  		c.ready.Done()
    91  		defer c.done.Done()
    92  
    93  		<-ctx.Done()
    94  
    95  		c.printMsg("Shutting down")
    96  		// do some cleanup...
    97  	}()
    98  
    99  	go func() {
   100  		c.ready.Done()
   101  		defer c.done.Done()
   102  
   103  		select {
   104  		case <-time.After(20 * time.Millisecond):
   105  			// encounter irrecoverable error
   106  			if c.id > 1 {
   107  				ctx.Throw(ErrNoRestart)
   108  			} else {
   109  				ctx.Throw(ErrTriggerRestart)
   110  			}
   111  		case <-ctx.Done():
   112  			c.printMsg("Cancelled by parent")
   113  		}
   114  	}()
   115  
   116  	close(c.started)
   117  }
   118  
   119  // simply return the Started channel
   120  // all startup processing is done in Start()
   121  func (c *ExampleComponent) Ready() <-chan struct{} {
   122  	ready := make(chan struct{})
   123  	go func() {
   124  		<-c.started
   125  		c.ready.Wait()
   126  		close(ready)
   127  	}()
   128  	return ready
   129  }
   130  
   131  // simply return the Stopped channel
   132  // all shutdown processing is done in shutdownOnCancel()
   133  func (c *ExampleComponent) Done() <-chan struct{} {
   134  	done := make(chan struct{})
   135  	go func() {
   136  		<-c.started
   137  		c.done.Wait()
   138  		close(done)
   139  	}()
   140  	return done
   141  }
   142  
   143  func (c *ExampleComponent) printMsg(msg string) {
   144  	fmt.Printf("[Component %d] %s\n", c.id, msg)
   145  }