Issue 930:RetryingCacheLoaderDecorator

This commit is contained in:
Adrian Cole 2012-05-14 23:02:07 -07:00
parent 2a10087df9
commit db40facb2d
8 changed files with 820 additions and 0 deletions

View File

@ -0,0 +1,79 @@
/**
* 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.cache;
import java.util.Map;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ListenableFuture;
/**
* A {@link CacheLoader} which forwards all its method calls to another {@link CacheLoader}.
* Subclasses should override one or more methods to modify the behavior of the backing cache as
* desired per the <a href="http://en.wikipedia.org/wiki/Decorator_pattern">decorator pattern</a>.
*
* @author Adrian Cole
* @since 1.5
*/
@Beta
public abstract class ForwardingCacheLoader<K, V> extends CacheLoader<K, V> {
/** Constructor for use by subclasses. */
protected ForwardingCacheLoader() {
}
protected abstract CacheLoader<K, V> delegate();
@Override
public V load(K key) throws Exception {
return delegate().load(key);
}
@Override
public ListenableFuture<V> reload(K key, V oldValue) throws Exception {
return delegate().reload(key, oldValue);
}
@Override
public Map<K, V> loadAll(Iterable<? extends K> keys) throws Exception {
return delegate().loadAll(keys);
}
/**
* A simplified version of {@link ForwardingCacheLoader} where subclasses can pass in an already
* constructed {@link CacheLoader} as the delegete.
*
*/
@Beta
public static class SimpleForwardingCacheLoader<K, V> extends ForwardingCacheLoader<K, V> {
private final CacheLoader<K, V> delegate;
protected SimpleForwardingCacheLoader(CacheLoader<K, V> delegate) {
this.delegate = Preconditions.checkNotNull(delegate);
}
@Override
protected final CacheLoader<K, V> delegate() {
return delegate;
}
}
}

View File

