|
@@ -0,0 +1,100 @@
|
|
|
+package fsm
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+type M2Hook[T any] func(ctx context.Context, event M2Event[T]) error
|
|
|
+
|
|
|
+type M2State[T any] interface {
|
|
|
+ Name() string
|
|
|
+ SetAfterEnter([]M2Hook[T])
|
|
|
+ AfterEnter() []M2Hook[T]
|
|
|
+ SetBeforeLeave([]M2Hook[T])
|
|
|
+ BeforeLeave() []M2Hook[T]
|
|
|
+}
|
|
|
+
|
|
|
+type M2Event[T any] interface {
|
|
|
+ Name() string
|
|
|
+ Data() T
|
|
|
+}
|
|
|
+
|
|
|
+type RetryableError interface {
|
|
|
+ error
|
|
|
+ RetryableError()
|
|
|
+}
|
|
|
+
|
|
|
+type M2Transition[T any] struct {
|
|
|
+ guards, actions []M2Hook[T]
|
|
|
+ next State
|
|
|
+}
|
|
|
+
|
|
|
+func (mt *M2Transition[T]) SetGuards([]M2Hook[T]) {}
|
|
|
+func (mt *M2Transition[T]) Guards() []M2Hook[T] { return mt.guards }
|
|
|
+func (mt *M2Transition[T]) SetActions([]M2Hook[T]) {}
|
|
|
+func (mt *M2Transition[T]) Actions() []M2Hook[T] { return mt.actions }
|
|
|
+func (mt *M2Transition[T]) Next() State { return mt.next }
|
|
|
+
|
|
|
+type M2Matrix[T any] map[M2State[T]]map[M2Event[T]]M2Transition[T]
|
|
|
+
|
|
|
+// M2BackoffFunc returning <0 means the retry limit has been reached.
|
|
|
+type M2BackoffFunc func() time.Duration
|
|
|
+
|
|
|
+type M2FSM[T any] interface {
|
|
|
+ Name() string
|
|
|
+
|
|
|
+ SetBackoff(fn M2BackoffFunc)
|
|
|
+
|
|
|
+ SetMatrix(mx M2Matrix[T])
|
|
|
+ Matrix() M2Matrix[T]
|
|
|
+
|
|
|
+ StartState() M2State[T]
|
|
|
+ EndState() M2State[T]
|
|
|
+ ErrorState() M2State[T]
|
|
|
+}
|
|
|
+
|
|
|
+type M2PushMachine[T any] interface {
|
|
|
+ M2FSM[T]
|
|
|
+ Handle(context.Context, M2Event[T]) error // If ctx == nil, handling uses the machine context.
|
|
|
+}
|
|
|
+
|
|
|
+type M2PullMachine[T any] interface {
|
|
|
+ M2FSM[T]
|
|
|
+ Start(ctx context.Context, events chan<- M2Event[T]) M2State[T]
|
|
|
+}
|
|
|
+
|
|
|
+type M2VerifiableMachine[T any] interface {
|
|
|
+ M2FSM[T]
|
|
|
+ IsReachable(s1, s2 M2State[T]) bool
|
|
|
+ IsStronglyConnected() bool // Minimal Complexity O(V+E) using DFS (Tarjan or Kosaraju)
|
|
|
+}
|
|
|
+
|
|
|
+var (
|
|
|
+ m2BackoffInitialDelay = time.Millisecond
|
|
|
+ m2BackoffMaxAttempts = 10
|
|
|
+ m2DefaultBackoffAttempts = 0
|
|
|
+
|
|
|
+ // M2YoloBackoff retries indefinitely without delay.
|
|
|
+ M2YoloBackoff M2BackoffFunc = func() time.Duration {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // M2NoBackoff retries immediately but only up to the maximum number of retries.
|
|
|
+ M2NoBackoff M2BackoffFunc = func() time.Duration {
|
|
|
+ m2DefaultBackoffAttempts++
|
|
|
+ if m2DefaultBackoffAttempts > m2BackoffMaxAttempts {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ return M2YoloBackoff()
|
|
|
+ }
|
|
|
+
|
|
|
+ // M2ExponentialBackoff retries with an exponential delay and maximum number of retries.
|
|
|
+ M2ExponentialBackoff M2BackoffFunc = func() time.Duration {
|
|
|
+ d := M2NoBackoff()
|
|
|
+ if d < 0 {
|
|
|
+ return d
|
|
|
+ }
|
|
|
+ return (1 << m2DefaultBackoffAttempts) * m2BackoffInitialDelay
|
|
|
+ }
|
|
|
+)
|