3

We have just started using spring state machine. Have a couple of questions:

  • Is the state context only once per state machine?
  • Do the events passed to the state machine run in a blocking way? Any way to make them run in parallel, like, providing a new state machine everytime an event is triggered?

Here is my code:

Configuring the states and transitions:

@Override
public void configure(
        StateMachineTransitionConfigurer<WorkFlowStates, WorkFlowEvent> transitions)
                throws Exception {
    transitions
    .withExternal()
    .source(WorkFlowStates.ready)
    .target(WorkFlowStates.l1Creation)
    .event(WorkFlowEvent.createWorkItem)
    .action(workFlowCreator.createL1())

Providing actions during state transitions:

public Action<WorkFlowStates, WorkFlowEvent> createL3() {
    return new Action<WorkFlowStates, WorkFlowEvent>() {

        public void execute(StateContext<WorkFlowStates, WorkFlowEvent> context) {
            System.out.println("l3 creation in action");
            Map<Object, Object> variables = context.getExtendedState().getVariables();
            Integer counter = (Integer)variables.get("counter");
            if(counter == null) counter = 1;
            else counter = counter+1;
            variables.put("counter", counter);
            System.out.println("Counter is "+counter);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            variables.put(Level.l3, WorkItemFactory.createFailureL1L2L3WorkItem());
            variables.put("level", Level.l3);
            System.out.println("l3 created");
        }
    };
}

Task executor:

public void configure(StateMachineConfigurationConfigurer<WorkFlowStates, 
WorkFlowEvent>config)
                throws Exception {
    config
    .withConfiguration()
    .autoStartup(true)
    .taskExecutor(taskExecutor())
    .listener(new WorkFlowStateMachineListener()); 
}

@Bean(name = StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.afterPropertiesSet();
    taskExecutor.setCorePoolSize(5);
    return taskExecutor;
}

And events passed to the state machine:

StateMachine<WorkFlowStates, WorkFlowEvent> stateMachine = 
    context.getBean(StateMachine.class);

    stateMachine.sendEvent(WorkFlowEvent.createWorkItem);
    stateMachine.sendEvent(WorkFlowEvent.createWorkItem);

1 Answers1

4

Yes, default behaviour is blocking as underlying TaskExecutor is SyncTaskExecutor. This can be changed via common config as mentioned in docs. Also see tasks recipe where ThreadPoolTaskExecutor is used on default to execute regions parallel.

When moving away from a blocking machine you need to pay attention how machine works and when its ready to process further events as machine may then be in a state where events are discarded. This is usually when you may need to start adding deferred events so that machine can process those in future in more suitable time.

Janne Valkealahti
  • 2,552
  • 1
  • 14
  • 12
  • Alright. Any views on the other question - "Is the state context only once per state machine?" – ShankaraNarayanan May 06 '16 at 14:05
  • Ah yes, people usually mix what these contexts mean. `StateContext` is what's created per a transition and `StateMachineContext` is then more high level context which is usually used when persisting a machine. So not exactly "once per machine". – Janne Valkealahti May 06 '16 at 14:13
  • But the ThreadPoolTaskExecutor is useful only if we have tasks right? When i ran it with just states, it is still blocking. – ShankaraNarayanan May 11 '16 at 13:21
  • I think you need to show your code as that is a way to make state machine execution asynchronous. Probably what you did didn't replace default `SyncTaskExecutor`. – Janne Valkealahti May 11 '16 at 15:58
  • Sure. I have updated the code above with the task executor. – ShankaraNarayanan May 12 '16 at 07:38
  • Ah, I need to check if that is a bug. `.taskExecutor(taskExecutor())` should override defaults. Could you leave it our and define bean as `@Bean(name = StateMachineSystemConstants.TASK_EXECUTOR_BEAN_NAME)`. Default bean name with annotation config used is `stateMachineTaskExecutor`, not `taskExecutor`. – Janne Valkealahti May 12 '16 at 12:54
  • Ya. So i changed the bean name to stateMachineTaskExecutor, but the same results. – ShankaraNarayanan May 12 '16 at 13:44
  • Oh wait, I may have had a misunderstanding what you asked. Events in a single machine/region cannot ever be parallel as state machine is always run-to-completion by definition. Use of `ThreadPoolTaskExecutor` will cause `sendEvent()` not to block for event processing. Parallel event execution can only happen if you have multiple independent orthogonal regions(which is what's used in tasks example). – Janne Valkealahti May 12 '16 at 14:25
  • Thanks for your reply. Since, not all tasks can be parallelized, do you recommend creating multiple INSTANCES of a state machine? – ShankaraNarayanan May 12 '16 at 15:15
  • If you can't represent your stuff in a single machine, then yes. That's why we have manual builders and @EnableStateMachineFactory. – Janne Valkealahti May 12 '16 at 17:41