Make autostart smarter
- Factory now listens machine event if autostart is requested which should give fully started machine i.e. if asynch executor is used. Bails out after 30 seconds. Further tweaks like allowing user to define this time or conditionally not wait could be added later. - Fix typos. - Fixes #298
This commit is contained in:
@@ -24,6 +24,8 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Stack;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -53,6 +55,7 @@ import org.springframework.statemachine.config.model.verifier.CompositeStateMach
|
||||
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
|
||||
import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.security.StateMachineSecurityInterceptor;
|
||||
@@ -367,7 +370,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
}
|
||||
|
||||
/**
|
||||
* Sett state machine monitor.
|
||||
* Set state machine monitor.
|
||||
*
|
||||
* @param stateMachineMonitor the state machine monitor
|
||||
*/
|
||||
@@ -377,7 +380,16 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
|
||||
private StateMachine<S, E> delegateAutoStartup(StateMachine<S, E> delegate) {
|
||||
if (handleAutostartup && delegate instanceof SmartLifecycle && ((SmartLifecycle) delegate).isAutoStartup()) {
|
||||
AutostartListener<S, E> autostartListener = new AutostartListener<>();
|
||||
delegate.addStateListener(autostartListener);
|
||||
((SmartLifecycle)delegate).start();
|
||||
try {
|
||||
autostartListener.latch.await(30, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
log.warn("Waited 30 seconds for machine to start as autostart was requested, machine may not be ready");
|
||||
} finally {
|
||||
delegate.removeStateListener(autostartListener);
|
||||
}
|
||||
}
|
||||
return delegate;
|
||||
}
|
||||
@@ -866,4 +878,17 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState<S, E> pseudoState);
|
||||
|
||||
/**
|
||||
* Simple utility listener waiting machine to get started if
|
||||
* autostart was requestes. Needed for machine to be ready
|
||||
* if async executor is used.
|
||||
*/
|
||||
private static class AutostartListener<S, E> extends StateMachineListenerAdapter<S, E> {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,15 @@ import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
@@ -36,6 +39,8 @@ import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
|
||||
public class StateMachineFactoryTests extends AbstractStateMachineTests {
|
||||
|
||||
@@ -118,6 +123,29 @@ public class StateMachineFactoryTests extends AbstractStateMachineTests {
|
||||
assertThat(machine2.getState().getIds(), contains(TestStates.S1));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Test
|
||||
public void testMachineFromFactoryWithAsyncExecutorAutoStart() throws Exception {
|
||||
context.register(Config6.class);
|
||||
context.refresh();
|
||||
|
||||
ObjectStateMachineFactory<TestStates, TestEvents> stateMachineFactory =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, ObjectStateMachineFactory.class);
|
||||
StateMachine<TestStates,TestEvents> machine = stateMachineFactory.getStateMachine();
|
||||
|
||||
// factory waits machine to get started so we
|
||||
// should have state immediately
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1));
|
||||
|
||||
// still need to listen state chance manually before
|
||||
// checking state as execution happens in a thread
|
||||
TestStateMachineListener listener = new TestStateMachineListener();
|
||||
machine.addStateListener(listener);
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
|
||||
assertThat(listener.latch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S2));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
@@ -255,4 +283,45 @@ public class StateMachineFactoryTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory
|
||||
static class Config6 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineConfigurationConfigurer<TestStates, TestEvents> config) throws Exception {
|
||||
config
|
||||
.withConfiguration()
|
||||
.autoStartup(true)
|
||||
.taskExecutor(new SimpleAsyncTaskExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S1)
|
||||
.state(TestStates.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S2)
|
||||
.event(TestEvents.E1);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestStateMachineListener extends StateMachineListenerAdapter<TestStates, TestEvents> {
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<TestStates, TestEvents> from, State<TestStates, TestEvents> to) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user