@ -0,0 +1,196 @@
/**
* 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.cache;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.cache.internal.BackoffExponentiallyAndRetryOnThrowableCacheLoader;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.cache.CacheLoader;
/**
* <p>
* A decorator of {@link CacheLoader} instances having any combination of the following features:
*
* <ul>
* <li>exponential backoff based on a particular Throwable type
* </ul>
*
* These features are all optional; cache loaders can be created using all or none of them. By
* default, the input cache loader is returned unaffected.
*
* <p>
* Usage example:
*
* <pre>
* @code
*
* CacheLoader<Key, Graph> loader = RetryingCacheLoaderDecorator.newDecorator()
* .on(ResourceNotFoundException.class).exponentiallyBackoff()
* .decorate(
* new CacheLoader<Key, Graph>() {
* public Graph load(Key key) throws AnyException {
* return createOnFlakeyConnection(key);
* }
* });}
* </pre>
*
* @param <K>
* the base key type for all cache loaders created by this decorator
* @param <V>
* the base value type for all cache loaders created by this decorator
* @author Adrian Cole
* @since 1.5
*/
@Beta
public class RetryingCacheLoaderDecorator<K, V> {
protected RetryingCacheLoaderDecorator() {
}
/**
* Constructs a new {@code RetryingCacheLoaderDecorator} instance with default settings, and no
* retrying any kind.
*/
// we can resolve generic type during decorate, as opposed to here
public static RetryingCacheLoaderDecorator<Object, Object> newDecorator() {
return new RetryingCacheLoaderDecorator<Object, Object>();
}
/**
* Determines the action to carry out on a particular throwable.
*
*/
public OnThrowableBuilder<K, V> on(Class<? extends Throwable> retryableThrowable) {
return new OnThrowableBuilder<K, V>(retryableThrowable);
}
public static class OnThrowableBuilder<K, V> {
Class<? extends Throwable> retryableThrowable;
protected OnThrowableBuilder(Class<? extends Throwable> retryableThrowable) {
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
}
/**
* For each attempt, exponentially backoff
*/
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> exponentiallyBackoff() {
return new BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V>(retryableThrowable);
}
}
public static class BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> extends
RetryingCacheLoaderDecorator<K, V> {
private long periodMs = 100l;
private long maxPeriodMs = 200l;
private int maxTries = 5;
private final Class<? extends Throwable> retryableThrowable;
protected BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator(Class<? extends Throwable> retryableThrowable) {
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
}
/**
* The initial period in milliseconds to delay between tries. with each try this period will
* increase exponentially.
* <p/>
* default: {@code 100}
*
*/
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> periodMs(long periodMs) {
checkArgument(periodMs > 1, "maxTries must be positive: %d", periodMs);
this.periodMs = periodMs;
return this;
}
/**
* The initial period in milliseconds to delay between tries. with each try this period will
* increase exponentially.
* <p/>
* default: {@code 200}
*
*/
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> maxPeriodMs(long maxPeriodMs) {
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
maxPeriodMs, periodMs);
this.maxPeriodMs = maxPeriodMs;
return this;
}
/**
* The maximum attempts to try on the given exception type
* <p/>
* default: {@code 5}
*
*/
public BackoffExponentiallyAndRetryOnThrowableCacheLoaderDecorator<K, V> maxTries(int maxTries) {
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
this.maxTries = maxTries;
return this;
}
@Override
public <K1 extends K, V1 extends V> CacheLoader<K1, V1> decorate(CacheLoader<K1, V1> loader) {
return new BackoffExponentiallyAndRetryOnThrowableCacheLoader<K1, V1>(retryableThrowable, periodMs,
maxPeriodMs, maxTries, super.decorate(loader));
}
@Override
protected Objects.ToStringHelper string() {
return string().add("retryableThrowable", retryableThrowable).add("periodMs", periodMs).add("maxPeriodMs",
maxPeriodMs).add("maxTries", maxTries);
}
}
/**
* Decorates a cacheloader, or returns the same value, if no retrying features were requested.
*
* <p>
* This method does not alter the state of this {@code RetryingCacheLoaderDecorator} instance, so
* it can be invoked again to create multiple independent cache loaders.
*
* @param loader
* the cache loader used to obtain new values
* @return a cache loader having the requested features
*/
public <K1 extends K, V1 extends V> CacheLoader<K1, V1> decorate(CacheLoader<K1, V1> loader) {
return (CacheLoader<K1, V1>) loader;
}
/**
* Returns a string representation for this RetryingCacheLoaderDecorator instance. The exact form
* of the returned string is not specified.
*/
@Override
public String toString() {
return string().toString();
}
/**
* append any state that should be considered in {@link #toString} here.
*/
protected Objects.ToStringHelper string() {
return Objects.toStringHelper(this);
}
}

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.cache.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import java.util.concurrent.Callable;
import org.jclouds.cache.ForwardingCacheLoader;
import com.google.common.annotations.Beta;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Exponentially backs off, if we encounter an exception of the given type during any of the
* following methods:
* <ul>
* <li>load
* <li>reload
* <li>loadAll
* </ul>
*
* @param <K>
* the key type of the cache loader
* @param <V>
* the value type of the cache loader
* @author Adrian Cole
* @since 1.5
*/
@Beta
public class BackoffExponentiallyAndRetryOnThrowableCacheLoader<K, V> extends ForwardingCacheLoader<K, V> {
private final Class<? extends Throwable> retryableThrowable;
private final long periodMs;
private final long maxPeriodMs;
private final int maxTries;
private final CacheLoader<K, V> loader;
/**
*
* @param retryableThrowable
* the exception which we can retry
* @param periodMs
* initial period, which exponentially increases with each try, specified in
* milliseconds
* @param maxPeriodMs
* maximum period duration, specified in milliseconds
* @param maxTries
* maximum amount of tries
* @param loader
* the loader we are able to retry
*/
public BackoffExponentiallyAndRetryOnThrowableCacheLoader(Class<? extends Throwable> retryableThrowable, long periodMs,
long maxPeriodMs, int maxTries, CacheLoader<K, V> loader) {
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
this.maxTries = maxTries;
checkArgument(periodMs > 0, "periodMs must be positive: %d", periodMs);
this.periodMs = periodMs;
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
maxPeriodMs, periodMs);
this.maxPeriodMs = maxPeriodMs;
this.loader = checkNotNull(loader, "loader");
}
@Override
protected CacheLoader<K, V> delegate() {
return loader;
}
// TODO: refactor into a better closure in java pattern, if one exists
@Override
public V load(final K key) throws Exception {
return backoffExponentiallyAndRetryOnThrowable(new Callable<V>() {
@Override
public V call() throws Exception {
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.load(key);
}
});
}
@Override
public ListenableFuture<V> reload(final K key, final V oldValue) throws Exception {
return backoffExponentiallyAndRetryOnThrowable(new Callable<ListenableFuture<V>>() {
@Override
public ListenableFuture<V> call() throws Exception {
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.reload(key, oldValue);
}
});
}
@Override
public Map<K, V> loadAll(final Iterable<? extends K> keys) throws Exception {
return backoffExponentiallyAndRetryOnThrowable(new Callable<Map<K, V>>() {
@Override
public Map<K, V> call() throws Exception {
return BackoffExponentiallyAndRetryOnThrowableCacheLoader.super.loadAll(keys);
}
});
}
private <T> T backoffExponentiallyAndRetryOnThrowable(Callable<T> callable) throws Exception {
return new BackoffExponentiallyAndRetryOnThrowableCallable<T>(retryableThrowable, periodMs, maxPeriodMs,
maxTries, callable).call();
}
}

