Skip to content

When I was learning to use state machines, I encountered an abnormality in state machine saving after using history. I'm not sure if it's a bug in the Spring State Machine or an issue with my configuration. #1193

@aohanhe

Description

@aohanhe

The following is a state machine for a fan that I created using spring spring-statemachine-starter 4.0. When using spring-statemachine-data-mongodb, I always get the error: "Cannot invoke "org.springframework.statemachine.state.State.getId()" because the return value of "org.springframework.statemachine.state.HistoryPseudoState.getState()" is null."
`package com.example.state_demo;

import java.util.EnumSet;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.configurers.StateConfigurer.History;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.persist.StateMachineRuntimePersister;
import org.springframework.statemachine.state.State;

@configuration
@EnableStateMachineFactory
public class FanStateMachineConfig extends StateMachineConfigurerAdapter<FanStates, FanEvents> {

@Autowired
private StateMachineRuntimePersister<FanStates, FanEvents, String> stateMachineRuntimePersister;

@Override
public void configure(StateMachineStateConfigurer<FanStates, FanEvents> states) throws Exception {
    states
        .withStates()
            .initial(FanStates.POWER_OFF)
            .states(EnumSet.of(FanStates.POWER_OFF, FanStates.POWER_ON))
            .and()
        .withStates()
            .parent(FanStates.POWER_ON)
            .initial(FanStates.STANDBY)
            .states(EnumSet.of(FanStates.STANDBY, FanStates.RUNNING))                 
            .and()
        .withStates()
            .parent(FanStates.RUNNING)
            .initial(FanStates.LEVEL_1)
            .states(EnumSet.of(FanStates.LEVEL_1, FanStates.LEVEL_2, FanStates.LEVEL_3))
            .history(FanStates.HISTORY,History.DEEP)                               
            ;
}

@Override
public void configure(StateMachineTransitionConfigurer<FanStates, FanEvents> transitions) throws Exception {
    transitions
    .withHistory()
        .source(FanStates.HISTORY)
        .target(FanStates.LEVEL_1)        
    .and()  
    .withExternal()
        .source(FanStates.POWER_OFF)
        .target(FanStates.POWER_ON)
        .event(FanEvents.POWER_ON_EVENT)
    .and()
    .withExternal()
        .source(FanStates.POWER_ON)
        .target(FanStates.POWER_OFF)
        .event(FanEvents.POWER_OFF_EVENT)
    .and()           
    
    .withExternal()
        .source(FanStates.STANDBY)
        .target(FanStates.HISTORY)  // 进入RUNNING时使用具体状态
        .event(FanEvents.TURN_ON_EVENT)
    .and()   
    .withExternal()
        .source(FanStates.RUNNING)
        .target(FanStates.STANDBY)
        .event(FanEvents.TURN_OFF_EVENT)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_1)
        .target(FanStates.LEVEL_2)
        .event(FanEvents.SPEED_LEVEL_2)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_1)
        .target(FanStates.LEVEL_3)
        .event(FanEvents.SPEED_LEVEL_3)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_2)
        .target(FanStates.LEVEL_1)
        .event(FanEvents.SPEED_LEVEL_1)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_2)
        .target(FanStates.LEVEL_3)
        .event(FanEvents.SPEED_LEVEL_3)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_3)
        .target(FanStates.LEVEL_1)
        .event(FanEvents.SPEED_LEVEL_1)
    .and()
    .withExternal()
        .source(FanStates.LEVEL_3)
        .target(FanStates.LEVEL_2)
        .event(FanEvents.SPEED_LEVEL_2);
       ;
}


@Override
public void configure(StateMachineConfigurationConfigurer<FanStates, FanEvents> config) throws Exception {
    config
        .withPersistence()
            .runtimePersister(stateMachineRuntimePersister)
        .and()
        .withConfiguration()
            .listener(listener())               
            ;

}

@Bean
public StateMachineListener<FanStates, FanEvents> listener() {
    return new StateMachineListenerAdapter<FanStates, FanEvents>() {
        @Override
        public void stateChanged(State<FanStates, FanEvents> from, State<FanStates, FanEvents> to) {
            
        }
    };
}

} `

I've pinpointed that the error occurs in the buildStateMachineContext method of AbstractPersistingStateMachineInterceptor.
if (historyState != null) { historyStates.put(null, ((HistoryPseudoState<S, E>)historyState).getState().getId()); }
The error occurs because the value of historyState.getState() is null. However, my state machine functions properly during testing—it's just that I can't save it. Could there be an error in my configuration? Below is my test code:
`@GetMapping("/test2/{id}")
public String test3(@PathVariable("id") String id) {
var stateMachine = stateMachineFactory.getStateMachine(id);
stateMachine.start();
stateMachine.sendEvent(FanEvents.POWER_ON_EVENT);
stateMachine.sendEvent(FanEvents.TURN_ON_EVENT);
stateMachine.sendEvent(FanEvents.SPEED_LEVEL_3);
stateMachine.sendEvent(FanEvents.POWER_OFF_EVENT);

	stateMachine.sendEvent(FanEvents.POWER_ON_EVENT);
	stateMachine.sendEvent(FanEvents.TURN_ON_EVENT);
	return stateMachine.getState().getIds().toString();

}`

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/need-triageTeam needs to triage and take a first look

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions