Skip to content

FORK -> JOIN states not working as expected #1196

@aud-copart

Description

@aud-copart

Example of config

@Override
public void configure(StateMachineStateConfigurer<States, Actions> states) throws Exception {
	states
			.withStates()
			.initial(States.BEGIN)
			.fork(States.FORK)
			.join(States.JOIN)
			.state(States.PROCESS_1, processOneStateMachineFactory)
			.state(States.PROCESS_2, processTwoStateMachineFactory)
			.end(States.END)
			.and()
			.withStates()
			.parent(States.FORK)
			.region(Regions.SEQUENTIAL.name())
			.initial(States.RECEIVING)
			.state(States.RECEIVING, receivingStateMachineFactory)
			.end(States.RECEIVING_DONE)
			.and()
			.withStates()
			.parent(States.FORK)
			.region(Regions.PARALLEL.name())
			.initial(States.OT)
			.state(States.OT, otStateMachineFactory)
			.end(States.OT_DONE);
}

Example of transitions for FORK and JOIN

@Override
public void configure(StateMachineTransitionConfigurer<States, Actions> transitions) throws Exception {
	transitions
			.withExternal()
			.source(States.BEGIN)
			.target(States.FORK)
			.event(Actions.START)
			.and()
			.withFork()
			.source(States.FORK)
			.target(States.OT)
			.target(States.RECEIVING)
			.and()
			.withJoin()
			.source(States.RECEIVING_DONE)
			.source(States.OT_DONE)
			.target(States.JOIN)
			.and()
			.withExternal()
			.source(States.JOIN)
			.target(States.PROCESS_1)
			.event(Actions.COMPLETE_JOIN)
			.and()
			.withExternal()
			.source(States.PROCESS_1)
			.target(States.PROCESS_2)
			.event(Actions.COMPLETE_ PROCESS_1)
			.and()
			.withExternal()
			.source(States.PROCESS_2)
			.target(States.END)
			.event(Actions.COMPLETE_ PROCESS_2);
}

In this case all work as expected until you have 1 instance of application and application won't be restarted.
But if we restart our application or call SM action on another instance thre we don't have memory context for called SM and one or both of our parallel processes was somewhere in the middle and we restore context from DB repository and proceed all action which set RECEIVING_DONE state and OT_DONE then state JOIN won't happen and SM execution is stuck.

Just for clarification parallel middle state mean what we have 3-4 internal states in RECEIVING or OT state machine and made restart when we was not on initial state.

I found solution how to avoid it but will be great to fix it in next versions.

This is the additional code that fixed this behavior:

@Override
public void configure(StateMachineTransitionConfigurer<States, Actions> transitions) throws Exception {
	transitions
			.withExternal()
			.source(States.BEGIN)
			.target(States.FORK)
			.event(Actions.START)
			.and()
			.withFork()
			.source(States.FORK)
			.target(States.OT)
			.target(States.RECEIVING)
			.and()
			.withJoin()
			.source(States.RECEIVING_DONE)
			.source(States.OT_DONE)
			.target(States.JOIN)
//=====================================================
			.and()
			.withExternal()
			.source(States.RECEIVING_DONE)
			.target(States.PROCESS_1)
			.guard(someJoinGuard). // guard where I checked what I have both required states as DONE
				.and()
			.withExternal()
			.source(States.OT_DONE)
			.target(States.PROCESS_1)
			.guard(someJoinGuard) // guard where I checked what I have both required states as DONE
//=====================================================
			.and()
			.withExternal()
			.source(States.JOIN)
			.target(States.PROCESS_1)
			.and()
			.withExternal()
			.source(States.PROCESS_1)
			.target(States.PROCESS_2)
			.event(Actions.COMPLETE_ PROCESS_1)
			.and()
			.withExternal()
			.source(States.PROCESS_2)
			.target(States.END)
			.event(Actions.COMPLETE_ PROCESS_2)
	;
}

someJoinGuard example

public class someJoinGuard implements Guard<States, Actions> {
	@Override
	public boolean evaluate(StateContext<States, Actions> context) {
		return context.getStateMachine().getState().getIds().contains(States.RECEIVING_DONE)
				&& context.getStateMachine().getState().getIds().contains(States.OT_DONE);
	}

	@Override
	public String toString() {
		return "someJoinGuard";
	}
}

PS: If it's required I can create example project sources and provide it.

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