View File

@ -0,0 +1,99 @@
/**
* 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.cache.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.concurrent.Callable;
import org.jclouds.util.Throwables2;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.base.Throwables;
import com.google.common.collect.ForwardingObject;
/**
* Exponentially backs off, if we encounter an exception of the given type during
* {@link Callable#call}
*
* @author Adrian Cole
* @since 1.5
*/
@Beta
class BackoffExponentiallyAndRetryOnThrowableCallable<T> extends ForwardingObject implements Callable<T> {
private final Class<? extends Throwable> retryableThrowable;
private final long periodMs;
private final long maxPeriodMs;
private final int maxTries;
private final Callable<T> callable;
BackoffExponentiallyAndRetryOnThrowableCallable(Class<? extends Throwable> retryableThrowable, long periodMs,
long maxPeriodMs, int maxTries, Callable<T> callable) {
this.retryableThrowable = checkNotNull(retryableThrowable, "retryableThrowable");
checkArgument(maxTries > 1, "maxTries must be more than one: %d", maxTries);
this.maxTries = maxTries;
checkArgument(periodMs > 0, "periodMs must be positive: %d", periodMs);
this.periodMs = periodMs;
checkArgument(maxPeriodMs > periodMs, "maxPeriodMs must be equal to or greater than periodMs: %d %d",
maxPeriodMs, periodMs);
this.maxPeriodMs = maxPeriodMs;
this.callable = checkNotNull(callable, "callable");
}
@Override
protected Callable<T> delegate() {
return callable;
}
@Override
public T call() throws Exception {
Exception currentException = null;
for (int currentTries = 0; currentTries < maxTries; currentTries++) {
try {
return delegate().call();
} catch (Exception e) {
currentException = e;
if (Throwables2.getFirstThrowableOfType(e, retryableThrowable) != null) {
imposeBackoffExponentialDelay(periodMs, maxPeriodMs, 2, currentTries, maxTries);
} else {
throw e;
}
}
}
throw currentException;
}
void imposeBackoffExponentialDelay(long period, long maxPeriod, int pow, int failureCount, int max) {
long delayMs = (long) (period * Math.pow(failureCount, pow));
delayMs = delayMs > maxPeriod ? maxPeriod : delayMs;
try {
Thread.sleep(delayMs);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
@Override
public String toString() {
return Objects.toStringHelper("").add("retryableThrowable", retryableThrowable).add("periodMs", periodMs).add(
"maxPeriodMs", maxPeriodMs).add("maxTries", maxTries).add("callable", callable).toString();
}
}

View File

@ -0,0 +1,82 @@
/**
* 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.cache;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertSame;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
/**
* Unit test for {@link ForwardingCacheLoader}.
*
* @author Adrian Cole
*/
@Test(testName = "ForwardingCacheLoaderTest", singleThreaded = true)
public class ForwardingCacheLoaderTest {
private CacheLoader<String, Boolean> forward;
private CacheLoader<String, Boolean> mock;
@SuppressWarnings("unchecked")
@BeforeMethod
public void setUp() {
// add mocked methods for default forwarded ones
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
forward = new ForwardingCacheLoader<String, Boolean>() {
@Override
protected CacheLoader<String, Boolean> delegate() {
return mock;
}
};
}
public void testLoad() throws Exception {
expect(mock.load("key")).andReturn(Boolean.TRUE);
replay(mock);
assertSame(Boolean.TRUE, forward.load("key"));
verify(mock);
}
public void testReload() throws Exception {
ListenableFuture<Boolean> trueF = Futures.immediateFuture(true);
expect(mock.reload("key", false)).andReturn(trueF);
replay(mock);
assertSame(forward.reload("key", false), trueF);
verify(mock);
}
public void testLoadAll() throws Exception {
expect(mock.loadAll(ImmutableList.of("key"))).andReturn(ImmutableMap.of("key", Boolean.TRUE));
replay(mock);
assertEquals(ImmutableMap.of("key", Boolean.TRUE), forward.loadAll(ImmutableList.of("key")));
verify(mock);
}
}

View File

@ -0,0 +1,85 @@
/**
* 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.cache;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.cache.CacheLoader;
/**
* Unit tests for RetryingCacheLoaderDecorator.
*
* @author Adrian Cole
*/
@Test(testName = "ForwardingCacheLoaderTest", singleThreaded = true)
public class RetryingCacheLoaderDecoratorTest {
private CacheLoader<String, Boolean> mock;
@SuppressWarnings("unchecked")
@BeforeMethod
public void setUp() {
// add mocked methods for default forwarded ones
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
}
public void testNewDecoratorDecorateSameWhenNoParams() throws Exception {
assertSame(mock, RetryingCacheLoaderDecorator.newDecorator().decorate(mock));
}
@Test
void testDefaultMaxTriesIs5() throws Exception {
CacheLoader<String, Boolean> backoff = RetryingCacheLoaderDecorator.newDecorator().on(
ResourceNotFoundException.class).exponentiallyBackoff().decorate(mock);
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(4);
expect(mock.load("foo")).andReturn(Boolean.TRUE);
replay(mock);
assertSame(backoff.load("foo"), Boolean.TRUE);
verify(mock);
}
@Test
void testMaxRetriesExceededThrowsException() throws Exception {
CacheLoader<String, Boolean> backoff = RetryingCacheLoaderDecorator.newDecorator()
.on(ResourceNotFoundException.class).exponentiallyBackoff()
.decorate(mock);
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(5);
replay(mock);
try {
backoff.load("foo");
assertTrue(false);
} catch (ResourceNotFoundException e) {
}
verify(mock);
}
}

View File

@ -0,0 +1,76 @@
/**
* 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.cache.internal;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.cache.CacheLoader;
@Test(groups = "unit", testName = "BackoffExponentiallyAndRetryOnThrowableCacheLoaderTest")
public class BackoffExponentiallyAndRetryOnThrowableCacheLoaderTest {
private CacheLoader<String, Boolean> mock;
@SuppressWarnings("unchecked")
@BeforeMethod
public void setUp() {
// add mocked methods for default forwarded ones
mock = createMockBuilder(CacheLoader.class).addMockedMethods("loadAll", "reload").createMock();
}
@Test
void testMaxRetriesNotExceededReturnsValue() throws Exception {
int attempts = 3;
BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean> backoff = new BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean>(
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(attempts - 1);
expect(mock.load("foo")).andReturn(Boolean.TRUE);
replay(mock);
assertSame(backoff.load("foo"), Boolean.TRUE);
verify(mock);
}
@Test
void testMaxRetriesExceededThrowsException() throws Exception {
int attempts = 3;
BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean> backoff = new BackoffExponentiallyAndRetryOnThrowableCacheLoader<String, Boolean>(
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
expect(mock.load("foo")).andThrow(new ResourceNotFoundException()).times(attempts);
replay(mock);
try {
backoff.load("foo");
assertTrue(false);
} catch (ResourceNotFoundException e) {
}
verify(mock);
}
}

View File

@ -0,0 +1,75 @@
/**
* 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.cache.internal;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.util.concurrent.Callable;
import org.jclouds.rest.ResourceNotFoundException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
@Test(groups = "unit", testName = "BackoffExponentiallyAndRetryOnThrowableCallableTest")
public class BackoffExponentiallyAndRetryOnThrowableCallableTest {
private Callable<String> mock;
@SuppressWarnings("unchecked")
@BeforeMethod
public void setUp() {
mock = createMock(Callable.class);
}
@Test
void testMaxRetriesNotExceededReturnsValue() throws Exception {
int attempts = 3;
BackoffExponentiallyAndRetryOnThrowableCallable<String> backoff = new BackoffExponentiallyAndRetryOnThrowableCallable<String>(
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
expect(mock.call()).andThrow(new ResourceNotFoundException()).times(attempts - 1);
expect(mock.call()).andReturn("foo");
replay(mock);
assertEquals(backoff.call(), "foo");
verify(mock);
}
@Test
void testMaxRetriesExceededThrowsException() throws Exception {
int attempts = 3;
BackoffExponentiallyAndRetryOnThrowableCallable<String> backoff = new BackoffExponentiallyAndRetryOnThrowableCallable<String>(
ResourceNotFoundException.class, 50l, 500l, attempts, mock);
expect(mock.call()).andThrow(new ResourceNotFoundException()).times(attempts);
replay(mock);
try {
backoff.call();
assertTrue(false);
} catch (ResourceNotFoundException e) {
}
verify(mock);
}
}