diff --git a/core/src/main/java/org/jclouds/http/HttpRetryHandler.java b/core/src/main/java/org/jclouds/http/HttpRetryHandler.java new file mode 100644 index 0000000000..49ec5c2057 --- /dev/null +++ b/core/src/main/java/org/jclouds/http/HttpRetryHandler.java @@ -0,0 +1,53 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.http; + +/** + * Indicate whether a request should be retried after a server + * error response (HTTP status code >= 500) based on the request's + * replayable status and the number of attempts already performed. + * + * @author James Murty + */ +public interface HttpRetryHandler { + public static final HttpRetryHandler ALWAYS_RETRY = new HttpRetryHandler() { + public boolean retryRequest(HttpFutureCommand command, HttpResponse response) + { + return true; + } + }; + + /** + * Return true if the command should be retried. This method should only be + * invoked when the response has failed with a HTTP 5xx error indicating a + * server-side error. + * + * @param command + * @param response + * @return + * @throws InterruptedException + */ + boolean retryRequest(HttpFutureCommand command, HttpResponse response) + throws InterruptedException; +} diff --git a/core/src/main/java/org/jclouds/http/annotation/RetryHandler.java b/core/src/main/java/org/jclouds/http/annotation/RetryHandler.java new file mode 100644 index 0000000000..02b8e7d4b2 --- /dev/null +++ b/core/src/main/java/org/jclouds/http/annotation/RetryHandler.java @@ -0,0 +1,43 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.http.annotation; + +import com.google.inject.BindingAnnotation; +import java.lang.annotation.Target; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + +/** + * Implies that the object can address {@link org.jclouds.http.HttpRetryHandler}s. + * + * @author James Murty + */ +@BindingAnnotation +@Target( { FIELD, PARAMETER, METHOD }) +@Retention(RUNTIME) +public @interface RetryHandler { +} diff --git a/core/src/main/java/org/jclouds/http/handlers/BackoffLimitedRetryHandler.java b/core/src/main/java/org/jclouds/http/handlers/BackoffLimitedRetryHandler.java new file mode 100644 index 0000000000..eb49903dda --- /dev/null +++ b/core/src/main/java/org/jclouds/http/handlers/BackoffLimitedRetryHandler.java @@ -0,0 +1,93 @@ +/** + * + * Copyright (C) 2009 Adrian Cole + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.jclouds.http.handlers; + +import javax.annotation.Resource; + +import org.apache.commons.io.IOUtils; +import org.jclouds.http.HttpFutureCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpRetryHandler; +import org.jclouds.logging.Logger; + +import com.google.inject.Inject; +import com.google.inject.name.Named; + +/** + * Allow replayable request to be retried a limited number of times, and + * impose an exponential back-off delay before returning. + *

+ * The back-off delay grows rapidly according to the formula + * 50 * ({@link HttpFutureCommand#getFailureCount()} ^ 2). For example: + * + * + * + * + * + * + * + *
Number of AttemptsDelay in milliseconds
150
2200
3450
4800
51250
+ *

+ * This implementation has two side-effects. It increments the command's failure count + * with {@link HttpFutureCommand#incrementFailureCount()}, because this failure count + * value is used to determine how many times the command has already been tried. It + * also closes the response's content input stream to ensure connections are cleaned up. + * + * @author James Murty + */ +public class BackoffLimitedRetryHandler implements HttpRetryHandler { + private final int retryCountLimit; + + @Resource + protected Logger logger = Logger.NULL; + + @Inject + public BackoffLimitedRetryHandler(@Named("jclouds.http.max-retries") int retryCountLimit) { + this.retryCountLimit = retryCountLimit; + } + + public boolean retryRequest(HttpFutureCommand command, HttpResponse response) + throws InterruptedException + { + IOUtils.closeQuietly(response.getContent()); + + command.incrementFailureCount(); + + if (!command.getRequest().isReplayable()) { + logger.warn("Cannot retry after server error, command is not replayable: %1$s", command); + return false; + } else if (command.getFailureCount() > retryCountLimit) { + logger.warn("Cannot retry after server error, command has exceeded retry limit %1$d: %2$s", + retryCountLimit, command); + return false; + } else { + long delayMs = (long) (50L * Math.pow(command.getFailureCount(), 2)); + logger.debug("Retry %1$d/%2$d after server error, delaying for %3$d ms: %4$s", + command.getFailureCount(), retryCountLimit, delayMs, command); + Thread.sleep(delayMs); + return true; + } + } + +}