 Documentation
      ¶
      Documentation
      ¶
    
    
  
    
  
    Overview ¶
Example ¶
package main
import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
	"github.com/onflow/flow-go/module"
	"github.com/onflow/flow-go/module/irrecoverable"
)
var ErrTriggerRestart = errors.New("restart me")
var ErrNoRestart = errors.New("fatal, no restarts")
func main() {
	// a context is mandatory in order to call RunComponent
	ctx, cancel := context.WithCancel(context.Background())
	// module.ComponentFactory encapsulates all of the component building logic
	// required before running Start()
	starts := 0
	componentFactory := func() (module.Component, error) {
		starts++
		return NewExampleComponent(starts), nil
	}
	// this is the place to inspect the encountered error and implement the appropriate error
	// handling behaviors, e.g. restarting the component, firing an alert to pagerduty, etc ...
	// the shutdown of the component is handled for you by RunComponent, but you may consider
	// performing additional cleanup here
	onError := func(err error, triggerRestart func()) {
		// check the error type to decide whether to restart or shutdown
		if errors.Is(err, ErrTriggerRestart) {
			fmt.Printf("Restarting component after fatal error: %v\n", err)
			triggerRestart()
			return
		} else {
			fmt.Printf("An unrecoverable error occurred: %v\n", err)
			// shutdown other components. it might also make sense to just panic here
			// depending on the circumstances
			cancel()
		}
	}
	// run the component. this is a blocking call, and will return with an error if the
	// first startup or any subsequent restart attempts fails or the context is canceled
	err := module.RunComponent(ctx, componentFactory, onError)
	if err != nil {
		fmt.Printf("Error returned from RunComponent: %v\n", err)
	}
}
// ExampleComponent is an example of a typical component
type ExampleComponent struct {
	id      int
	started chan struct{}
	ready   sync.WaitGroup
	done    sync.WaitGroup
}
func NewExampleComponent(id int) *ExampleComponent {
	return &ExampleComponent{
		id:      id,
		started: make(chan struct{}),
	}
}
// start the component and register its shutdown handler
// this component will throw an error after 20ms to demonstrate the error handling
func (c *ExampleComponent) Start(ctx irrecoverable.SignalerContext) {
	c.printMsg("Starting up")
	// do some setup...
	c.ready.Add(2)
	c.done.Add(2)
	go func() {
		c.ready.Done()
		defer c.done.Done()
		<-ctx.Done()
		c.printMsg("Shutting down")
		// do some cleanup...
	}()
	go func() {
		c.ready.Done()
		defer c.done.Done()
		select {
		case <-time.After(20 * time.Millisecond):
			// encounter irrecoverable error
			if c.id > 1 {
				ctx.Throw(ErrNoRestart)
			} else {
				ctx.Throw(ErrTriggerRestart)
			}
		case <-ctx.Done():
			c.printMsg("Cancelled by parent")
		}
	}()
	close(c.started)
}
// simply return the Started channel
// all startup processing is done in Start()
func (c *ExampleComponent) Ready() <-chan struct{} {
	ready := make(chan struct{})
	go func() {
		<-c.started
		c.ready.Wait()
		close(ready)
	}()
	return ready
}
// simply return the Stopped channel
// all shutdown processing is done in shutdownOnCancel()
func (c *ExampleComponent) Done() <-chan struct{} {
	done := make(chan struct{})
	go func() {
		<-c.started
		c.done.Wait()
		close(done)
	}()
	return done
}
func (c *ExampleComponent) printMsg(msg string) {
	fmt.Printf("[Component %d] %s\n", c.id, msg)
}
Output: [Component 1] Starting up [Component 1] Shutting down Restarting component after fatal error: restart me [Component 2] Starting up [Component 2] Shutting down An unrecoverable error occurred: fatal, no restarts Error returned from RunComponent: context canceled
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Throw ¶
If we have an SignalerContext, we can directly ctx.Throw.
But a lot of library methods expect context.Context, & we want to pass the same w/o boilerplate Moreover, we could have built with: context.WithCancel(irrecoverable.WithSignaler(ctx, sig)), "downcasting" to context.Context. Yet, we can still type-assert and recover.
Throw can be a drop-in replacement anywhere we have a context.Context likely to support Irrecoverables. Note: this is not a method
Types ¶
type Signaler ¶
type Signaler struct {
	// contains filtered or unexported fields
}
    Signaler sends the error out
func NewSignaler ¶
type SignalerContext ¶
type SignalerContext interface {
	context.Context
	Throw(err error) // delegates to the signaler
	// contains filtered or unexported methods
}
    We define a constrained interface to provide a drop-in replacement for context.Context including in interfaces that compose it.
func WithSignaler ¶
func WithSignaler(ctx context.Context, sig *Signaler) SignalerContext
the One True Way of getting a SignalerContext