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:
Janne Valkealahti
2016-12-29 09:58:04 +00:00
parent ea2fa03de9
commit 8bb852cc79
2 changed files with 95 additions and 1 deletions

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}