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 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 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
}
We define 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)
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.