From 10f0ec0edfe8f084e97da80961444e65cb42bc3e Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 8 May 2019 18:27:27 +0100 Subject: [PATCH] Clear event defer list - In ReactiveStateMachineExecutor deferList is clear with deferred event if it triggers. - Fixes #747 --- .../support/ReactiveStateMachineExecutor.java | 12 ++++++--- .../statemachine/EventDeferTests.java | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java index 242f7e63..78cc3aab 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/ReactiveStateMachineExecutor.java @@ -54,6 +54,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; +/** + * Default reactive implementation of a {@link StateMachineExecutor}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ public class ReactiveStateMachineExecutor extends LifecycleObjectSupport implements StateMachineExecutor { private static final Log log = LogFactory.getLog(ReactiveStateMachineExecutor.class); @@ -66,15 +74,12 @@ public class ReactiveStateMachineExecutor extends LifecycleObjectSupport i private final Message initialEvent; private final TransitionComparator transitionComparator; private final TransitionConflictPolicy transitionConflictPolicy; - // TODO deferList is never cleared private final Queue> deferList = new ConcurrentLinkedQueue>(); private final AtomicBoolean initialHandled = new AtomicBoolean(false); private final StateMachineInterceptorList interceptors = new StateMachineInterceptorList(); - private volatile Message forwardedInitialEvent; private volatile Message queuedMessage = null; private StateMachineExecutorTransit stateMachineExecutorTransit; - private EmitterProcessor triggerProcessor = EmitterProcessor.create(false); private FluxSink triggerSink; private Flux triggerFlux; @@ -215,6 +220,7 @@ public class ReactiveStateMachineExecutor extends LifecycleObjectSupport i if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) { if (trigger != null && trigger.evaluate(new DefaultTriggerContext(queuedEvent.getPayload()))) { + deferList.remove(queuedEvent); return Mono.just(new TriggerQueueItem(trigger, queuedEvent)); } } 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 04067a4a..73c53fdb 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 @@ -365,6 +365,32 @@ public class EventDeferTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains("SUB2")); } + @Test + public void testDeferEventCleared() throws Exception { + context.register(Config5.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + + machine.start(); + assertThat(machine.getState().getIds(), containsInAnyOrder("S1")); + + machine.sendEvent("E2"); + assertThat(machine.getState().getIds(), containsInAnyOrder("S1")); + Object executor = TestUtils.readField("stateMachineExecutor", machine); + Collection readField = TestUtils.readField("deferList", executor); + assertThat(readField.size(), is(1)); + + machine.sendEvent("E1"); + assertThat(machine.getState().getIds(), containsInAnyOrder("S1")); + readField = TestUtils.readField("deferList", executor); + assertThat(readField.size(), is(0)); + + // deferred event handled so should not get back to S1 + machine.sendEvent("E1"); + assertThat(machine.getState().getIds(), containsInAnyOrder("S2")); + } + @Configuration @EnableStateMachine static class Config1 extends StateMachineConfigurerAdapter {