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
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
NewExceptionf is NewException with the ability to add formatting and context to the error text.
func Throw ¶
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
func NewMockSignalerContext ¶ added in v0.28.0
func NewMockSignalerContext(t *testing.T, ctx context.Context) *MockSignalerContext
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 ¶
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.