added convenience for retrying and getting a result

This commit is contained in:
Alex Heneveld 2011-12-01 23:20:22 +00:00
parent 7b72ef5cfc
commit d42469d450
5 changed files with 206 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();

View File

@ -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();
}
}

View File

@ -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"));
}
}