-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathstatemachine.go
More file actions
77 lines (65 loc) · 2.32 KB
/
Copy pathstatemachine.go
File metadata and controls
77 lines (65 loc) · 2.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Copyright (c) 2026 Onur Cinar.
// The source code is provided under MIT License.
// https://github.com/cinar/resile
package resile
import (
"context"
"sync"
)
// TransitionFunc defines the signature for a state transition function.
// It takes the current state, data, and event, and returns the next state and data.
// It also receives the current RetryState to allow transitions to adapt to failure history.
type TransitionFunc[S any, D any, E any] func(ctx context.Context, state S, data D, event E, rs RetryState) (S, D, error)
// StateMachine represents a resilient state machine inspired by Erlang's gen_statem.
// Every state transition is protected by the configured resilience policies.
type StateMachine[S any, D any, E any] struct {
mu sync.RWMutex
state S
data D
transition TransitionFunc[S, D, E]
options []Option
}
// NewStateMachine creates a new resilient state machine with the provided initial state, data, and transition function.
func NewStateMachine[S any, D any, E any](initialState S, initialData D, transition TransitionFunc[S, D, E], opts ...Option) *StateMachine[S, D, E] {
return &StateMachine[S, D, E]{
state: initialState,
data: initialData,
transition: transition,
options: opts,
}
}
// Handle processes an event and performs a state transition.
// The transition is executed within a resilience envelope (Retries, Circuit Breakers, etc.).
// If the transition succeeds, the state machine updates its internal state and data.
func (sm *StateMachine[S, D, E]) Handle(ctx context.Context, event E) error {
sm.mu.Lock()
defer sm.mu.Unlock()
type update struct {
state S
data D
}
result, err := DoState(ctx, func(ctx context.Context, rs RetryState) (update, error) {
nextState, nextData, err := sm.transition(ctx, sm.state, sm.data, event, rs)
if err != nil {
return update{}, err
}
return update{state: nextState, data: nextData}, nil
}, sm.options...)
if err == nil {
sm.state = result.state
sm.data = result.data
}
return err
}
// GetState returns the current state of the state machine.
func (sm *StateMachine[S, D, E]) GetState() S {
sm.mu.RLock()
defer sm.mu.RUnlock()
return sm.state
}
// GetData returns the current data of the state machine.
func (sm *StateMachine[S, D, E]) GetData() D {
sm.mu.RLock()
defer sm.mu.RUnlock()
return sm.data
}