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.
|
||||
* 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
|
||||
*/
|
||||
|
@ -92,6 +97,7 @@ public class RetryablePredicate<T> implements Predicate<T> {
|
|||
}
|
||||
|
||||
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));
|
||||
interval = interval > maxPeriod ? maxPeriod : interval;
|
||||
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