Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested submachine (sub-submachine) with fork and join entry and exit actions ignored or missing context #969

Open
adrien-lamoureux opened this issue Jun 17, 2021 · 0 comments
Labels
status/need-triage Team needs to triage and take a first look

Comments

@adrien-lamoureux
Copy link

Hi there,

I have a statemachine defined with two fork/joins and with each of those submachines having their own fork/join. I have tested that all states correctly transition including all sub states and fork/joins all the way to the final end state. That is great!, however, I discovered two problems related to actions on sub-submachines (2 levels down):

  1. Transitioning within a sub-submachine (2 levels down) region never invokes the exit action (but mysteriously, they all invoke their entry actions)

  2. Transitioning out of a join from the sub-submachine into another state results in a StateContext containing no useful information on what state is involved (I assume this is because its coming from a join, but I though an entry Action would at least have the target non-null).

Full example showing this issue (will echo entry and exit, as well as a tests on expected states entered/exited):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineBuilder.Builder;


public class MissingActionExecution {


  private List<String> statesEntered_;
  private List<String> statesExited_;
  
  public void before()
  {
    statesEntered_ = new ArrayList<String>();
    statesExited_ = new ArrayList<String>();
  }
    
  
  public static void main(String[] args) throws Exception {
    MissingActionExecution missingActions = new MissingActionExecution();
    
    missingActions.before();
    missingActions.run();
  }

