mirror of https://github.com/apache/jclouds.git
added convenience for retrying and getting a result
This commit is contained in:
parent
7b72ef5cfc
commit
d42469d450
|
@ -0,0 +1,43 @@
|
||||||
|
package org.jclouds.predicates;
|
||||||
|
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
public abstract class PredicateCallable<Result> implements PredicateWithResult<Void, Result>, Callable<Result> {
|
||||||
|
|
||||||
|
Result lastResult;
|
||||||
|
Exception lastFailure;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Void input) {
|
||||||
|
try {
|
||||||
|
lastResult = call();
|
||||||
|
onCompletion();
|
||||||
|
return isAcceptable(lastResult);
|
||||||
|
} catch (Exception e) {
|
||||||
|
lastFailure = e;
|
||||||
|
onFailure();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onFailure() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onCompletion() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAcceptable(Result result) {
|
||||||
|
return result!=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result getResult() {
|
||||||
|
return lastResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable getLastFailure() {
|
||||||
|
return lastFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.jclouds.predicates;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
|
public interface PredicateWithResult<Input,Result> extends Predicate<Input> {
|
||||||
|
|
||||||
|
Result getResult();
|
||||||
|
|
||||||
|
Throwable getLastFailure();
|
||||||
|
|
||||||
|
}
|
|
@ -34,6 +34,11 @@ import com.google.common.base.Predicate;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Retries a condition until it is met or a timeout occurs.
|
* Retries a condition until it is met or a timeout occurs.
|
||||||
|
* maxWait parameter is required.
|
||||||
|
* Initial retry period and retry maxPeriod are optionally configurable,
|
||||||
|
* defaulting to 50ms and 1000ms respectively,
|
||||||
|
* with the retrier increasing the interval by a factor of 1.5 each time within these constraints.
|
||||||
|
* All values taken as millis unless TimeUnit specified.
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
|
@ -92,6 +97,7 @@ public class RetryablePredicate<T> implements Predicate<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected long nextMaxInterval(long attempt, Date end) {
|
protected long nextMaxInterval(long attempt, Date end) {
|
||||||
|
// FIXME i think this should be pow(1.5, attempt) -- or alternatively newInterval = oldInterval*1.5
|
||||||
long interval = (period * (long) Math.pow(attempt, 1.5));
|
long interval = (period * (long) Math.pow(attempt, 1.5));
|
||||||
interval = interval > maxPeriod ? maxPeriod : interval;
|
interval = interval > maxPeriod ? maxPeriod : interval;
|
||||||
long max = end.getTime() - System.currentTimeMillis();
|
long max = end.getTime() - System.currentTimeMillis();
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.jclouds.predicates;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.common.base.Predicate;
|
||||||
|
|
||||||
|
public class Retryables {
|
||||||
|
|
||||||
|
public static <Input> boolean retry(Predicate<Input> predicate, Input input, long maxWaitMillis) {
|
||||||
|
return new RetryablePredicate<Input>(predicate, maxWaitMillis).apply(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <Input> boolean retry(Predicate<Input> predicate, Input input, long maxWait, long period, TimeUnit unit) {
|
||||||
|
return new RetryablePredicate<Input>(predicate, maxWait, period, unit).apply(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <Input> void assertEventually(Predicate<Input> predicate, Input input,
|
||||||
|
long maxWaitMillis, String failureMessage) {
|
||||||
|
if (!new RetryablePredicate<Input>(predicate, maxWaitMillis).apply(input))
|
||||||
|
throw new AssertionError(failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <Input,Result> Result retryGettingResultOrFailing(PredicateWithResult<Input,Result> predicate,
|
||||||
|
Input input, long maxWaitMillis, String failureMessage) {
|
||||||
|
if (!new RetryablePredicate<Input>(predicate, maxWaitMillis).apply(input))
|
||||||
|
throw (AssertionError)new AssertionError(failureMessage).initCause(predicate.getLastFailure());
|
||||||
|
return predicate.getResult();
|
||||||
|
}
|
||||||
|
public static <Input,Result> Result retryGettingResultOrFailing(PredicateWithResult<Input,Result> predicate,
|
||||||
|
Input input, long maxWait, long period, TimeUnit unit, String failureMessage) {
|
||||||
|
if (!new RetryablePredicate<Input>(predicate, maxWait, period, unit).apply(input))
|
||||||
|
throw (AssertionError)new AssertionError(failureMessage).initCause(predicate.getLastFailure());
|
||||||
|
return predicate.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.jclouds.predicates;
|
||||||
|
|
||||||
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
import static org.jclouds.predicates.Retryables.retry;
|
||||||
|
import static org.jclouds.predicates.Retryables.retryGettingResultOrFailing;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public class RetryablesTest {
|
||||||
|
|
||||||
|
public static class FindX implements PredicateWithResult<String,Character> {
|
||||||
|
Character result;
|
||||||
|
Throwable lastFailure;
|
||||||
|
int attempts=0;
|
||||||
|
@Override
|
||||||
|
public boolean apply(String input) {
|
||||||
|
try {
|
||||||
|
result = input.charAt(attempts++);
|
||||||
|
return (result=='x');
|
||||||
|
} catch (Exception e) {
|
||||||
|
lastFailure = e;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Character getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public Throwable getLastFailure() {
|
||||||
|
return lastFailure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPredicateWithResult() {
|
||||||
|
FindX findX = new FindX();
|
||||||
|
assertFalse(findX.apply("hexy"));
|
||||||
|
assertEquals((char)findX.getResult(), 'h');
|
||||||
|
assertFalse(findX.apply("hexy"));
|
||||||
|
assertTrue(findX.apply("hexy"));
|
||||||
|
assertEquals((char)findX.getResult(), 'x');
|
||||||
|
|
||||||
|
assertFalse(findX.apply("hexy"));
|
||||||
|
assertNull(findX.getLastFailure());
|
||||||
|
//now we get error
|
||||||
|
assertFalse(findX.apply("hexy"));
|
||||||
|
assertNotNull(findX.getLastFailure());
|
||||||
|
assertEquals((char)findX.getResult(), 'y');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRetry() {
|
||||||
|
FindX findX = new FindX();
|
||||||
|
assertTrue(retry(findX, "hexy", 1000, 1, MILLISECONDS));
|
||||||
|
assertEquals(findX.attempts, 3);
|
||||||
|
assertEquals((char)findX.getResult(), 'x');
|
||||||
|
assertNull(findX.getLastFailure());
|
||||||
|
|
||||||
|
//now we'll be getting errors
|
||||||
|
assertFalse(retry(findX, "hexy", 100, 1, MILLISECONDS));
|
||||||
|
assertEquals((char)findX.getResult(), 'y');
|
||||||
|
assertNotNull(findX.getLastFailure());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRetryGetting() {
|
||||||
|
FindX findX = new FindX();
|
||||||
|
assertEquals((char)retryGettingResultOrFailing(findX, "hexy", 1000, "shouldn't happen"), 'x');
|
||||||
|
|
||||||
|
//now we'll be getting errors
|
||||||
|
boolean secondRetrySucceeds=false;
|
||||||
|
try {
|
||||||
|
retryGettingResultOrFailing(findX, "hexy", 100, "expected");
|
||||||
|
secondRetrySucceeds = true;
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertTrue(e.toString().contains("expected"));
|
||||||
|
}
|
||||||
|
if (secondRetrySucceeds) fail("should have thrown");
|
||||||
|
assertNotNull(findX.getLastFailure());
|
||||||
|
assertFalse(findX.getLastFailure().toString().contains("expected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
//using PredicateCallable we can repeat the above test, with the job expressed more simply
|
||||||
|
public static class FindXSimpler extends PredicateCallable<Character> {
|
||||||
|
String input = "hexy";
|
||||||
|
int attempts=0;
|
||||||
|
public Character call() {
|
||||||
|
return input.charAt(attempts++);
|
||||||
|
}
|
||||||
|
public boolean isAcceptable(Character result) {
|
||||||
|
return result=='x';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSimplerPredicateCallableRetryGetting() {
|
||||||
|
FindXSimpler findX = new FindXSimpler();
|
||||||
|
assertEquals((char)retryGettingResultOrFailing(findX, null, 1000, "shouldn't happen"), 'x');
|
||||||
|
|
||||||
|
//now we'll be getting errors
|
||||||
|
boolean secondRetrySucceeds=false;
|
||||||
|
try {
|
||||||
|
retryGettingResultOrFailing(findX, null, 100, "expected");
|
||||||
|
secondRetrySucceeds = true;
|
||||||
|
} catch (AssertionError e) {
|
||||||
|
assertTrue(e.toString().contains("expected"));
|
||||||
|
}
|
||||||
|
if (secondRetrySucceeds) fail("should have thrown");
|
||||||
|
assertNotNull(findX.getLastFailure());
|
||||||
|
assertFalse(findX.getLastFailure().toString().contains("expected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue