// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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 com.twitter.common.util; import java.util.logging.Level; import java.util.logging.Logger; import com.google.common.base.Throwables; import org.apache.commons.lang.builder.ToStringBuilder; import com.twitter.common.base.ExceptionalCommand; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; /** * A runnable task that is retried in a user-configurable fashion. * * @param <E> The type of exception that the ExceptionalCommand throws. * * @author Utkarsh Srivastava */ public class RetryingRunnable<E extends Exception> implements Runnable { private final String name; private final int tryNum; private final int numTries; private final Amount<Long, Time> retryDelay; private final ExceptionalCommand<E> task; private final CommandExecutor commandExecutor; private final Class<E> exceptionClass; private static final Logger LOG = Logger.getLogger(RetryingRunnable.class.getName()); /** * Create a Task with name {@code name} that executes at most {@code numTries} * in case of failure with an interval of {@code retryDelay} between attempts. * * @param name Human readable name for this task. * @param task the task to execute. * @param exceptionClass class of the exception thrown by the task. * @param numTries the total number of times to try. * @param retryDelay the delay between successive tries. * @param commandExecutor Executor to resubmit retries to. * @param tryNum the seq number of this try. */ public RetryingRunnable( String name, ExceptionalCommand<E> task, Class<E> exceptionClass, int numTries, Amount<Long, Time> retryDelay, CommandExecutor commandExecutor, int tryNum) { this.name = checkNotNull(name); this.task = checkNotNull(task); this.exceptionClass = checkNotNull(exceptionClass); this.retryDelay = checkNotNull(retryDelay); this.commandExecutor = checkNotNull(commandExecutor); checkArgument(numTries > 0); this.tryNum = tryNum; this.numTries = numTries; } /** * Create a Task with name {@code name} that executes at most {@code numTries} * in case of failure with an interval of {@code retryDelay} between attempts * and sets tryNum to be the first (=1). * * @param name Human readable name for this task. * @param task the task to execute. * @param exceptionClass class of the exception thrown by the task. * @param numTries the total number of times to try. * @param retryDelay the delay between successive tries. * @param commandExecutor Executor to resubmit retries to. */ public RetryingRunnable( String name, ExceptionalCommand<E> task, Class<E> exceptionClass, int numTries, Amount<Long, Time> retryDelay, CommandExecutor commandExecutor) { this(name, task, exceptionClass, numTries, retryDelay, commandExecutor, /*tryNum=*/ 1); } @Override public void run() { try { task.execute(); } catch (Exception e) { if (e.getClass().isAssignableFrom(exceptionClass)) { if (tryNum < numTries) { commandExecutor.execute(name, task, exceptionClass, numTries - 1, retryDelay); } else { LOG.log(Level.INFO, "Giving up on task: " + name + " " + "after " + "trying " + numTries + " times" + ".", e); } } else { LOG.log(Level.INFO, "Giving up on task: " + name + " after trying " + numTries + " times. " + "due to unhandled exception ", e); throw Throwables.propagate(e); } } } @Override public String toString() { return new ToStringBuilder(this) .append("name", name) .append("tryNum", tryNum) .append("numTries", numTries) .append("retryDelay", retryDelay) .toString(); } }