Joergi
Joergi

Reputation: 1593

(UML) Spring-Statemachine is still running after a statemachine.stop()

I have a guard in my Spring-Statemachine.

@Bean
public Guard<String, String> myGuard() {
    return context -> {
         try {
            String x = null;
            x.length();
            return true;
         } catch (Exception e) {
             context.getStateMachine().setStateMachineError(e);
             context.getStateMachine().stop();
             throw e;
         }
     };
}

This will of course be a java.lang.NullPointerException.
So, after I make a context.getStateMachine().stop(); I was expecting, that the statemachine is stopping.

2017-11-24 18:02:55.783  INFO 30652 --- [nio-8080-exec-1] o.s.s.support.LifecycleObjectSupport     : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@367d6b09

So, it stopped. But than all other guards and states and actions are called afterwards.

But when an error appeared, I definitely don't want the guard just switch to false, I really want to exit the machine.

There is only one region. So, as far as I understood it, it should just stop.

Is there any way to handle this? Or is there any way to call something like an "Exit event" when this is happening?

I'm thankful for any ideas how to deal with this.

Using:

spring-boot: 1.5.8
spring-statemachine-uml 1.2.6

Upvotes: 0

Views: 2348

Answers (2)

Joergi
Joergi

Reputation: 1593

I'm catching now all errors with this SpringAOP function:

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;    
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class ErrorInterceptor {

    @Autowired
    private StateMachine<String, String> myStateMachine;

    @AfterThrowing(pointcut = "execution(* org.springframework.statemachine.guard.Guard.evaluate(*))", throwing = "ex")
    public void errorInterceptorGuard(Exception ex) {
        log.info("Error on Guard: " + myStateMachine.getState().getId() + " with Exception " + ex.toString());
        myStateMachine.setStateMachineError(ex);
    }

    @AfterThrowing(pointcut = "execution(* org.springframework.statemachine.action.Action.execute(*))", throwing = "ex")
    public void errorInterceptorAction(Exception ex) {
        log.info("Error on Action: " + myStateMachine.getState().getId() + " with Exception " + ex.toString());
        myStateMachine.setStateMachineError(ex);
    }
}

And in my Controller where I start my StateMachine I make something like:

if(myStateMachine.hasStateMachineError()) {
   // do something which is returning the ERROR.
}

Still not 100% that what I wanted, because a statemachine.stop is not working from the inside, but I'm dealing it differnt.

Upvotes: 0

Janne Valkealahti
Janne Valkealahti

Reputation: 2646

I'm not sure what should happen in this case when one tries to stop machine from guard. Being honest I've never even tried it as what you try to do makes no sense. Looking back how I originally chose to expose context to guard as is, with access to machine itself, thus having stop method visible. I would not try to stop machine from internal logic when machine is running. I should have thought about this a long time ago :)

But having said that, guard really have only one role which is to protect a transition and it should return either true or false. We do handle catching exception(we evaluate it to false, afaik) when guard is called, but throwing something from a guard is generally undefined behaviour.

Assuming that something you do with actions errors, then that is a point where you should do something error related. I would i.e. create a terminal state, setup transitions to that terminal state and then you can send event from action(within catch) to instruct machine to go that state. Normally this kind of error handling is not done within single root level machine as it usually causes state explosion(where you need to start setting up transitions from all states to your error state). That's why substates are generally used to handle proper error handling.

But if you open up a bit more your use case, and what you want to accomplish, I could try to give better examples. Error handling is always a specific topic as it usually needs to be build into your machine design. A bit more sophisticate error handling what I'm currently working with spring-cloud-skipper can be seen from below state chart. This is probably too much out of a scope for your question, but gives some background to your next comments.

statechart

Upvotes: 2

Related Questions