diff --git a/statemachine.go b/statemachine.go index fcbc2bb..99e4c2a 100644 --- a/statemachine.go +++ b/statemachine.go @@ -354,6 +354,10 @@ func (sm *StateMachine) internalFireOne(ctx context.Context, trigger Trigger, ar err = sm.handleTransitioningTrigger(ctx, representativeState, transition, args...) } case *transitioningTriggerBehaviour: + if source == t.Destination { + // If a trigger was found on a superstate that would cause unintended reentry, don't trigger. + break + } transition := Transition{Source: source, Destination: t.Destination, Trigger: trigger} err = sm.handleTransitioningTrigger(ctx, representativeState, transition, args...) case *internalTriggerBehaviour: diff --git a/statemachine_test.go b/statemachine_test.go index 77b6a87..a89718e 100644 --- a/statemachine_test.go +++ b/statemachine_test.go @@ -1612,3 +1612,28 @@ func assertPanic(t *testing.T, f func()) { }() f() } + +func TestStateMachineWhenInSubstate_TriggerSuperStateTwiceToSameSubstate_DoesNotReenterSubstate(t *testing.T) { + sm := NewStateMachine(stateA) + var eCount = 0 + + sm.Configure(stateB). + OnEntry(func(_ context.Context, _ ...any) error { + eCount++ + return nil + }). + SubstateOf(stateC) + + sm.Configure(stateA). + SubstateOf(stateC) + + sm.Configure(stateC). + Permit(triggerX, stateB) + + sm.Fire(triggerX) + sm.Fire(triggerX) + + if eCount != 1 { + t.Errorf("expected 1, got %d", eCount) + } +}