  public void run() throws Exception
  {
    Builder<String,String> builder = StateMachineBuilder.builder();
    
    builder.configureStates().withStates().initial("startState")
      .fork("forkState")
      .state("state2") 
      .join("joinState")
      .state("state3", new EchoEntryAction<String,String>("state3"), new EchoExitAction<String,String>("state3"))
      .end("endState")
      .and()
      .withStates()
         .parent("state2")
         .initial("state21SubStart")
         .state("state21SubStateA", new EchoEntryAction<String,String>("state21SubStateA"), new EchoExitAction<String,String>("state21SubStateA"))
         .fork("forkInState21")
         .state("state21SubStateB")
         .join("joinInState21")
         .state("state21SubStateC", new EchoEntryAction<String,String>("state21SubStateC"), new EchoExitAction<String,String>("state21SubStateC"))
         .end("state21SubEnd")
         .and()
       .withStates()
         .parent("state2")
         .initial("state22SubStart")
         .state("state22SubStateA", new EchoEntryAction<String,String>("state22SubStateA"), new EchoExitAction<String,String>("state22SubStateA"))
         .fork("forkInState22")
         .state("state22SubStateB")
         .join("joinInState22")
         .state("state22SubStateC", new EchoEntryAction<String,String>("state22SubStateC"), new EchoExitAction<String,String>("state22SubStateC"))
         .end("state22SubEnd")
         .and()
         .withStates()
            .parent("state21SubStateB")
            .initial("state21SubStateBStart")
            .state("state21SubStateBA", new EchoEntryAction<String,String>("state21SubStateBA"), new EchoExitAction<String,String>("state21SubStateBA"))
            .state("state21SubStateBB", new EchoEntryAction<String,String>("state21SubStateBB"), new EchoExitAction<String,String>("state21SubStateBB"))
            .state("state21SubStateBC", new EchoEntryAction<String,String>("state21SubStateBC"), new EchoExitAction<String,String>("state21SubStateBC"))
            .end("state21SubStateBEnd")
            .and()
         .withStates()
            .parent("state21SubStateB")
            .initial("state211SubStateBStart")
            .state("state211SubStateBA", new EchoEntryAction<String,String>("state211SubStateBA"), new EchoExitAction<String,String>("state211SubStateBA"))
            .state("state211SubStateBB", new EchoEntryAction<String,String>("state211SubStateBB"), new EchoExitAction<String,String>("state211SubStateBB"))
            .state("state211SubStateBC", new EchoEntryAction<String,String>("state211SubStateBC"), new EchoExitAction<String,String>("state211SubStateBC"))
            .end("state211SubStateBEnd")
          .and()
          .withStates()
             .parent("state22SubStateB")
             .initial("state22SubStateBStart")
             .state("state22SubStateBA", new EchoEntryAction<String,String>("state22SubStateBA"), new EchoExitAction<String,String>("state22SubStateBA"))
             .state("state22SubStateBB", new EchoEntryAction<String,String>("state22SubStateBB"), new EchoExitAction<String,String>("state22SubStateBB"))
             .state("state22SubStateBC", new EchoEntryAction<String,String>("state22SubStateBC"), new EchoExitAction<String,String>("state22SubStateBC"))
             .end("state22SubStateBEnd")
             .and()
          .withStates()
             .parent("state22SubStateB")
             .initial("state222SubStateBStart")
             .state("state222SubStateBA", new EchoEntryAction<String,String>("state222SubStateBA"), new EchoExitAction<String,String>("state222SubStateBA"))
             .state("state222SubStateBB", new EchoEntryAction<String,String>("state222SubStateBB"), new EchoExitAction<String,String>("state222SubStateBB"))
             .state("state222SubStateBC", new EchoEntryAction<String,String>("state222SubStateBC"), new EchoExitAction<String,String>("state222SubStateBC"))
             .end("state222SubStateBEnd");       
         
                   
    
    builder.configureTransitions()
    .withExternal().source("startState").target("state2").and()
    .withFork().source("forkState").target("state21SubStart").target("state22SubStart").and()
        .withExternal().source("state21SubStart").target("state21SubStateA").and()
        .withExternal().source("state21SubStateA").target("state21SubStateB").and()
        .withFork().source("forkInState21").target("state21SubStateBStart").target("state211SubStateBStart").and()
          .withExternal().source("state21SubStateBStart").target("state21SubStateBA").and()
          .withExternal().source("state21SubStateBA").target("state21SubStateBB").and()
          .withExternal().source("state21SubStateBB").target("state21SubStateBC").and()
          .withExternal().source("state21SubStateBC").target("state21SubStateBEnd").and()
          .withExternal().source("state211SubStateBStart").target("state211SubStateBA").and()
          .withExternal().source("state211SubStateBA").target("state211SubStateBB").and()
          .withExternal().source("state211SubStateBB").target("state211SubStateBC").and()
          .withExternal().source("state211SubStateBC").target("state211SubStateBEnd").and()
        .withJoin().source("state21SubStateBEnd").source("state211SubStateBEnd").target("joinInState21").and()            
        .withExternal().source("joinInState21").target("state21SubStateC").and()
        .withExternal().source("state21SubStateC").target("state21SubEnd").and()
        .withExternal().source("state22SubStart").target("state22SubStateA").and()
        .withExternal().source("state22SubStateA").target("state22SubStateB").and()
        .withFork().source("forkInState22").target("state22SubStateBStart").target("state222SubStateBStart").and()
          .withExternal().source("state22SubStateBStart").target("state22SubStateBA").and()
          .withExternal().source("state22SubStateBA").target("state22SubStateBB").and()
          .withExternal().source("state22SubStateBB").target("state22SubStateBC").and()
          .withExternal().source("state22SubStateBC").target("state22SubStateBEnd").and()
          .withExternal().source("state222SubStateBStart").target("state222SubStateBA").and()
          .withExternal().source("state222SubStateBA").target("state222SubStateBB").and()
          .withExternal().source("state222SubStateBB").target("state222SubStateBC").and()
          .withExternal().source("state222SubStateBC").target("state222SubStateBEnd").and()              
        .withJoin().source("state22SubStateBEnd").source("state222SubStateBEnd").target("joinInState22").and()
        .withExternal().source("joinInState22").target("state22SubStateC").and()
        .withExternal().source("state22SubStateC").target("state22SubEnd").and()
   .withJoin().source("state21SubEnd").source("state22SubEnd").target("joinState").and()
   .withExternal().source("joinState").target("state3").and()
   .withExternal().source("state3").target("endState");
         
     
     
      
    StateMachine<String,String> sm = builder.build();
      
    sm.start();

    System.out.println("End State is:" +sm.getState().getIds() + "\n");
    
    
    Set<String> expected = new HashSet<String>(Arrays.asList(new String[]{
        "state21SubStateA",
        "state21SubStateBA","state21SubStateBB","state21SubStateBC",
        "state211SubStateBA","state211SubStateBB","state211SubStateBC",
        "state21SubStateC",
        "state22SubStateA",
        "state22SubStateBA","state22SubStateBB","state22SubStateBC",
        "state222SubStateBA","state222SubStateBB","state222SubStateBC",
        "state22SubStateC",
        "state3"}));
    

    boolean statesExpectedOnEntry = new HashSet<String>(statesEntered_).equals(expected);
    if (!statesExpectedOnEntry)
    {
        System.out.println("ENTRY TEST FAIL: \nfound:\n" + statesEntered_ + "\nexpected: \n" + expected + "\n");
    }
    
    boolean statesExpectedOnExit = new HashSet<String>(statesExited_).equals(expected);
    if (!statesExpectedOnExit)
    {
        System.out.println("EXIT TEST FAIL: \nfound:\n" + statesExited_ + "\nexpected: \n" + expected + "\n");
    }    
    
    sm.stop();    
  }
  
  private class EchoEntryAction<S, E> implements Action<S, E> {

    private String state_;
    public EchoEntryAction(String state)
    {
      state_= state;
    }    
    
    public void execute(StateContext<S, E> context) {
      
      if (context == null || context.getTarget() == null)
      {
        System.out.println("Entered NULL TARGET: " + state_);
        //statesEntered_.add(state_);
        return;
      }
      
      statesEntered_.add((String)context.getTarget().getId());
      System.out.println("Entered: " + context.getTarget().getId());
      
    }
    
  }
  
  private class EchoExitAction<S, E> implements Action<S, E> {

    private String state_;
    public EchoExitAction(String state)
    {
      state_= state;
    }
    
    public void execute(StateContext<S, E> context) {
      
      if (context == null || context.getSource() == null)
      {
        System.out.println("Exited NULL SOURCE: " + state_);
        //statesExited_.add(state_);
        return;
      }      
      
      statesExited_.add((String)context.getSource().getId());
      System.out.println("Exited: " + context.getSource().getId());
      
    }
  }
  
}

@github-actions github-actions bot added the status/need-triage Team needs to triage and take a first look label Jun 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status/need-triage Team needs to triage and take a first look
Projects
None yet
Development

No branches or pull requests

1 participant