fsm.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Package fsm implements a finite state machine with specific features:
  2. //
  3. // # Operation
  4. //
  5. // - All data types are parametric and checked: state key, event key, and event data
  6. // - Low-level (push) mode: clients call its Handle method, feeding events to these calls
  7. // - High-level (pull) mode: machine receives events from a Feed
  8. // This enables automatic progress on clock ticks, generating tick events which can be Transition triggers.
  9. // - Feed for high-level mode is injectable, defaulting to a channel.
  10. //
  11. // # Hooks
  12. //
  13. // - Events support guards and action hooks.
  14. // - States support enter and leave hooks
  15. // - Hooks have a unified signature
  16. //
  17. // # Event payloads
  18. //
  19. // - Events can carry a typed payload, available to all hooks.
  20. // - All machine types are parameterized on it, enabling strong typing.
  21. //
  22. // # Contexts
  23. //
  24. // - Machine carries a context enabling global cancellation
  25. // - Handlers all take a per-event context enabling individual cancellation
  26. // - Handler contexts should be either nil, which uses the machine context, or based on that context.
  27. package fsm
  28. import (
  29. "context"
  30. "time"
  31. )
  32. type Hook[K comparable, EV any] func(ctx context.Context, event Event[K, EV]) error
  33. type State[SK, EK comparable, EV any] interface {
  34. Name() SK
  35. SetAfterEnter([]Hook[EK, EV])
  36. AfterEnter() []Hook[EK, EV]
  37. SetBeforeLeave([]Hook[EK, EV])
  38. BeforeLeave() []Hook[EK, EV]
  39. }
  40. type Event[EK comparable, EV any] interface {
  41. Name() EK
  42. Data() EV
  43. }
  44. type RetryableError interface {
  45. error
  46. RetryableError()
  47. }
  48. type Transition[SK, EK comparable, EV any] interface {
  49. SetGuards([]Hook[EK, EV])
  50. Guards() []Hook[EK, EV]
  51. SetActions([]Hook[EK, EV])
  52. Actions() []Hook[EK, EV]
  53. Next() SK
  54. }
  55. // Matrix describes the set of Transition defining an FSM.
  56. type Matrix[SK, EK comparable, EV any] map[SK]map[EK]Transition[SK, EK, EV]
  57. // BackoffFunc returning <0 means the retry limit has been reached.
  58. type BackoffFunc func() time.Duration
  59. type FSM[SK, EK comparable, EV any] interface {
  60. Context() context.Context
  61. Name() string
  62. SetBackoff(fn BackoffFunc)
  63. // SetFeed allows the FSM to support pull mode.
  64. SetFeed(feed Feed[SK, EK, EV])
  65. // SetMatrix defines the Transition matrix on an FSM.
  66. SetMatrix(mx Matrix[SK, EK, EV])
  67. // SetOnUnavailable defines optional behaviour when receiving an event not accepted by the current state.
  68. // Default is OnErrorIgnore.
  69. SetOnUnavailable(OnError[SK, EK])
  70. // SetOnGuardFailure defines optional behaviour when a guard rejects a transition attempt.
  71. // Default is OnErrorReturn.
  72. SetOnGuardFailure(OnError[SK, EK])
  73. // SetOnActionFailure defines optional behaviour when failing an event action.
  74. // Default is OnErrorReturn.
  75. SetOnActionFailure(OnError[SK, EK])
  76. // SetOnEnterFailure defines optional behaviour when a state entry fails.
  77. // Default is OnErrorReturn.
  78. SetOnEnterFailure(OnError[SK, EK])
  79. // SetOnLeaveFailure defines optional behaviour when a state leave fails.
  80. // Default is OnErrorReturn.
  81. SetOnLeaveFailure(OnError[SK, EK])
  82. StartState() State[SK, EK, EV]
  83. EndState() State[SK, EK, EV]
  84. ErrorState() State[SK, EK, EV]
  85. }
  86. type PushMachine[SK, EK comparable, EV any] interface {
  87. FSM[SK, EK, EV]
  88. Handle(context.Context, Event[EK, EV]) error // If ctx == nil, handling uses the machine context.
  89. }
  90. type PullMachine[SK, EK comparable, EV any] interface {
  91. FSM[SK, EK, EV]
  92. Start(ctx context.Context, events chan<- Event[EK, EV]) State[SK, EK, EV]
  93. }
  94. var (
  95. BackoffInitialDelay = time.Millisecond
  96. BackoffMaxAttempts = 10
  97. DefaultBackoffAttempts = 0
  98. // NoBackoff retries without delay, but only up to the maximum number of retries.
  99. NoBackoff BackoffFunc = func() time.Duration {
  100. DefaultBackoffAttempts++
  101. if DefaultBackoffAttempts > BackoffMaxAttempts {
  102. return -1
  103. }
  104. return YoloBackoff()
  105. }
  106. // ExponentialBackoff retries with an exponential delay and maximum number of retries.
  107. ExponentialBackoff BackoffFunc = func() time.Duration {
  108. d := NoBackoff()
  109. if d < 0 {
  110. return d
  111. }
  112. return (1 << DefaultBackoffAttempts) * BackoffInitialDelay
  113. }
  114. // YoloBackoff retries indefinitely without delay. Not a good idea outside specific test cases.
  115. YoloBackoff BackoffFunc = func() time.Duration {
  116. return 0
  117. }
  118. )
  119. type Feed[SK, EK comparable, EV any] func(fsm FSM[SK, EK, EV]) Event[EK, EV]