package org.radargun.stages.lifecycle;
import java.util.Collection;
import java.util.Set;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.reporting.Timeline;
import org.radargun.state.ServiceListener;
import org.radargun.state.SlaveState;
import org.radargun.traits.Clustered;
import org.radargun.traits.Killable;
import org.radargun.traits.Lifecycle;
import org.radargun.traits.Partitionable;
import org.radargun.utils.TimeService;
/**
* Helper class for controlling the lifecycle of a service.
* Assumes that the service has the {@link Lifecycle} trait,
* other traits are optional.
*/
public class LifecycleHelper {
private static final Log log = LogFactory.getLog(LifecycleHelper.class);
protected static final String LIFECYCLE = "Lifecycle";
private LifecycleHelper() {
}
/**
* Starts the service.
* If the service supports {@link Clustered} trait and {@code validate} is set to true,
* the method waits until {@link org.radargun.traits.Clustered#getMembers() clustered trait}
* reports {@code expectedSlaves} slaves or for {@code clusterFormationTimeout} milliseconds.
* If the service supports {@link Partitionable} trait, the set of {@code reachable} slaves
* is set up before the service is started.
* Also, this method calls the {@link ServiceListener service listeners} on {@link SlaveState}.
* If the start fails, attempt to stop the service is executed.
*
* @param slaveState
* @param validate
* @param expectedSlaves
* @param clusterFormationTimeout
* @param reachable
*/
public static void start(SlaveState slaveState, boolean validate, Integer expectedSlaves, long clusterFormationTimeout,
Set<Integer> reachable) {
Lifecycle lifecycle = slaveState.getTrait(Lifecycle.class);
Clustered clustered = slaveState.getTrait(Clustered.class);
Partitionable partitionable = slaveState.getTrait(Partitionable.class);
try {
if (partitionable != null) {
partitionable.setStartWithReachable(slaveState.getSlaveIndex(), reachable);
}
for (ServiceListener listener : slaveState.getListeners()) {
listener.beforeServiceStart();
}
long startingTime = TimeService.currentTimeMillis();
lifecycle.start();
long startedTime = TimeService.currentTimeMillis();
slaveState.getTimeline().addEvent(LifecycleHelper.LIFECYCLE, new Timeline.IntervalEvent(startingTime, "Start", startedTime - startingTime));
if (validate && clustered != null) {
int expectedNumberOfSlaves = expectedSlaves != null ? expectedSlaves : slaveState.getGroupSize();
long clusterFormationDeadline = TimeService.currentTimeMillis() + clusterFormationTimeout;
for (; ; ) {
Collection<Clustered.Member> members = clustered.getMembers();
if (members == null || members.size() != expectedNumberOfSlaves) {
String msg = "Number of members=" + members + " is not the one expected: " + expectedNumberOfSlaves;
log.info(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
if (TimeService.currentTimeMillis() > clusterFormationDeadline) {
if (members == null) {
log.warn("Startup timed out without being able to confirm number of members.");
break;
} else {
throw new ClusterFormationTimeoutException(msg);
}
}
} else {
log.info("Number of members is the one expected: " + members.size());
break;
}
}
}
for (ServiceListener listener : slaveState.getListeners()) {
try {
listener.afterServiceStart();
} catch (Exception e) {
log.error("Failed to run listener " + listener, e);
}
}
} catch (RuntimeException e) {
log.trace("Failed to start", e);
try {
lifecycle.stop();
} catch (Exception ignored) {
log.trace("Failed to stop after start failed", ignored);
}
throw e;
}
}
/**
* Stops the service.
* If the service supports the {@link Killable} trait and {@code graceful} is set to false,
* this trait is used to kill the service instead of stopping it. Also, non-graceful stop
* can be executed asynchronously using {@link org.radargun.traits.Killable#killAsync()}.
* This method calls the {@link ServiceListener service listeners} on {@link SlaveState}.
*
* @param slaveState
* @param graceful
* @param async
*/
public static void stop(SlaveState slaveState, boolean graceful, boolean async) {
stop(slaveState, graceful, async, 0);
}
public static void stop(SlaveState slaveState, boolean graceful, boolean async, long gracefulStopTimeout) {
final Lifecycle lifecycle = slaveState.getTrait(Lifecycle.class);
if (lifecycle == null) throw new IllegalArgumentException();
Killable killable = slaveState.getTrait(Killable.class);
if (lifecycle.isRunning()) {
for (ServiceListener listener : slaveState.getListeners()) {
listener.beforeServiceStop(graceful);
}
try {
long stoppingTime;
if (graceful || killable == null) {
if (async) {
log.warn("Async graceful stop is not supported.");
}
if (graceful) {
log.info("Stopping service.");
} else {
log.info("Service is not Killable, stopping instead");
}
stoppingTime = TimeService.currentTimeMillis();
if (gracefulStopTimeout <= 0) {
lifecycle.stop();
} else {
Thread stopThread = new Thread(new Runnable() {
@Override
public void run() {
lifecycle.stop();
}
}, "StopThread");
stopThread.start();
try {
stopThread.join(gracefulStopTimeout);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Interrupted when waiting for Lifecycle.stop()");
}
if (stopThread.isAlive() && killable != null) {
log.warn("Lifecycle did not finished withing timeout, killing instead.");
killable.kill();
}
}
} else {
log.info("Killing service.");
stoppingTime = TimeService.currentTimeMillis();
if (async) {
killable.killAsync();
} else {
killable.kill();
}
}
long stoppedTime = TimeService.currentTimeMillis();
slaveState.getTimeline().addEvent(LIFECYCLE, new Timeline.IntervalEvent(stoppingTime, "Stop", stoppedTime - stoppingTime));
} finally {
for (ServiceListener listener : slaveState.getListeners()) {
listener.afterServiceStop(graceful);
}
}
} else {
log.info("No cache wrapper deployed on this slave, nothing to do.");
}
}
private static class ClusterFormationTimeoutException extends RuntimeException {
public ClusterFormationTimeoutException(String msg) {
super(msg);
}
}
}