spring-projects / spring-statemachine

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

StateMachinePersister is working inconsistently with orthogonal regions #1150

Open manhnt217 opened 6 months ago

manhnt217 commented 6 months ago

Hi, I'm new to Spring state machine.

Recently I'm testing persistence support of the framework and I found this issue.

My testing is pretty much similar to this example Using StateMachinePersister except that I'm using a state machine with 2 orthogonal regions (named LEFT, RIGHT). It looks like below

State diagram

I first start machine1 and then send these events: START, EL1, EL2 (in this order) to it. Now machine1.getState().getIds() give me the result [LR, L1, R1] (which is correct).

Then after transfering (persist/restore) the state of machine1 into machine2, I check the state of machine2 and receive the result [LR, L1, R1] (which is still correct).

Then I send ER1 to both machines then check the results. I realize that they are different:

I don't know which one (machine1 or machine1) behave correctly (because I was asking this question a few days ago on stackoverflow . But it seems like the persister did not "fully transfer" the state from machine1 to machine2.

Below is the code that I am using:

        machine1.startReactively().block();

        machine1.sendEvents(Flux.just(
                SMConfig.MEvent.START,
                SMConfig.MEvent.EL1,
                SMConfig.MEvent.EL2
        ).map(p -> MessageBuilder.withPayload(p).build())).blockLast();

        log.info("---- SM1 state = {}", machine1.getState().getIds());

        persister.persist(machine1, "myid");
        persister.restore(machine2, "myid");

        log.info("SM2 state = {}", machine2.getState().getIds());

        machine1.sendEvents(Flux.just(
                SMConfig.MEvent.ER1
        ).map(p -> MessageBuilder.withPayload(p).build())).blockLast();

        log.info("---- SM1 state = {}", machine1.getState().getIds());

        machine2.sendEvents(Flux.just(
                SMConfig.MEvent.ER1
        ).map(p -> MessageBuilder.withPayload(p).build())).blockLast();

        log.info("SM2 state = {}", machine2.getState().getIds());

and the machine configuration

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states.withStates()
                .initial(I)
                .fork(FORK)
                .state(LR)
                .join(JOIN)
                .state(E)
                .and().withStates().parent(LR)
                    .region("LEFT")
                        .initial(R1)
                        .state(R2)
                .and().withStates().parent(LR)
                    .region("RIGHT")
                        .initial(L1)
                        .state(L2);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
        transitions
                        .withExternal()          .source(I)            .target(FORK)              .event(START)
                .and()
                        .withFork()              .source(FORK)             .target(LR)
                .and()
                        .withExternal()          .source(L1)            .target(L2)             .event(EL1)
                .and()
                        .withExternal()          .source(L2)            .target(L1)             .event(EL2)
                .and()
                        .withExternal()          .source(R1)            .target(R2)             .event(ER1)
                .and()
                        .withExternal()          .source(R2)            .target(R1)             .event(ER2)
                .and()
                        .withJoin()              .source(R2).source(L2) .target(JOIN)
                .and()
                        .withExternal()          .source(JOIN)             .target(E)
                .and()
                        .withExternal()          .source(E)             .target(I)             .event(RESET)
                ;
    }

Thank you for your time!

manhnt217 commented 6 months ago

Debugging into the code, I see that JoinTracker does not get populated the correct state if the state machine is being restored from persistent context. Or in another word, the state of JoinTracker doesn't get persisted.