123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- // Package fsm implements a finite state machine with specific features:
- //
- // # Operation
- //
- // - All data types are parametric and checked: state key, event key, and event data
- // - Low-level (push) mode: clients call its Handle method, feeding events to these calls
- // - High-level (pull) mode: machine receives events from a Feed
- // This enables automatic progress on clock ticks, generating tick events which can be Transition triggers.
- // - Feed for high-level mode is injectable, defaulting to a channel.
- //
- // # Hooks
- //
- // - Events support guards and action hooks.
- // - States support enter and leave hooks
- // - Hooks have a unified signature
- //
- // # Event payloads
- //
- // - Events can carry a typed payload, available to all hooks.
- // - All machine types are parameterized on it, enabling strong typing.
- //
- // # Contexts
- //
- // - Machine carries a context enabling global cancellation
- // - Handlers all take a per-event context enabling individual cancellation
- // - Handler contexts should be either nil, which uses the machine context, or based on that context.
- package fsm
- import (
- "context"
- "time"
- )
- type Hook[K comparable, EV any] func(ctx context.Context, event Event[K, EV]) error
- type State[SK, EK comparable, EV any] interface {
- Name() SK
- SetAfterEnter([]Hook[EK, EV])
- AfterEnter() []Hook[EK, EV]
- SetBeforeLeave([]Hook[EK, EV])
- BeforeLeave() []Hook[EK, EV]
- }
- type Event[EK comparable, EV any] interface {
- Name() EK
- Data() EV
- }
- type RetryableError interface {
- error
- RetryableError()
- }
- type Transition[SK, EK comparable, EV any] interface {
- SetGuards([]Hook[EK, EV])
- Guards() []Hook[EK, EV]
- SetActions([]Hook[EK, EV])
- Actions() []Hook[EK, EV]
- Next() SK
- }
- // Matrix describes the set of Transition defining an FSM.
- type Matrix[SK, EK comparable, EV any] map[SK]map[EK]Transition[SK, EK, EV]
- // BackoffFunc returning <0 means the retry limit has been reached.
- type BackoffFunc func() time.Duration
- type FSM[SK, EK comparable, EV any] interface {
- Context() context.Context
- Name() string
- SetBackoff(fn BackoffFunc)
- // SetFeed allows the FSM to support pull mode.
- SetFeed(feed Feed[SK, EK, EV])
- // SetMatrix defines the Transition matrix on an FSM.
- SetMatrix(mx Matrix[SK, EK, EV])
- // SetOnUnavailable defines optional behaviour when receiving an event not accepted by the current state.
- // Default is OnErrorIgnore.
- SetOnUnavailable(OnError[SK, EK])
- // SetOnGuardFailure defines optional behaviour when a guard rejects a transition attempt.
- // Default is OnErrorReturn.
- SetOnGuardFailure(OnError[SK, EK])
- // SetOnActionFailure defines optional behaviour when failing an event action.
- // Default is OnErrorReturn.
- SetOnActionFailure(OnError[SK, EK])
- // SetOnEnterFailure defines optional behaviour when a state entry fails.
- // Default is OnErrorReturn.
- SetOnEnterFailure(OnError[SK, EK])
- // SetOnLeaveFailure defines optional behaviour when a state leave fails.
- // Default is OnErrorReturn.
- SetOnLeaveFailure(OnError[SK, EK])
- StartState() State[SK, EK, EV]
- EndState() State[SK, EK, EV]
- ErrorState() State[SK, EK, EV]
- }
- type PushMachine[SK, EK comparable, EV any] interface {
- FSM[SK, EK, EV]
- Handle(context.Context, Event[EK, EV]) error // If ctx == nil, handling uses the machine context.
- }
- type PullMachine[SK, EK comparable, EV any] interface {
- FSM[SK, EK, EV]
- Start(ctx context.Context, events chan<- Event[EK, EV]) State[SK, EK, EV]
- }
- var (
- BackoffInitialDelay = time.Millisecond
- BackoffMaxAttempts = 10
- DefaultBackoffAttempts = 0
- // NoBackoff retries without delay, but only up to the maximum number of retries.
- NoBackoff BackoffFunc = func() time.Duration {
- DefaultBackoffAttempts++
- if DefaultBackoffAttempts > BackoffMaxAttempts {
- return -1
- }
- return YoloBackoff()
- }
- // ExponentialBackoff retries with an exponential delay and maximum number of retries.
- ExponentialBackoff BackoffFunc = func() time.Duration {
- d := NoBackoff()
- if d < 0 {
- return d
- }
- return (1 << DefaultBackoffAttempts) * BackoffInitialDelay
- }
- // YoloBackoff retries indefinitely without delay. Not a good idea outside specific test cases.
- YoloBackoff BackoffFunc = func() time.Duration {
- return 0
- }
- )
- type Feed[SK, EK comparable, EV any] func(fsm FSM[SK, EK, EV]) Event[EK, EV]
|