/************************************************************************* * Copyright 2009-2013 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.autoscaling.activities; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.annotation.Nullable; import org.apache.log4j.Logger; import com.eucalyptus.autoscaling.config.AutoScalingConfiguration; import com.eucalyptus.util.async.CheckedListenableFuture; import com.eucalyptus.util.async.Futures; import com.google.common.collect.Maps; /** * Runs tasks with duplication checks and back-off */ public class BackoffRunner { private static final Logger logger = Logger.getLogger( BackoffRunner.class ); private static final ConcurrentMap<String,TaskInfo> tasksInProgress = Maps.newConcurrentMap(); private static final BackoffRunner instance = new BackoffRunner(); static BackoffRunner getInstance() { return instance; } BackoffRunner(){ } boolean taskInProgress( final String uniqueKey ) { final long timestamp = timestamp(); final TaskInfo taskInfo = tasksInProgress.get( uniqueKey ); return taskInfo != null && !taskInfo.isDone( timestamp ); } boolean runTask( final TaskWithBackOff task ) { boolean run = doRunTask( task, timestamp() ); if ( run ) { task.runTask(); } else { logger.info( "Not running task " + task ); } return run; } protected long timestamp() { return System.currentTimeMillis(); } private static long getTaskTimeout() { return AutoScalingConfiguration.getActivityTimeoutMillis(); } private static long getMaxBackoff() { return AutoScalingConfiguration.getActivityMaxBackoffMillis(); } private static long getInitialBackoff() { return AutoScalingConfiguration.getActivityInitialBackoffMillis(); } private static boolean doRunTask( final TaskWithBackOff task, final long timestamp ) { boolean run = false; final TaskInfo previous = tasksInProgress.putIfAbsent( task.getUniqueKey(), task.info( new TaskInfo( task, timestamp, 0 ) ) ); if ( previous == null ) { run = true; } else if ( previous.canFollow( timestamp, task.getBackoffGroup() ) && tasksInProgress.remove( task.getUniqueKey(), previous ) && tasksInProgress.putIfAbsent( task.getUniqueKey(), task.info( new TaskInfo( task, timestamp, previous.getNextFailureCount( task.getBackoffGroup() ) ) ) )==null ) { run = true; } return run; } static abstract class TaskWithBackOff { private final String uniqueKey; private final String backoffGroup; private final CheckedListenableFuture<Boolean> future; private volatile TaskInfo taskInfo; TaskWithBackOff( final String uniqueKey, final String backoffGroup ) { this.uniqueKey = uniqueKey; this.backoffGroup = backoffGroup; this.future = Futures.newGenericeFuture(); } TaskInfo info( final TaskInfo taskInfo ) { return this.taskInfo = taskInfo; } @Nullable TaskWithBackOff onSuccess() { return null; } final String getUniqueKey() { return uniqueKey; } final String getBackoffGroup() { return backoffGroup; } private Future<Boolean> getFuture() { return future; } abstract void runTask( ); final void success() { final TaskWithBackOff onSuccess = onSuccess(); if ( onSuccess != null ) { final boolean run = getUniqueKey().equals( onSuccess.getUniqueKey() ) ? tasksInProgress.replace( getUniqueKey(), taskInfo, onSuccess.info( new TaskInfo( onSuccess, System.currentTimeMillis(), 0 ) ) ) : doRunTask( onSuccess, System.currentTimeMillis() ); if ( !run ) { logger.info( "Unable to create activity: " + onSuccess.getBackoffGroup() + " for " + onSuccess.getUniqueKey() ); } else { future.set( true ); onSuccess.runTask(); } } if ( !future.isDone() ) { future.set( true ); } } final void failure() { future.set(false); } public String toString() { return uniqueKey + " " + backoffGroup; } } private final static class TaskInfo { private final String group; private final long created; private final Future<Boolean> resultFuture; private final int failureCount; private TaskInfo( final TaskWithBackOff task, final long created, final int failureCount ) { this.group = task.getBackoffGroup(); this.created = created; this.resultFuture = task.getFuture(); this.failureCount = failureCount; } private boolean canFollow( final long timestamp, final String group ) { return isSuccess() || ( isDone( timestamp ) && isBackoffExpired( timestamp, group ) ); } private boolean isBackoffExpired( final long timestamp, final String group ) { return !this.group.equals( group ) || // different groups not subject to back off timestamp - created > calculateBackoff(); } private long calculateBackoff() { long backoff = getInitialBackoff(); for ( int i=0; i<failureCount && backoff < getMaxBackoff() ; i++ ) { backoff *= 2; } return Math.min( backoff, getMaxBackoff() ); } private int getNextFailureCount( final String group ) { return this.group.equals( group ) && !isSuccess() ? failureCount + 1 : 0; } private boolean isDone( final long timestamp ) { return isTimedOut( timestamp ) || isComplete(); } private boolean isTimedOut( final long timestamp ) { return timestamp - created > getTaskTimeout(); } private boolean isSuccess() { try { return resultFuture != null && resultFuture.isDone() && !resultFuture.isCancelled() && resultFuture.get(); } catch ( final ExecutionException e ) { return false; } catch ( final InterruptedException e ) { return false; } } private boolean isComplete() { return resultFuture != null && resultFuture.isDone(); } } }