spring-projects / spring-statemachine

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

resetStateMachineReactivly() not working on completed stateMachine #966

Open Maxlmore opened 3 years ago

Maxlmore commented 3 years ago

Hi,

I'm trying to implement a statemachine with redis persitence similar to the "Persisting a statemachine" example from the docs. I have four states SI -> S0 -> S1 -> SF which are conntected by one Event, NEXT.

I use a function feedMachine to send Events to the statemachine, which also checks weather a machine is completed or not:

My Controller:

    @GetMapping("/event")
    @ResponseBody
    public String event(@RequestParam(value = "eventName") String eventName,
                        HttpServletRequest httpServletRequest) throws Exception {
        resetStateMachineFromStore(httpServletRequest.getSession().getId());
        feedMachine(Mono.just(
                MessageBuilder.withPayload(Events.valueOf(eventName)).build()),
                httpServletRequest.getSession().getId())
                .subscribe();
        return convertToJson(buildGetStateResponse());
    }

    private Flux<StateMachineEventResult<States, Events>> feedMachine(Mono<Message<Events>> message, String identifier) {
        return stateMachine.sendEvent(message)
                .doOnComplete(() -> {
                    try {
                        if (stateMachine.getState().getPseudoState() != null && stateMachine.getState().getPseudoState().getKind() == PseudoStateKind.END) {
                            redisTemplate.delete("spring:session:statemachine:" + identifier);
                        } else {
                            stateMachinePersister.persist(stateMachine, "spring:session:statemachine:" + identifier);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
    }

    private void resetStateMachineFromStore(String identifier) throws Exception {
        stateMachinePersister.restore(stateMachine, "spring:session:statemachine:" + identifier);
    }

Expected behavior

  1. From a fresh browser window, sending the first event will result in a Got null context, resetting to initial state, clearing extended state and machine id and a new entry in redis, the controller returns state S0 (SI -> S0)
  2. After calling localhost:8080/event?eventName=NEXT two more times, I reach my end state .end(States.SF)
  3. The stateMachine gets deleted from redis via redisTemplate.delete("spring:session:statemachine:" + identifier);
  4. Upon sending the next event, stateMachinePersister will get context null from the db (as no statemachine with this sessionId can be found) and repeat from Step 1

Actual behavior

In Step 4, the statemachine is successfully reset internally using

        stateMachine.getStateMachineAccessor().doWithAllRegions((function) -> {
            Void var10000 = (Void)function.resetStateMachineReactively(context).block();
        });

and also is persisted into redis, but the event is not being processed and the machine will still be in state SI. Debugging it will return isComplete = false, so I dont see any reason why the event would fail now.

I've uploaded a minimal failing example on my private git here which requires a redis db on localhost:6379.

Sidenote: "Resetting" a session by deleting it from the db while its not in a end state (for example because spring session would expires) via

public class MySessionEventListener {

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @EventListener
    public void sessionExpired(SessionExpiredEvent event) {
            redisTemplate.delete("spring:session:statemachine:" + event.getSessionId());
        }
    }
}

works flawlessly.