spring-projects / spring-statemachine

Spring Statemachine is a framework for application developers to use state machine concepts with Spring.
1.52k stars 598 forks source link

State Machine blocked on "Stopping" State after calling stopReactively #1065

Open fredjoseph opened 1 year ago

fredjoseph commented 1 year ago

Hi,

I currently have a problem when stopping a state machine that is already on an end state. If the configuration specified a state to be an end state, a call to the stopReactively method will update the reactiveLifecycleManager state to STOPPING instead of STOPPED.

If I try to start again this State Machine (after reseting the State Machine with a new context), the reactiveLifecycleManager state will be kept to STOPPING instead of STARTED

@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> {

    @Override
    public void configure(StateMachineStateConfigurer<States, Events> states)
            throws Exception {
        states.withStates()
        .initial(States.S0)
                .state(States.S0)
                .end(States.S1);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
            throws Exception {
        transitions
                .withExternal()
                        .source(States.S0).target(States.S1).event(Events.I);
    }
}
@Test
public void testResetWithEnumToCorrectStartState() throws Exception {
    context.register(Config1.class);
    context.refresh();
    StateMachine<States, Events> machine = resolveMachine(context);

    doStartAndAssert(machine);
    assertThat(machine.getState().getIds()).containsOnly(States.S0);

    doSendEventAndConsumeAll(machine, Events.I);
    assertThat(machine.getState().getIds()).containsOnly(States.S1);

    doStopAndAssert(machine);
    DefaultStateMachineContext<States, Events> stateMachineContext = new DefaultStateMachineContext<States, Events>(
            States.S11, null, null, null);
    machine.getStateMachineAccessor()
            .doWithAllRegions(function -> function.resetStateMachineReactively(stateMachineContext).block());

    // Here the manager state is 'STOPPING'
    doStartAndAssert(machine);
    // Here the manager state is still 'STOPPING'
}

As a workaround, I can replace the line .end(States.S1) by .state(States.S1), but in this case what is the purpose to mark a state as end state ? Do you have any examples where the end method is still useful ?

lbilger commented 1 year ago

I have the same problem, though it took me a lot of debugging to realize this is what's happening. Debugging some more, I think it is caused by some interaction between the lifecycle of the StateMachine itself and the lifecycle of the StateMachineExecutor. It's really hard to see through hundreds of lines of Reactor stack traces, but I think what is happening is this:

It seems this makes a state machine instance completely unusable if it enters the end state once.

lbilger commented 1 year ago

I think I can work around this by using a StateMachineFactory and creating a new state machine instance instead of reusing the existing one, but still this looks like a bug to me.

yonigev commented 1 year ago

Same issue here, This removes the ability to pool statemachine instances (or reuse state machine instances in general).