Merge pull request #199 from ahgittin/765-retry-made-easier

added convenience for retrying and getting a result - issue 765
This commit is contained in:
Adrian Cole 2011-12-02 07:37:53 -08:00
commit da96b57c37
5 changed files with 292 additions and 0 deletions

View File

@ -0,0 +1,66 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.predicates;
import java.util.concurrent.Callable;
import com.google.common.annotations.Beta;
/** Provides a facility to convert an arbitrary Callable to a Predicate, implementing PredicateWithResult,
* for use e.g. with Retryables.retryGetting... methods */
@Beta
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,31 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.predicates;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
@Beta
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.
* 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();

View File

@ -0,0 +1,61 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.predicates;
import java.util.concurrent.TimeUnit;
import com.google.common.annotations.Beta;
import com.google.common.base.Predicate;
/** convenience methods to retry application of a predicate, and optionally (reducing quite a bit of boilerplate)
* get the final result or throw assertion error
*
* @author alex heneveld
*/
@Beta
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,128 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.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"));
}
}