spring-projects / spring-statemachine

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

resetStateMachine method of the StateMachineAccessor fails when using reactive model #911

Closed fanaSA closed 3 years ago

fanaSA commented 3 years ago

I'm using Spring State Machine 3.0.0-RC1 and I'm trying to set create a State Machine instance and set it to a particular state using the following code:

public StateMachine<States, Events> getStateMachineInstance(String applicationNumber, States state) {
    final var stateMachine = this.stateMachineFactory.getStateMachine(applicationNumber);

    stateMachine.getStateMachineAccessor()
    .doWithAllRegions((stateMachineAccess) -> {
        stateMachineAccess.addStateMachineInterceptor(new StateMachineInterceptorAdapter<>() {
            @Override
            public void preStateChange(State<States, Events> state, Message<Events> message, Transition<States, Events> transition, StateMachine<States, Events> stateMachine, StateMachine<States, Events> rootStateMachine) {
                log.info("about to change state to: {}", state.getId().name());
            }

            @Override
            public void postStateChange(State<States, Events> state, Message<Events> message, Transition<States, Events> transition, StateMachine<States, Events> stateMachine, StateMachine<States, Events> rootStateMachine) {
                log.info("changed state to: {}", state.getId().name());
            }
        });
        log.info("Setting state to: {}", state);
        stateMachineAccess.resetStateMachine(new DefaultStateMachineContext<>(state, null, null, null));
    });
    stateMachine.startReactively().subscribe();
    return stateMachine;
}

When I call this method with a state other than the initial state (or null), it bombs out with an error on this code stateMachineAccess.resetStateMachine(new DefaultStateMachineContext<>(state, null, null, null));. The error is standard Reactor error caused by making a blocking call on a non-blocking thread see issue.

Looking through the code, it seems that somewhere in the resetMachineState method, there's a call to org.springframework.statemachine.support.LifecycleObjectSupport#start which delegates to startReactively().block().

Is there another way to create a State Machine instance and set it to a state other than the initial state without getting this error?

jvalkeal commented 3 years ago

You are right as I now see that call for a state lifecycle. Tests should have catch that so thanks for finding it out. As we still have blocking api's in place only a user should call those, machine internals in this part should be non-blocking.

fanaSA commented 3 years ago

@jvalkeal thank you for the feedback. Not quite sure how feasible this suggestion is but I can attempt to fix this issue if it is logged and tracked

jvalkeal commented 3 years ago

Let me first check that what actually needs to be done. I think offending code for this is:

https://github.com/spring-projects/spring-statemachine/blob/81801efecd102aba30ad5f739be166ba86ab5b8f/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java#L851-L853

fanaSA commented 3 years ago

@jvalkeal I've switched to persisting the State Machine following this example and the DefaultStateMachineService has the same LifeCycleObjectSupport issue here: https://github.com/spring-projects/spring-statemachine/blob/81801efecd102aba30ad5f739be166ba86ab5b8f/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java#L164-L167

and here:

https://github.com/spring-projects/spring-statemachine/blob/81801efecd102aba30ad5f739be166ba86ab5b8f/spring-statemachine-core/src/main/java/org/springframework/statemachine/service/DefaultStateMachineService.java#L175-L178

jvalkeal commented 3 years ago

Not sure why BlockHound with tests didn't catch these but found a way to make a failing test(manually running in nonblocking thread). There's one commit in this branch https://github.com/jvalkeal/spring-statemachine/tree/gh911-work1.

If you want to try fixing this then it would basically involve adding reactive counterpart to StateMachineAccess.resetStateMachine():void like StateMachineAccess.resetStateMachineReactively():Mono<Void>.

fanaSA commented 3 years ago

Thank you @jvalkeal, I'll get to it now

slice-sagarg commented 1 year ago

After resetStateMachine method id of machine set to null. Why this is happening?