/*
* Copyright © 2014 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.common.twill;
import com.google.common.collect.ImmutableMap;
import org.apache.twill.api.EventHandler;
import org.apache.twill.api.EventHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* A Twill {@link EventHandler} that abort the application if for some runnable it cannot provision container for
* too long.
*/
public class AbortOnTimeoutEventHandler extends EventHandler {
private static final Logger LOG = LoggerFactory.getLogger(AbortOnTimeoutEventHandler.class);
private long abortTime;
private boolean abortIfNotFull;
/**
* Constructs an instance of AbortOnTimeoutEventHandler that abort the application if some runnable has no
* containers, same as calling {@link #AbortOnTimeoutEventHandler(long, boolean)} with second parameter as
* {@code false}.
*
* @param abortTime Time in milliseconds to pass before aborting the application if no container is given to
* a runnable.
*/
public AbortOnTimeoutEventHandler(long abortTime) {
this(abortTime, false);
}
/**
* Constructs an instance of AbortOnTimeoutEventHandler that abort the application if some runnable has not enough
* containers.
* @param abortTime Time in milliseconds to pass before aborting the application if no container is given to
* a runnable.
* @param abortIfNotFull If {@code true}, it will abort the application if any runnable doesn't meet the expected
* number of instances.
*/
public AbortOnTimeoutEventHandler(long abortTime, boolean abortIfNotFull) {
this.abortTime = abortTime;
this.abortIfNotFull = abortIfNotFull;
}
@Override
protected Map<String, String> getConfigs() {
return ImmutableMap.of("abortTime", Long.toString(abortTime),
"abortIfNotFull", Boolean.toString(abortIfNotFull));
}
@Override
public void initialize(EventHandlerContext context) {
super.initialize(context);
this.abortTime = Long.parseLong(context.getSpecification().getConfigs().get("abortTime"));
this.abortIfNotFull = Boolean.parseBoolean(context.getSpecification().getConfigs().get("abortIfNotFull"));
}
@Override
public TimeoutAction launchTimeout(Iterable<TimeoutEvent> timeoutEvents) {
long now = System.currentTimeMillis();
for (TimeoutEvent event : timeoutEvents) {
LOG.info("Requested {} containers for runnable {}, only got {} after {} ms.",
event.getExpectedInstances(), event.getRunnableName(),
event.getActualInstances(), System.currentTimeMillis() - event.getRequestTime());
boolean pass = abortIfNotFull ? event.getActualInstances() == event.getExpectedInstances()
: event.getActualInstances() != 0;
if (!pass && (now - event.getRequestTime()) > abortTime) {
LOG.info("No containers for {}. Abort the application.", event.getRunnableName());
return TimeoutAction.abort();
}
}
// Check again in half of abort time.
return TimeoutAction.recheck(abortTime / 2, TimeUnit.MILLISECONDS);
}
}