irrecoverable

package
v0.31.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2023 License: AGPL-3.0 Imports: 7 Imported by: 21

Documentation

Overview

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/onflow/flow-go/module/component"
	"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())
	defer cancel()

	// component.ComponentFactory encapsulates all of the component building logic
	// required before running Start()
	starts := 0
	componentFactory := func() (component.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) component.ErrorHandlingResult {
		// 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)
			return component.ErrorHandlingRestart
		} else {
			fmt.Printf("An irrecoverable error occurred: %v\n", err)
			// shutdown other components. it might also make sense to just panic here
			// depending on the circumstances
			return component.ErrorHandlingStop
		}
	}

	// 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 := component.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 irrecoverable error occurred: fatal, no restarts
Error returned from RunComponent: fatal, no restarts

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewException added in v0.30.0

func NewException(err error) error

NewException wraps the input error as an exception, stripping any sentinel error information. This ensures that all upper levels in the stack will consider this an unexpected error.

func NewExceptionf added in v0.30.0

func NewExceptionf(msg string, args ...any) error

NewExceptionf is NewException with the ability to add formatting and context to the error text.

func Throw

func Throw(ctx context.Context, err error)

Throw enables throwing an irrecoverable error using any context.Context.

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 MockSignalerContext added in v0.28.0

type MockSignalerContext struct {
	context.Context
	// contains filtered or unexported fields
}

MockSignalerContext is a SignalerContext which will immediately fail a test if an error is thrown.

func NewMockSignalerContext added in v0.28.0

func NewMockSignalerContext(t *testing.T, ctx context.Context) *MockSignalerContext

func NewMockSignalerContextWithCancel added in v0.31.0

func NewMockSignalerContextWithCancel(t *testing.T, parent context.Context) (*MockSignalerContext, context.CancelFunc)

func (MockSignalerContext) Throw added in v0.28.0

func (m MockSignalerContext) Throw(err error)

type Signaler

type Signaler struct {
	// contains filtered or unexported fields
}

Signaler sends the error out.

func NewSignaler

func NewSignaler() (*Signaler, <-chan error)

func (*Signaler) Throw

func (s *Signaler) Throw(err error)

Throw is a narrow drop-in replacement for panic, log.Fatal, log.Panic, etc anywhere there's something connected to the error channel. It only sends the first error it is called with to the error channel, and logs subsequent errors as unhandled.

type SignalerContext

type SignalerContext interface {
	context.Context
	Throw(err error) // delegates to the signaler
	// contains filtered or unexported methods
}

SignalerContext is a constrained interface to provide a drop-in replacement for context.Context including in interfaces that compose it.

func WithSignaler

func WithSignaler(parent context.Context) (SignalerContext, <-chan error)

WithSignaler is the One True Way of getting a SignalerContext.

func WithSignallerAndCancel added in v0.29.0

func WithSignallerAndCancel(ctx context.Context) (SignalerContext, context.CancelFunc, <-chan error)

WithSignallerAndCancel returns an irrecoverable context, the cancel function for the context, and the error channel for the context.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL