spring-statemachine icon indicating copy to clipboard operation
spring-statemachine copied to clipboard

StateMachineRuntimePersister doesn't save/restore region states

Open holyhoehle opened this issue 5 years ago • 1 comments

I have the following StateMachine configuration:

@Configuration
open class PooledAppConfig {

    @Bean
    fun stateMachineRuntimePersister(
            jpaStateMachineRepository: JpaStateMachineRepository): StateMachineRuntimePersister<String, String, String> {
        return JpaPersistingStateMachineInterceptor<String, String, String>(jpaStateMachineRepository)
    }

    @Bean
    fun stateMachineTarget(stateMachineRuntimePersister: StateMachineRuntimePersister<String, String, String>): StateMachine<String, String> {
        val builder = StateMachineBuilder.builder<String, String>()
        builder.configureConfiguration()
                .withConfiguration()
                .machineId("ItemSM")

        builder.configureConfiguration()
                .withPersistence()
                .runtimePersister(stateMachineRuntimePersister)

        builder.configureStates()
                .withStates()
                .initial("Init")
                .state("S1")
                .fork("FRK")
                .state("GROUP")
                .join("JN")
                .and()
                .withStates()
                    .parent("GROUP")
                    .region("R1")
                    .initial("F1I")
                    .state("F1S")
                    .end("F1E")
                .and()
                .withStates()
                    .parent("GROUP")
                    .region("R2")
                    .initial("F2I")
                    .state("F2S")
                    .end("F2E")
                .and()
                .withStates()
                    .parent("GROUP")
                    .region("R3")
                    .initial("F3I")
                    .state("F3S")
                    .end("F3E")
                .and()
                .withStates()
                .state("END")

        builder.configureTransitions()
                .withExternal()
                    .source("Init").target("S1")
                    .event("E_IS1")
                    .and()
                .withExternal()
                    .source("S1").target("FRK")
                    .event("E_FRK")
                    .and()
                .withFork()
                    .source("FRK")
                    .target("GROUP")
                    .and()
                .withJoin()
                    .source("GROUP")
                    .target("JN")
                    .and()
                .withExternal()
                    .source("F1I").target("F1S")
                    .event("E_F1IS")
                    .and()
                .withExternal()
                    .source("F2I").target("F2S")
                    .event("E_F2IS")
                    .and()
                .withExternal()
                    .source("F3I").target("F3S")
                    .event("E_F3IS")
                    .and()
                .withExternal()
                    .source("F1S").target("F1E")
                    .event("E_F1SE")
                    .and()
                .withExternal()
                    .source("F2S").target("F2E")
                    .event("E_F2SE")
                    .and()
                .withExternal()
                    .source("F3S").target("F3E")
                    .event("E_F3SE")
                    .and()
                .withExternal()
                    .source("JN").target("END")

        return builder.build()
    }
}

And my controller looks like this:

@RestController
class StateMachineController {

    @Autowired
    private lateinit var stateMachine: StateMachine<String, String>

    @Autowired
    private lateinit var runtimePersister: StateMachineRuntimePersister<String, String, String>

    @GetMapping("/state")
    fun state(String, @RequestParam("event") event: String): String {
        resetStateMachineFromStore()
        feedStateMachine(event)
        return stateMachine.state.toString()
    }

    private fun resetStateMachineFromStore() {
        val context = runtimePersister.read("ItemSM")
        if (context != null) {
            stateMachine.stop()
            stateMachine.stateMachineAccessor.doWithRegion {
                it.resetStateMachine(context)
            }
            stateMachine.start()
        }
    }

    private fun feedStateMachine(event: String) {
        stateMachine.sendEvent(event)
    }
}

If I trigger the events in the following order the following SQL statements are being executed:

'E_IS1' -> UPDATE STATE_MACHINE SET STATE='S1', [...] WHERE MACHINE_ID='ItemSM' 

'E_FRK' -> UPDATE STATE_MACHINE SET STATE='GROUP', [...] WHERE MACHINE_ID='ItemSM'
          -> UPDATE STATE_MACHINE SET STATE='F1I', [...] WHERE MACHINE_ID='ItemSM#R1'
          -> UPDATE STATE_MACHINE SET STATE='F3I', [...] WHERE MACHINE_ID='ItemSM#R3'
          -> UPDATE STATE_MACHINE SET STATE='F2I', [...] WHERE MACHINE_ID='ItemSM#R2'

'E_F1IS' -> UPDATE STATE_MACHINE SET STATE='F1S', [...] WHERE MACHINE_ID='ItemSM'
'E_F2IS' -> UPDATE STATE_MACHINE SET STATE='F2S', [...] WHERE MACHINE_ID='ItemSM'

For the last two events (E_F1IS/E_F2IS) I would have expected the region rows ItemSM#R1 and ItemSM#R2 to get updated to F1S and F2S. Instead the main ItemSM row gets updated twice.

This leads to the problem that the state machine cannot be restored properly. The resulting state machine is loaded with state GROUP but the region states are all in the initial F1I,F2I,F3I states.

Is there a problem with my state machine config or what is the reason for this behavior?

holyhoehle avatar Aug 09 '19 15:08 holyhoehle

I have the exact same problem in version 3.0.1.

Any news about this?

michelsciortino avatar Jul 27 '21 22:07 michelsciortino