Skip to content

StateAction randomly not executed (race condition?) #493

Closed
@mprimi

Description

@mprimi

I was seeing some actions associated to my states not being invoked.
The machine seems to proceed normally, other than for the action being ignored.

Here's a simple self-contained test that shows this behavior (as the comment notes, this succeeds 99% of the time. But if you execute the test in a loop, it is eventually going to fail).

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.test.StateMachineTestPlanBuilder;

import java.util.concurrent.atomic.AtomicInteger;

public class SmallStateActionTest {
    enum States {
        READY,
        S1,
        END,
    }

    enum Events {
        START,
        S1_DONE,
    }

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    /**
     * This is the simplest test I could come up with that reproduces a state action not being invoked for some reason.
     * Possibly some race condition bug.
     *
     * Executing this test once will very likely succeed.
     * Meaning the state machine configured works ok.
     *
     * If however the test is executed in a loop (in IDEA: Edit Configuration > Repeat: 'Until failure') it will rapidly
     * go through a lot of iterations, and eventually fail (takes a 2 to 5 of seconds for me).
     *
     * @throws Exception
     */
    @Test
    public void stateActionsTest() throws Exception {

        AtomicInteger s1ActionExecuted = new AtomicInteger(0);

        StateMachineBuilder.Builder<States, Events> builder;

        builder = StateMachineBuilder.builder();

        builder.configureConfiguration()
                .withConfiguration()
                .taskExecutor(new SyncTaskExecutor());

        builder.configureStates()
                .withStates()
                .initial(States.READY)
                .end(States.END)
                .state(States.S1, context -> {
                    System.out.println("Executing state action");
                    s1ActionExecuted.incrementAndGet();
                });

        builder.configureTransitions()
                .withExternal()
                .source(States.READY)
                .target(States.S1)
                .event(Events.START)
                .and()
                .withExternal()
                .source(States.S1)
                .target(States.END)
                .event(Events.S1_DONE);

        StateMachine<States, Events> stateMachine = builder.build();


        StateMachineTestPlanBuilder.<States, Events>builder()
                .defaultAwaitTime(2)
                .stateMachine(stateMachine)
                .step()
                .sendEvent(Events.START)
                .expectStateChanged(2)
                .expectState(States.S1)
                .and()
                .step()
                .sendEvent(Events.S1_DONE)
                .expectStateChanged(1)
                .expectState(States.END)
                .expectStateMachineStopped(1)
                .and()
                .build()
                .test();

        Assert.assertTrue(stateMachine.isComplete());

        Assert.assertEquals(1, s1ActionExecuted.get());
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions