diff --git a/spring-statemachine-samples/web/src/main/java/demo/web/StateMachineConfig.java b/spring-statemachine-samples/web/src/main/java/demo/web/StateMachineConfig.java index 03c5b4f4..3102a10f 100644 --- a/spring-statemachine-samples/web/src/main/java/demo/web/StateMachineConfig.java +++ b/spring-statemachine-samples/web/src/main/java/demo/web/StateMachineConfig.java @@ -108,7 +108,7 @@ public class StateMachineConfig { .source(States.S1).target(States.S2).event(Events.C) .and() .withExternal() - .source(States.S2).target(States.S1).event(Events.C) + .source(States.S2).target(States.S1).event(Events.K) .and() .withExternal() .source(States.S1).target(States.S0).event(Events.D) @@ -201,7 +201,7 @@ public class StateMachineConfig { } public static enum Events { - A, B, C, D, E, F, G, H, I, J + A, B, C, D, E, F, G, H, I, J, K } private static class FooAction implements Action { diff --git a/spring-statemachine-samples/web/src/main/resources/static/index.html b/spring-statemachine-samples/web/src/main/resources/static/index.html index 25806caf..9b4a0d3b 100755 --- a/spring-statemachine-samples/web/src/main/resources/static/index.html +++ b/spring-statemachine-samples/web/src/main/resources/static/index.html @@ -33,6 +33,7 @@ +
diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java index 93682079..8d76bcf4 100644 --- a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; @@ -124,9 +125,13 @@ public class StateMachineTestPlan { sendVia.add(stateMachines.values().iterator().next()); } assertThat("Error finding machine to send via", sendVia, not(empty())); - for (StateMachine machine : sendVia) { - log.info("Sending test event " + step.sendEvent + " via machine " + machine); - machine.sendEvent(step.sendEvent); + if (!step.sendEventParallel) { + for (StateMachine machine : sendVia) { + log.info("Sending test event " + step.sendEvent + " via machine " + machine); + machine.sendEvent(step.sendEvent); + } + } else { + sendEventParallel(sendVia, step.sendEvent); } } else if (step.sendMessage != null) { ArrayList> sendVia = new ArrayList>(); @@ -244,4 +249,40 @@ public class StateMachineTestPlan { } } + /** + * Send event parallel to all machines. + * + * @param machines the machines + * @param event the event + */ + private void sendEventParallel(final List> machines, final E event) { + final CountDownLatch latch = new CountDownLatch(1); + final ArrayList joins = new ArrayList(); + int threadCount = machines.size(); + for (int i = 0; i < threadCount; ++i) { + final StateMachine machine = machines.get(i); + Runnable runner = new Runnable() { + + @Override + public void run() { + try { + latch.await(); + machine.sendEvent(event); + } catch (InterruptedException e) { + } + } + }; + Thread t = new Thread(runner, "EventSenderThread" + i); + joins.add(t); + t.start(); + } + latch.countDown(); + for (Thread t : joins) { + try { + t.join(); + } catch (InterruptedException e) { + } + } + } + } diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java index a3cb01b8..30d6e8df 100644 --- a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java @@ -114,6 +114,7 @@ public class StateMachineTestPlanBuilder { Message sendMessage; Object sendEventMachineId; boolean sendEventToAll = false; + boolean sendEventParallel = false; final Collection expectStates = new ArrayList(); Integer expectStateChanged; Integer expectStateEntered; @@ -172,9 +173,25 @@ public class StateMachineTestPlanBuilder { * @return the state machine test plan step builder */ public StateMachineTestPlanStepBuilder sendEvent(E event, boolean sendToAll) { + sendEvent(event, sendToAll, false); + return this; + } + + /** + * Send an event {@code E}. If {@code sendToAll} is set to {@code TRUE} event + * will be send to all existing machines. If {@code sendPalallel} is set to + * {@code TRUE} event to all machines will be send by parallel threads. + * + * @param event the event + * @param sendToAll send to all machines + * @param sendParallel send event parallel + * @return the state machine test plan step builder + */ + public StateMachineTestPlanStepBuilder sendEvent(E event, boolean sendToAll, boolean sendParallel) { this.sendEvent = event; this.sendEventMachineId = null; this.sendEventToAll = sendToAll; + this.sendEventParallel = sendParallel; return this; } @@ -405,9 +422,10 @@ public class StateMachineTestPlanBuilder { */ public StateMachineTestPlanBuilder and() { steps.add(new StateMachineTestPlanStep(sendEvent, sendMessage, sendEventMachineId, sendEventToAll, - expectStates, expectStateChanged, expectStateEntered, expectStateExited, expectEventNotAccepted, - expectTransition, expectTransitionStarted, expectTransitionEnded, expectStateMachineStarted, - expectStateMachineStopped, expectVariableKeys, expectVariables, expectExtendedStateChanged)); + sendEventParallel, expectStates, expectStateChanged, expectStateEntered, expectStateExited, + expectEventNotAccepted, expectTransition, expectTransitionStarted, expectTransitionEnded, + expectStateMachineStarted, expectStateMachineStopped, expectVariableKeys, expectVariables, + expectExtendedStateChanged)); return StateMachineTestPlanBuilder.this; } @@ -418,6 +436,7 @@ public class StateMachineTestPlanBuilder { Message sendMessage; Object sendEventMachineId; boolean sendEventToAll = false; + boolean sendEventParallel = false; final Collection expectStates; Integer expectStateChanged; Integer expectStateEntered; @@ -433,15 +452,17 @@ public class StateMachineTestPlanBuilder { final Map expectVariables; public StateMachineTestPlanStep(E sendEvent, Message sendMessage, Object sendEventMachineId, - boolean sendEventToAll, Collection expectStates, Integer expectStateChanged, - Integer expectStateEntered, Integer expectStateExited, Integer expectEventNotAccepted, - Integer expectTransition, Integer expectTransitionStarted, Integer expectTransitionEnded, - Integer expectStateMachineStarted, Integer expectStateMachineStopped, - Collection expectVariableKeys, Map expectVariables, Integer expectExtendedStateChanged) { + boolean sendEventToAll, boolean sendEventParallel, Collection expectStates, + Integer expectStateChanged, Integer expectStateEntered, Integer expectStateExited, + Integer expectEventNotAccepted, Integer expectTransition, Integer expectTransitionStarted, + Integer expectTransitionEnded, Integer expectStateMachineStarted, Integer expectStateMachineStopped, + Collection expectVariableKeys, Map expectVariables, + Integer expectExtendedStateChanged) { this.sendEvent = sendEvent; this.sendMessage = sendMessage; this.sendEventMachineId = sendEventMachineId; this.sendEventToAll = sendEventToAll; + this.sendEventParallel = sendEventParallel; this.expectStates = expectStates; this.expectStateChanged = expectStateChanged; this.expectStateEntered = expectStateEntered; diff --git a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java index 980386d9..ea7074b5 100644 --- a/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java +++ b/spring-statemachine-zookeeper/src/main/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineEnsemble.java @@ -223,6 +223,9 @@ public class ZookeeperStateMachineEnsemble extends StateMachineEnsembleObj log.debug("Requesting persist write " + context + " with version " + stat.getVersion() + " for ensemble " + uuid); } persist.write(context, stat); + if (log.isDebugEnabled()) { + log.debug("Request persist write ok " + context + " new version " + stat.getVersion() + " for ensemble " + uuid); + } stateRef.set(new StateWrapper(context, stat.getVersion())); } catch (Exception e) { throw new StateMachineException("Error persisting data", e); diff --git a/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java b/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java index 764ac217..37563c5e 100644 --- a/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java +++ b/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java @@ -304,6 +304,76 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests { plan.test(); } + @Test + public void testParallelEvents() throws Exception { + context.register(ZkServerConfig.class, BaseConfig.class); + context.refresh(); + + CuratorFramework curatorClient = + context.getBean("curatorClient", CuratorFramework.class); + + StateMachine machine1 = + buildTestStateMachine2(curatorClient); + StateMachine machine2 = + buildTestStateMachine2(curatorClient); + StateMachine machine3 = + buildTestStateMachine2(curatorClient); + StateMachine machine4 = + buildTestStateMachine2(curatorClient); + StateMachine machine5 = + buildTestStateMachine2(curatorClient); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .defaultAwaitTime(2) + .stateMachine(machine1) + .stateMachine(machine2) + .stateMachine(machine3) + .stateMachine(machine4) + .stateMachine(machine5) + .step() + .expectStates("SI") + .and() + .step() + .sendEvent("E1", true) + .expectStateChanged(1) + .expectStates("S1") + .and() + .step() + .sendEvent("E2", true, true) + .expectStateChanged(1) + .expectStates("S2") + .and() + .step() + .sendEvent("E3", true, true) + .expectStateChanged(1) + .expectStates("S1") + .and() + .step() + .sendEvent("E2", true, true) + .expectStateChanged(1) + .expectStates("S2") + .and() + .step() + .sendEvent("E3", true, true) + .expectStateChanged(1) + .expectStates("S1") + .and() + .step() + .sendEvent("E2", true, true) + .expectStateChanged(1) + .expectStates("S2") + .and() + .step() + .sendEvent("E3", true, true) + .expectStateChanged(1) + .expectStates("S1") + .and() + .build(); + + plan.test(); + } + @Test public void testExtendedStateVariables1() throws Exception { context.register(ZkServerConfig.class, BaseConfig.class); @@ -843,6 +913,9 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests { .withExternal() .source("S1").target("S2").event("E2") .and() + .withExternal() + .source("S2").target("S1").event("E3") + .and() .withInternal() .source("SI").event("EV") .action(setVariableAction())