/*
* Copyright 2014 Baidu, 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 com.baidubce.http;
import static com.google.common.base.Preconditions.checkArgument;
import java.io.IOException;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baidubce.BceClientConfiguration;
import com.baidubce.BceClientException;
import com.baidubce.BceServiceException;
import com.baidubce.ErrorCode;
/**
* Retry policy that can be configured on a specific service client using {@link BceClientConfiguration}. This class is
* immutable, therefore safe to be shared by multiple clients.
*
* @see BceClientConfiguration
*/
public class DefaultRetryPolicy implements RetryPolicy {
private static Logger logger = LoggerFactory.getLogger(DefaultRetryPolicy.class);
/**
* Base sleep time (milliseconds) for general exceptions. *
*/
private static final int SCALE_FACTOR = 300;
/**
* Non-negative integer indicating the max retry count.
*/
private int maxErrorRetry;
/**
* Max delay time in millis.
*/
private long maxDelayInMillis;
/**
* Constructs a new DefaultRetryPolicy.
*/
public DefaultRetryPolicy() {
this(RetryPolicy.DEFAULT_MAX_ERROR_RETRY, RetryPolicy.DEFAULT_MAX_DELAY_IN_MILLIS);
}
/**
* Constructs a new retry policy.
*
* @param maxErrorRetry Maximum number of retry attempts for failed requests.
* @param maxDelayInMillis Maximum delay time (in milliseconds) before next retry attempt.
* @see BceClientConfiguration
*/
public DefaultRetryPolicy(int maxErrorRetry, long maxDelayInMillis) {
checkArgument(maxErrorRetry >= 0, "maxErrorRetry should be a non-negative.");
checkArgument(maxDelayInMillis >= 0, "maxDelayInMillis should be a non-negative.");
this.maxErrorRetry = maxErrorRetry;
this.maxDelayInMillis = maxDelayInMillis;
}
/**
* Returns the maximum number of retry attempts.
*
* @return The maximum number of retry attempts.
*/
@Override
public int getMaxErrorRetry() {
return this.maxErrorRetry;
}
/**
* Returns the maximum delay time (in milliseconds) before retrying a request.
*
* @return the maximum delay time (in milliseconds) before retrying a request.
*/
@Override
public long getMaxDelayInMillis() {
return this.maxDelayInMillis;
}
/**
* Returns the delay (in milliseconds) before next retry attempt. A negative value indicates that no more retries
* should be made.
*
* @param exception the exception from the failed request, represented as an BceClientException object.
* @param retriesAttempted the number of times the current request has been attempted
* (not including the next attempt after the delay).
* @return the delay (in milliseconds) before next retry attempt.A negative value indicates that no more retries
* should be made.
*/
@Override
public long getDelayBeforeNextRetryInMillis(BceClientException exception, int retriesAttempted) {
if (!this.shouldRetry(exception, retriesAttempted)) {
return -1;
}
if (retriesAttempted < 0) {
return 0;
}
return (1 << (retriesAttempted + 1)) * SCALE_FACTOR;
}
/**
* Returns whether a failed request should be retried according to the given request context. In the following
* circumstances, the request will fail directly without consulting this method:
* <ul>
* <li>if it has already reached the max retry limit,
* <li>if the request contains non-repeatable content,
* <li>if any RuntimeException or Error is thrown when executing the request.
* </ul>
*
* @param exception the exception from the failed request, represented as a BceClientException object.
* @param retriesAttempted the number of times the current request has been attempted.
* @return true if the failed request should be retried.
*/
protected boolean shouldRetry(BceClientException exception, int retriesAttempted) {
// Always retry on client exceptions caused by IOException
if (exception.getCause() instanceof IOException) {
logger.debug("Retry for IOException.");
return true;
}
// Only retry on a subset of service exceptions
if (exception instanceof BceServiceException) {
BceServiceException e = (BceServiceException) exception;
/*
* For 500 internal server errors and 503 service unavailable errors and 502 service bad gateway, we want to retry, but we need to use
* an exponential back-off strategy so that we don't overload a server with a flood of retries.
*/
if (e.getStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
logger.debug("Retry for internal server error.");
return true;
}
if (e.getStatusCode() == HttpStatus.SC_BAD_GATEWAY) {
logger.debug("Retry for bad gateway.");
return true;
}
if (e.getStatusCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) {
logger.debug("Retry for service unavailable.");
return true;
}
String errorCode = e.getErrorCode();
if (ErrorCode.REQUEST_EXPIRED.equals(errorCode)) {
logger.debug("Retry for request expired.");
return true;
}
}
return false;
}
}