package state import ( "context" "log" "time" "alin.ovh/erl/command" ) type State int const ( NotStarted State = iota Running Stopping Exited ) func (s State) String() string { switch s { case NotStarted: return "NotStarted" case Running: return "Running" case Stopping: return "Stopping" case Exited: return "Exited" default: return "Unknown" } } type Event int const ( Start Event = iota Shutdown Exit Restart ) func (e Event) String() string { switch e { case Start: return "Start" case Shutdown: return "Shutdown" case Exit: return "Stopped" case Restart: return "Restart" default: return "Unknown" } } type Action func() type StateMachine struct { log *log.Logger timeout time.Duration currentState State transitions map[State]map[Event]State actions map[State]map[Event]Action } const DefaultTimeout = 1 * time.Second type Options struct { Timeout time.Duration Logger *log.Logger } func New(cmd command.Command, opts Options) *StateMachine { if opts.Timeout == 0 { opts.Timeout = DefaultTimeout } log := opts.Logger sm := &StateMachine{ log: opts.Logger, timeout: opts.Timeout, currentState: NotStarted, transitions: make(map[State]map[Event]State), actions: make(map[State]map[Event]Action), } sm.transitions[NotStarted] = map[Event]State{ Start: Running, } sm.transitions[Running] = map[Event]State{ Exit: Exited, Shutdown: Stopping, Restart: Stopping, } sm.transitions[Stopping] = map[Event]State{ Shutdown: Exited, Exit: Exited, } sm.transitions[Exited] = map[Event]State{ Start: Running, Restart: Running, } var waiting chan struct{} wait := func(cmd command.Command) { err := cmd.Wait() if err != nil { log.Printf("Error waiting for command: %v\n", err) } sm.SendEvent(Exit) close(waiting) } start := func() { waiting = make(chan struct{}, 1) err := cmd.Start() if err != nil { log.Printf("Failed to start command: %v", err) } go wait(cmd) } stop := func() { ctx, cancel := context.WithTimeout(context.Background(), sm.timeout) defer cancel() err := cmd.Stop(ctx) if err != nil { log.Printf("Error stopping command: %v\n", err) } } restart := func() { stop() <-waiting sm.SendEvent(Restart) } sm.actions[NotStarted] = map[Event]Action{ Start: start, } sm.actions[Running] = map[Event]Action{ Shutdown: stop, Restart: restart, } sm.actions[Exited] = map[Event]Action{ Start: start, Restart: start, } return sm } func (sm *StateMachine) SendEvent(ev Event) { currentState := sm.currentState if nextState, ok := sm.transitions[currentState][ev]; ok { sm.currentState = nextState if action, ok := sm.actions[currentState][ev]; ok { action() } } }