1
1
package statemachine
2
2
3
3
import (
4
+ "context"
4
5
"fmt"
5
6
"slices"
6
7
"sync"
7
- )
8
-
9
- // State represents the possible states of the install process
10
- type State string
11
8
12
- var (
13
- _ Interface = & stateMachine {}
9
+ "github.com/replicatedhq/embedded-cluster/api/pkg/logger"
10
+ "github.com/sirupsen/logrus"
14
11
)
15
12
16
- // Interface is the interface for the state machine
17
- type Interface interface {
18
- // CurrentState returns the current state
19
- CurrentState () State
20
- // IsFinalState checks if the current state is a final state
21
- IsFinalState () bool
22
- // ValidateTransition checks if a transition from the current state to a new state is valid
23
- ValidateTransition (lock Lock , newState State ) error
24
- // Transition attempts to transition to a new state and returns an error if the transition is
25
- // invalid.
26
- Transition (lock Lock , nextState State ) error
27
- // AcquireLock acquires a lock on the state machine.
28
- AcquireLock () (Lock , error )
29
- // IsLockAcquired checks if a lock already exists on the state machine.
30
- IsLockAcquired () bool
31
- }
32
-
33
- type Lock interface {
34
- // Release releases the lock.
35
- Release ()
36
- }
37
-
38
13
// stateMachine manages the state transitions for the install process
39
14
type stateMachine struct {
40
15
currentState State
41
16
validStateTransitions map [State ][]State
42
17
lock * lock
43
18
mu sync.RWMutex
19
+ eventHandlers map [State ][]EventHandler
20
+ logger logrus.FieldLogger
44
21
}
45
22
23
+ // StateMachineOption is a configurable state machine option.
24
+ type StateMachineOption func (* stateMachine )
25
+
46
26
// New creates a new state machine starting in the given state with the given valid state
47
- // transitions.
48
- func New (currentState State , validStateTransitions map [State ][]State ) * stateMachine {
49
- return & stateMachine {
27
+ // transitions and options .
28
+ func New (currentState State , validStateTransitions map [State ][]State , opts ... StateMachineOption ) * stateMachine {
29
+ sm := & stateMachine {
50
30
currentState : currentState ,
51
31
validStateTransitions : validStateTransitions ,
32
+ logger : logger .NewDiscardLogger (),
33
+ eventHandlers : make (map [State ][]EventHandler ),
34
+ }
35
+
36
+ for _ , opt := range opts {
37
+ opt (sm )
38
+ }
39
+
40
+ return sm
41
+ }
42
+
43
+ func WithLogger (logger logrus.FieldLogger ) StateMachineOption {
44
+ return func (sm * stateMachine ) {
45
+ sm .logger = logger
52
46
}
53
47
}
54
48
@@ -109,9 +103,13 @@ func (sm *stateMachine) ValidateTransition(lock Lock, nextState State) error {
109
103
return nil
110
104
}
111
105
112
- func (sm * stateMachine ) Transition (lock Lock , nextState State ) error {
106
+ func (sm * stateMachine ) Transition (lock Lock , nextState State ) ( finalError error ) {
113
107
sm .mu .Lock ()
114
- defer sm .mu .Unlock ()
108
+ defer func () {
109
+ if finalError != nil {
110
+ sm .mu .Unlock ()
111
+ }
112
+ }()
115
113
116
114
if sm .lock == nil {
117
115
return fmt .Errorf ("lock not acquired" )
@@ -123,11 +121,43 @@ func (sm *stateMachine) Transition(lock Lock, nextState State) error {
123
121
return fmt .Errorf ("invalid transition from %s to %s" , sm .currentState , nextState )
124
122
}
125
123
124
+ fromState := sm .currentState
126
125
sm .currentState = nextState
127
126
127
+ // Trigger event handlers after successful transition
128
+ handlers , exists := sm .eventHandlers [nextState ]
129
+ safeHandlers := make ([]EventHandler , len (handlers ))
130
+ copy (safeHandlers , handlers ) // Copy to avoid holding the lock while calling handlers
131
+
132
+ // We can release the lock here since the transition is successful and there will be no further operations to the state machine internal state
133
+ sm .mu .Unlock ()
134
+
135
+ if ! exists || len (safeHandlers ) == 0 {
136
+ return nil
137
+ }
138
+
139
+ for _ , handler := range safeHandlers {
140
+ err := handler .TriggerHandler (context .Background (), fromState , nextState )
141
+ if err != nil {
142
+ sm .logger .WithFields (logrus.Fields {"fromState" : fromState , "toState" : nextState }).Errorf ("event handler error: %v" , err )
143
+ }
144
+ }
145
+
128
146
return nil
129
147
}
130
148
149
+ func (sm * stateMachine ) RegisterEventHandler (targetState State , handler EventHandlerFunc , options ... EventHandlerOption ) {
150
+ sm .mu .Lock ()
151
+ defer sm .mu .Unlock ()
152
+ sm .eventHandlers [targetState ] = append (sm .eventHandlers [targetState ], NewEventHandler (handler , options ... ))
153
+ }
154
+
155
+ func (sm * stateMachine ) UnregisterEventHandler (targetState State ) {
156
+ sm .mu .Lock ()
157
+ defer sm .mu .Unlock ()
158
+ delete (sm .eventHandlers , targetState )
159
+ }
160
+
131
161
func (sm * stateMachine ) isValidTransition (currentState State , newState State ) bool {
132
162
validTransitions , ok := sm .validStateTransitions [currentState ]
133
163
if ! ok {
0 commit comments