package edu.washington.escience.myria.operator.failures; import java.util.Random; import java.util.concurrent.TimeUnit; import com.google.common.collect.ImmutableMap; import edu.washington.escience.myria.DbException; import edu.washington.escience.myria.Schema; import edu.washington.escience.myria.operator.Operator; import edu.washington.escience.myria.operator.UnaryOperator; import edu.washington.escience.myria.storage.TupleBatch; /** * Inject a random failure. The injection is conducted as the following:<br/> * <ul> * <li>At the time the operator is opened, the delay starts to count.</li> * <li>Do nothing before delay expires.</li> * <li>After delay expires, for each second, randomly decide if a failure should be injected, i.e. throw an * {@link InjectedFailureException}, according to the failure probability.</li> * </ul> * */ public class SingleRandomFailureInjector extends UnaryOperator { /** * Logger. * */ private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SingleRandomFailureInjector.class.getName()); /** * failure probability per second. * */ private final double failureProbabilityPerSecond; /** * Record if a failure is already injected. The class injects only a single failure. * */ private volatile boolean hasFailed; /** * Denoting if the next tuple retrieval event should trigger a failure. * */ private volatile boolean toFail; /** * failure inject thread. This thread decides if a failure should be injected. * */ private volatile Thread failureInjectThread; /** * delay in milliseconds. * */ private final long delayInMS; /** * @param delay the delay * @param delayUnit the timeunit of the delay * @param failureProbabilityPerSecond per second failure probability. * @param child the child operator. * */ public SingleRandomFailureInjector( final long delay, final TimeUnit delayUnit, final double failureProbabilityPerSecond, final Operator child) { super(child); this.failureProbabilityPerSecond = failureProbabilityPerSecond; hasFailed = false; toFail = false; delayInMS = delayUnit.toMillis(delay); } /** * */ private static final long serialVersionUID = 1L; @Override protected final void init(final ImmutableMap<String, Object> initProperties) throws DbException { toFail = false; if (!hasFailed) { failureInjectThread = new Thread() { @Override public void run() { Random r = new Random(); try { Thread.sleep(delayInMS); } catch (InterruptedException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(SingleRandomFailureInjector.this + " exit during delay."); } return; } while (true) { if (r.nextDouble() < failureProbabilityPerSecond) { toFail = true; hasFailed = true; return; } try { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); } catch (InterruptedException e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(SingleRandomFailureInjector.this + " exit during 1 second sleep."); } return; } } } }; failureInjectThread.start(); } else { failureInjectThread = null; } } @Override protected final void cleanup() throws DbException { if (failureInjectThread != null) { failureInjectThread.interrupt(); failureInjectThread = null; } toFail = false; } @Override protected final TupleBatch fetchNextReady() throws DbException { if (toFail) { toFail = false; throw new InjectedFailureException("Failure injected by " + this); } return getChild().nextReady(); } @Override protected final Schema generateSchema() { Operator child = getChild(); if (child == null) { return null; } return child.getSchema(); } }