mirror of https://github.com/apache/jclouds.git
Fix for Issue 37: Added configurable retry handler to impose maximum limits and a back-off delay algorithm to retries in response to server 5xx errors
git-svn-id: http://jclouds.googlecode.com/svn/trunk@850 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
parent
be3f1add34
commit
7d7e7744e7
|
@ -34,6 +34,7 @@ import static org.jclouds.command.pool.PoolConstants.PROPERTY_POOL_REQUEST_INVOK
|
||||||
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_ADDRESS;
|
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_ADDRESS;
|
||||||
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_PORT;
|
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_PORT;
|
||||||
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_SECURE;
|
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_SECURE;
|
||||||
|
import static org.jclouds.http.HttpConstants.PROPERTY_HTTP_MAX_RETRIES;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
@ -79,6 +80,7 @@ public class S3ContextFactory {
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_ADDRESS, "s3.amazonaws.com");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_ADDRESS, "s3.amazonaws.com");
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_PORT, "443");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_PORT, "443");
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_SECURE, "true");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_SECURE, "true");
|
||||||
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_HTTP_MAX_RETRIES, "5");
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_CONNECTION_REUSE, "75");
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_MAX_SESSION_FAILURES, "2");
|
||||||
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, "1");
|
DEFAULT_PROPERTIES.setProperty(PROPERTY_POOL_REQUEST_INVOKER_THREADS, "1");
|
||||||
|
|
|
@ -35,9 +35,12 @@ import org.jclouds.aws.s3.internal.LiveS3Connection;
|
||||||
import org.jclouds.http.HttpConstants;
|
import org.jclouds.http.HttpConstants;
|
||||||
import org.jclouds.http.HttpRequestFilter;
|
import org.jclouds.http.HttpRequestFilter;
|
||||||
import org.jclouds.http.HttpResponseHandler;
|
import org.jclouds.http.HttpResponseHandler;
|
||||||
|
import org.jclouds.http.HttpRetryHandler;
|
||||||
import org.jclouds.http.annotation.ClientErrorHandler;
|
import org.jclouds.http.annotation.ClientErrorHandler;
|
||||||
import org.jclouds.http.annotation.RedirectHandler;
|
import org.jclouds.http.annotation.RedirectHandler;
|
||||||
|
import org.jclouds.http.annotation.RetryHandler;
|
||||||
import org.jclouds.http.annotation.ServerErrorHandler;
|
import org.jclouds.http.annotation.ServerErrorHandler;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
|
|
||||||
|
@ -77,6 +80,8 @@ public class LiveS3ConnectionModule extends AbstractModule {
|
||||||
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
|
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
|
||||||
bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class).to(
|
bind(HttpResponseHandler.class).annotatedWith(ServerErrorHandler.class).to(
|
||||||
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
|
ParseAWSErrorFromXmlContent.class).in(Scopes.SINGLETON);
|
||||||
|
bind(HttpRetryHandler.class).annotatedWith(RetryHandler.class).to(
|
||||||
|
BackoffLimitedRetryHandler.class).in(Scopes.SINGLETON);
|
||||||
requestInjection(this);
|
requestInjection(this);
|
||||||
logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port);
|
logger.info("S3 Context = %1$s://%2$s:%3$s", (isSecure ? "https" : "http"), address, port);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,22 +23,25 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.aws.s3.config;
|
package org.jclouds.aws.s3.config;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent;
|
||||||
|
import org.jclouds.aws.s3.reference.S3Constants;
|
||||||
|
import org.jclouds.http.HttpResponseHandler;
|
||||||
|
import org.jclouds.http.HttpRetryHandler;
|
||||||
|
import org.jclouds.http.annotation.ClientErrorHandler;
|
||||||
|
import org.jclouds.http.annotation.RedirectHandler;
|
||||||
|
import org.jclouds.http.annotation.RetryHandler;
|
||||||
|
import org.jclouds.http.annotation.ServerErrorHandler;
|
||||||
|
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
|
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.name.Names;
|
import com.google.inject.name.Names;
|
||||||
import org.jclouds.aws.s3.handlers.ParseAWSErrorFromXmlContent;
|
|
||||||
import org.jclouds.aws.s3.reference.S3Constants;
|
|
||||||
import org.jclouds.http.HttpResponseHandler;
|
|
||||||
import org.jclouds.http.annotation.ClientErrorHandler;
|
|
||||||
import org.jclouds.http.annotation.RedirectHandler;
|
|
||||||
import org.jclouds.http.annotation.ServerErrorHandler;
|
|
||||||
import org.jclouds.http.config.JavaUrlHttpFutureCommandClientModule;
|
|
||||||
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
|
||||||
import static org.testng.Assert.assertEquals;
|
|
||||||
import org.testng.annotations.AfterMethod;
|
|
||||||
import org.testng.annotations.BeforeMethod;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
|
@ -58,6 +61,7 @@ public class S3ContextModuleTest {
|
||||||
"localhost");
|
"localhost");
|
||||||
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_HTTP_PORT)).to("1000");
|
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_HTTP_PORT)).to("1000");
|
||||||
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_HTTP_SECURE)).to("false");
|
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_HTTP_SECURE)).to("false");
|
||||||
|
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_HTTP_MAX_RETRIES)).to("5");
|
||||||
super.configure();
|
super.configure();
|
||||||
}
|
}
|
||||||
}, new JavaUrlHttpFutureCommandClientModule());
|
}, new JavaUrlHttpFutureCommandClientModule());
|
||||||
|
@ -99,4 +103,16 @@ public class S3ContextModuleTest {
|
||||||
assertEquals(error.errorHandler.getClass(), CloseContentAndSetExceptionHandler.class);
|
assertEquals(error.errorHandler.getClass(), CloseContentAndSetExceptionHandler.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class RetryHandlerTest {
|
||||||
|
@Inject
|
||||||
|
@RetryHandler
|
||||||
|
HttpRetryHandler retryHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRetryHandler() {
|
||||||
|
RetryHandlerTest handler = createInjector().getInstance(RetryHandlerTest.class);
|
||||||
|
assertEquals(handler.retryHandler.getClass(), BackoffLimitedRetryHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -32,4 +32,5 @@ public interface HttpConstants extends HttpHeaders, ContentTypes {
|
||||||
public static final String PROPERTY_HTTP_SECURE = "jclouds.http.secure";
|
public static final String PROPERTY_HTTP_SECURE = "jclouds.http.secure";
|
||||||
public static final String PROPERTY_HTTP_PORT = "jclouds.http.port";
|
public static final String PROPERTY_HTTP_PORT = "jclouds.http.port";
|
||||||
public static final String PROPERTY_HTTP_ADDRESS = "jclouds.http.address";
|
public static final String PROPERTY_HTTP_ADDRESS = "jclouds.http.address";
|
||||||
|
public static final String PROPERTY_HTTP_MAX_RETRIES = "jclouds.http.max-retries";
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,9 @@ import com.google.inject.Inject;
|
||||||
import org.jclouds.http.*;
|
import org.jclouds.http.*;
|
||||||
import org.jclouds.http.annotation.ClientErrorHandler;
|
import org.jclouds.http.annotation.ClientErrorHandler;
|
||||||
import org.jclouds.http.annotation.RedirectHandler;
|
import org.jclouds.http.annotation.RedirectHandler;
|
||||||
|
import org.jclouds.http.annotation.RetryHandler;
|
||||||
import org.jclouds.http.annotation.ServerErrorHandler;
|
import org.jclouds.http.annotation.ServerErrorHandler;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
|
|
||||||
|
@ -54,22 +56,16 @@ public abstract class BaseHttpFutureCommandClient implements HttpFutureCommandCl
|
||||||
@ServerErrorHandler
|
@ServerErrorHandler
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
protected HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
|
protected HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
|
||||||
|
|
||||||
|
@RetryHandler
|
||||||
|
@Inject(optional = true)
|
||||||
|
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BaseHttpFutureCommandClient(URL target) {
|
public BaseHttpFutureCommandClient(URL target) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isRetryable(HttpFutureCommand<?> command,
|
|
||||||
HttpResponse response) {
|
|
||||||
int code = response.getStatusCode();
|
|
||||||
if (command.getRequest().isReplayable() && code >= 500) {
|
|
||||||
logger.debug("resubmitting command: %1$s", command);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) {
|
protected void handleResponse(HttpFutureCommand<?> command, HttpResponse response) {
|
||||||
int code = response.getStatusCode();
|
int code = response.getStatusCode();
|
||||||
if (code >= 500) {
|
if (code >= 500) {
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class JavaUrlHttpFutureCommandClient extends BaseHttpFutureCommandClient
|
||||||
connection);
|
connection);
|
||||||
response = getResponse(connection);
|
response = getResponse(connection);
|
||||||
logger.trace("%1$s - received response %2$s", target, response);
|
logger.trace("%1$s - received response %2$s", target, response);
|
||||||
if (isRetryable(command, response))
|
if (response.getStatusCode() >= 500 && httpRetryHandler.retryRequest(command, response))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,8 @@ public class URLFetchServiceClient extends BaseHttpFutureCommandClient {
|
||||||
target, gaeResponse.getResponseCode(),
|
target, gaeResponse.getResponseCode(),
|
||||||
headersAsString(gaeResponse.getHeaders()));
|
headersAsString(gaeResponse.getHeaders()));
|
||||||
response = convert(gaeResponse);
|
response = convert(gaeResponse);
|
||||||
if (isRetryable(command, response))
|
int statusCode = response.getStatusCode();
|
||||||
|
if (statusCode >= 500 && httpRetryHandler.retryRequest(command, response))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,12 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.http.httpnio.pool;
|
package org.jclouds.http.httpnio.pool;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpEntityEnclosingRequest;
|
import org.apache.http.HttpEntityEnclosingRequest;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
|
@ -33,17 +38,17 @@ import org.apache.http.protocol.HttpContext;
|
||||||
import org.jclouds.http.HttpFutureCommand;
|
import org.jclouds.http.HttpFutureCommand;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponseHandler;
|
import org.jclouds.http.HttpResponseHandler;
|
||||||
|
import org.jclouds.http.HttpRetryHandler;
|
||||||
import org.jclouds.http.annotation.ClientErrorHandler;
|
import org.jclouds.http.annotation.ClientErrorHandler;
|
||||||
import org.jclouds.http.annotation.RedirectHandler;
|
import org.jclouds.http.annotation.RedirectHandler;
|
||||||
|
import org.jclouds.http.annotation.RetryHandler;
|
||||||
import org.jclouds.http.annotation.ServerErrorHandler;
|
import org.jclouds.http.annotation.ServerErrorHandler;
|
||||||
|
import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
|
||||||
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
import org.jclouds.http.handlers.CloseContentAndSetExceptionHandler;
|
||||||
import org.jclouds.http.httpnio.util.HttpNioUtils;
|
import org.jclouds.http.httpnio.util.HttpNioUtils;
|
||||||
import org.jclouds.logging.Logger;
|
import org.jclouds.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import com.google.inject.Inject;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // TODO: Adrian: Document this!
|
* // TODO: Adrian: Document this!
|
||||||
|
@ -70,6 +75,10 @@ public class HttpNioFutureCommandExecutionHandler implements
|
||||||
@Inject(optional = true)
|
@Inject(optional = true)
|
||||||
private HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
|
private HttpResponseHandler serverErrorHandler = new CloseContentAndSetExceptionHandler();
|
||||||
|
|
||||||
|
@RetryHandler
|
||||||
|
@Inject(optional = true)
|
||||||
|
protected HttpRetryHandler httpRetryHandler = new BackoffLimitedRetryHandler(5);
|
||||||
|
|
||||||
public interface ConsumingNHttpEntityFactory {
|
public interface ConsumingNHttpEntityFactory {
|
||||||
public ConsumingNHttpEntity create(HttpEntity httpEntity);
|
public ConsumingNHttpEntity create(HttpEntity httpEntity);
|
||||||
}
|
}
|
||||||
|
@ -115,11 +124,17 @@ public class HttpNioFutureCommandExecutionHandler implements
|
||||||
|
|
||||||
int code = response.getStatusCode();
|
int code = response.getStatusCode();
|
||||||
if (code >= 500) {
|
if (code >= 500) {
|
||||||
if (isRetryable(command)) {
|
boolean retryRequest = false;
|
||||||
commandQueue.add(command);
|
try {
|
||||||
} else {
|
retryRequest = httpRetryHandler.retryRequest(command, response);
|
||||||
this.serverErrorHandler.handle(command, response);
|
} catch (InterruptedException ie) {
|
||||||
}
|
// TODO: Add interrupt exception to command and abort?
|
||||||
|
}
|
||||||
|
if (retryRequest) {
|
||||||
|
commandQueue.add(command);
|
||||||
|
} else {
|
||||||
|
this.serverErrorHandler.handle(command, response);
|
||||||
|
}
|
||||||
} else if (code >= 400 && code < 500) {
|
} else if (code >= 400 && code < 500) {
|
||||||
this.clientErrorHandler.handle(command, response);
|
this.clientErrorHandler.handle(command, response);
|
||||||
} else if (code >= 300 && code < 400) {
|
} else if (code >= 300 && code < 400) {
|
||||||
|
@ -136,14 +151,6 @@ public class HttpNioFutureCommandExecutionHandler implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isRetryable(HttpFutureCommand<?> command) {
|
|
||||||
if (command.getRequest().isReplayable()) {
|
|
||||||
logger.debug("resubmitting command: %1$s", command);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void releaseConnectionToPool(
|
protected void releaseConnectionToPool(
|
||||||
HttpNioFutureCommandConnectionHandle handle) {
|
HttpNioFutureCommandConnectionHandle handle) {
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in New Issue