From 9d59209ce8d30c9e058ebe21b3c3dfdc2edfce05 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 30 Dec 2015 08:46:52 +0000 Subject: [PATCH] Fix event defer issues - Event may get not accepted in some cases while it should be deferred. Check defer again before accept would return false. - Relates to #131 --- .../support/AbstractStateMachine.java | 7 ++++ .../support/DefaultStateMachineExecutor.java | 1 + .../statemachine/EventDeferTests.java | 33 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 215ba6a8..f6fa55af 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -592,6 +592,13 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } } } + // if we're about to not accept event, check defer again in case + // state was changed between original check and now + if ((currentState != null && currentState.shouldDefer(message))) { + log.info("Current state " + currentState + " deferred event " + message); + stateMachineExecutor.queueDeferredEvent(message); + return true; + } return false; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java index ec87785d..05b5ff99 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateMachineExecutor.java @@ -272,6 +272,7 @@ public class DefaultStateMachineExecutor extends LifecycleObjectSupport im State currentState = stateMachine.getState(); if (queuedEvent != null) { if ((currentState != null && currentState.shouldDefer(queuedEvent))) { + log.info("Current state " + currentState + " deferred event " + queuedEvent); queueDeferredEvent(queuedEvent); return true; } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EventDeferTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EventDeferTests.java index 83124928..396a9054 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EventDeferTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EventDeferTests.java @@ -153,6 +153,39 @@ public class EventDeferTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains("READY")); } + @Test + public void testDeferWithSubs2ThreadExecutor() throws Exception { + context.register(Config1.class, ExecutorConfig.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + TestListener listener = new TestListener(); + machine.addStateListener(listener); + machine.start(); + + assertThat(listener.stateMachineStartedLatch.await(3, TimeUnit.SECONDS), is(true)); + assertThat(listener.stateChangedLatch.await(3, TimeUnit.SECONDS), is(true)); + + listener.reset(0, 0, 2, 0); + machine.sendEvent("E2"); + machine.sendEvent("E2"); + + assertThat(listener.readyStateEnteredLatch.await(3, TimeUnit.SECONDS), is(true)); + assertThat(listener.readyStateEnteredCount, is(2)); + + assertThat(machine.getState().getIds(), contains("READY")); + } + + @Test + public void testDeferWithSubs2ThreadExecutorSmoke() throws Exception { + // smoke above test to see threading issues + for (int i = 0; i < 500; i++) { + setup(); + testDeferWithSubs2ThreadExecutor(); + clean(); + } + } + @Test public void testSubNotDeferOverrideSuperTransition() throws Exception { context.register(Config3.class);