mirror of https://github.com/apache/jclouds.git
Merge pull request #84 from ddurnev/master
Issue 253: allow users to override timeouts on sync interface
This commit is contained in:
commit
a7d1f38266
|
@ -230,4 +230,23 @@ public interface Constants {
|
||||||
*/
|
*/
|
||||||
public static final String PROPERTY_CREDENTIAL = "jclouds.credential";
|
public static final String PROPERTY_CREDENTIAL = "jclouds.credential";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long properties
|
||||||
|
* <p/>
|
||||||
|
* Overrides timeouts on sync interfaces. Timeout value is in ms.
|
||||||
|
* Here's an example of an override for a single method:
|
||||||
|
* <p/>
|
||||||
|
* <code>
|
||||||
|
* #10 seconds <br/>
|
||||||
|
* jclouds.timeouts.S3Client.bucketExists=10000
|
||||||
|
* </code>
|
||||||
|
* <p/>
|
||||||
|
* Or for all methods:
|
||||||
|
* <p/>
|
||||||
|
* <code>
|
||||||
|
* jclouds.timeouts.GridServerClient = 350000
|
||||||
|
* </code>
|
||||||
|
*/
|
||||||
|
public static final String PROPERTY_TIMEOUTS_PREFIX = "jclouds.timeouts.";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
import org.jclouds.concurrent.Timeout;
|
import org.jclouds.concurrent.Timeout;
|
||||||
import org.jclouds.internal.ClassMethodArgs;
|
import org.jclouds.internal.ClassMethodArgs;
|
||||||
import org.jclouds.rest.annotations.Delegate;
|
import org.jclouds.rest.annotations.Delegate;
|
||||||
|
@ -52,9 +54,12 @@ import com.google.inject.ProvisionException;
|
||||||
public class SyncProxy implements InvocationHandler {
|
public class SyncProxy implements InvocationHandler {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T proxy(Class<T> clazz, SyncProxy proxy) throws IllegalArgumentException, SecurityException,
|
public static <T> T proxy(Class<T> clazz, Object async,
|
||||||
|
@Named("sync") Cache<ClassMethodArgs, Object> delegateMap,
|
||||||
|
Map<Class<?>, Class<?>> sync2Async, Map<String, Long> timeouts) throws IllegalArgumentException, SecurityException,
|
||||||
NoSuchMethodException {
|
NoSuchMethodException {
|
||||||
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz }, proxy);
|
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[] { clazz },
|
||||||
|
new SyncProxy(clazz, async, delegateMap, sync2Async, timeouts));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Object delegate;
|
private final Object delegate;
|
||||||
|
@ -67,8 +72,9 @@ public class SyncProxy implements InvocationHandler {
|
||||||
private static final Set<Method> objectMethods = ImmutableSet.of(Object.class.getMethods());
|
private static final Set<Method> objectMethods = ImmutableSet.of(Object.class.getMethods());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SyncProxy(Class<?> declaring, Object async,
|
private SyncProxy(Class<?> declaring, Object async,
|
||||||
@Named("sync") Cache<ClassMethodArgs, Object> delegateMap, Map<Class<?>, Class<?>> sync2Async)
|
@Named("sync") Cache<ClassMethodArgs, Object> delegateMap, Map<Class<?>,
|
||||||
|
Class<?>> sync2Async, final Map<String, Long> timeouts)
|
||||||
throws SecurityException, NoSuchMethodException {
|
throws SecurityException, NoSuchMethodException {
|
||||||
this.delegateMap = delegateMap;
|
this.delegateMap = delegateMap;
|
||||||
this.delegate = async;
|
this.delegate = async;
|
||||||
|
@ -90,13 +96,7 @@ public class SyncProxy implements InvocationHandler {
|
||||||
throw new IllegalArgumentException(String.format(
|
throw new IllegalArgumentException(String.format(
|
||||||
"method %s has different typed exceptions than delegated method %s", method, delegatedMethod));
|
"method %s has different typed exceptions than delegated method %s", method, delegatedMethod));
|
||||||
if (delegatedMethod.getReturnType().isAssignableFrom(ListenableFuture.class)) {
|
if (delegatedMethod.getReturnType().isAssignableFrom(ListenableFuture.class)) {
|
||||||
if (method.isAnnotationPresent(Timeout.class)) {
|
timeoutMap.put(method, getTimeout(method, typeNanos, timeouts));
|
||||||
Timeout methodTimeout = method.getAnnotation(Timeout.class);
|
|
||||||
long methodNanos = convertToNanos(methodTimeout);
|
|
||||||
timeoutMap.put(method, methodNanos);
|
|
||||||
} else {
|
|
||||||
timeoutMap.put(method, typeNanos);
|
|
||||||
}
|
|
||||||
methodMap.put(method, delegatedMethod);
|
methodMap.put(method, delegatedMethod);
|
||||||
} else {
|
} else {
|
||||||
syncMethodMap.put(method, delegatedMethod);
|
syncMethodMap.put(method, delegatedMethod);
|
||||||
|
@ -105,6 +105,16 @@ public class SyncProxy implements InvocationHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long getTimeout(Method method, long typeNanos, final Map<String,Long> timeouts) {
|
||||||
|
Long timeout = overrideTimeout(method, timeouts);
|
||||||
|
if (timeout == null && method.isAnnotationPresent(Timeout.class)) {
|
||||||
|
Timeout methodTimeout = method.getAnnotation(Timeout.class);
|
||||||
|
timeout = convertToNanos(methodTimeout);
|
||||||
|
}
|
||||||
|
return timeout != null ? timeout : typeNanos;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static long convertToNanos(Timeout timeout) {
|
static long convertToNanos(Timeout timeout) {
|
||||||
long methodNanos = TimeUnit.NANOSECONDS.convert(timeout.duration(), timeout.timeUnit());
|
long methodNanos = TimeUnit.NANOSECONDS.convert(timeout.duration(), timeout.timeUnit());
|
||||||
return methodNanos;
|
return methodNanos;
|
||||||
|
@ -139,6 +149,19 @@ public class SyncProxy implements InvocationHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override timeout by values configured in properties(in ms)
|
||||||
|
private Long overrideTimeout(final Method method, final Map<String, Long> timeouts) {
|
||||||
|
if (timeouts == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String className = declaring.getSimpleName();
|
||||||
|
Long timeout = timeouts.get(className + "." + method.getName());
|
||||||
|
if (timeout == null) {
|
||||||
|
timeout = timeouts.get(className);
|
||||||
|
}
|
||||||
|
return timeout != null ? TimeUnit.MILLISECONDS.toNanos(timeout) : null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == null || !(obj instanceof SyncProxy))
|
if (obj == null || !(obj instanceof SyncProxy))
|
||||||
|
|
|
@ -36,19 +36,17 @@ import static org.jclouds.Constants.PROPERTY_ENDPOINT;
|
||||||
import static org.jclouds.Constants.PROPERTY_IDENTITY;
|
import static org.jclouds.Constants.PROPERTY_IDENTITY;
|
||||||
import static org.jclouds.Constants.PROPERTY_ISO3166_CODES;
|
import static org.jclouds.Constants.PROPERTY_ISO3166_CODES;
|
||||||
import static org.jclouds.Constants.PROPERTY_PROVIDER;
|
import static org.jclouds.Constants.PROPERTY_PROVIDER;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
import org.jclouds.concurrent.MoreExecutors;
|
import org.jclouds.concurrent.MoreExecutors;
|
||||||
import org.jclouds.concurrent.SingleThreaded;
|
import org.jclouds.concurrent.SingleThreaded;
|
||||||
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
import org.jclouds.concurrent.config.ConfiguresExecutorService;
|
||||||
|
@ -72,10 +70,6 @@ import org.jclouds.rest.internal.RestContextImpl;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.ImmutableMultimap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.LinkedHashMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
|
@ -117,6 +111,23 @@ public class RestContextBuilder<S, A> {
|
||||||
return LinkedHashMultimap.create(builder.build());
|
return LinkedHashMultimap.create(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("TIMEOUTS")
|
||||||
|
protected Map<String, Long> timeouts() {
|
||||||
|
final ImmutableMap.Builder<String, Long> builder = ImmutableMap.<String, Long> builder();
|
||||||
|
for (final Entry<Object, Object> entry : properties.entrySet()) {
|
||||||
|
final String key = String.valueOf(entry.getKey());
|
||||||
|
if (key.startsWith(PROPERTY_TIMEOUTS_PREFIX) && entry.getValue() != null) {
|
||||||
|
try {
|
||||||
|
final Long val = Long.valueOf(String.valueOf(entry.getValue()));
|
||||||
|
builder.put(key.replaceFirst(PROPERTY_TIMEOUTS_PREFIX, ""), val);
|
||||||
|
} catch (final Throwable t) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
Properties toBind = new Properties();
|
Properties toBind = new Properties();
|
||||||
|
|
|
@ -63,7 +63,9 @@ public class ClientProvider<S, A> implements Provider<S> {
|
||||||
new TypeLiteral<Cache<ClassMethodArgs, Object>>() {
|
new TypeLiteral<Cache<ClassMethodArgs, Object>>() {
|
||||||
}, Names.named("sync")));
|
}, Names.named("sync")));
|
||||||
try {
|
try {
|
||||||
return (S) SyncProxy.proxy(syncClientType, new SyncProxy(syncClientType, client, delegateMap, sync2Async));
|
return (S) SyncProxy.proxy(syncClientType, client, delegateMap, sync2Async,
|
||||||
|
injector.getInstance(Key.get(new TypeLiteral<Map<String, Long>>() {
|
||||||
|
}, Names.named("TIMEOUTS"))));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagate(e);
|
Throwables.propagate(e);
|
||||||
assert false : "should have propagated";
|
assert false : "should have propagated";
|
||||||
|
|
|
@ -27,6 +27,10 @@ import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
|
||||||
|
import com.google.inject.Injector;
|
||||||
|
import com.google.inject.Key;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
import com.google.inject.name.Names;
|
||||||
import org.jclouds.concurrent.internal.SyncProxy;
|
import org.jclouds.concurrent.internal.SyncProxy;
|
||||||
import org.jclouds.internal.ClassMethodArgs;
|
import org.jclouds.internal.ClassMethodArgs;
|
||||||
|
|
||||||
|
@ -42,6 +46,8 @@ import com.google.common.cache.CacheLoader;
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object> {
|
public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object> {
|
||||||
|
@Inject
|
||||||
|
Injector injector;
|
||||||
private final Cache<ClassMethodArgs, Object> asyncMap;
|
private final Cache<ClassMethodArgs, Object> asyncMap;
|
||||||
private final Provider<Cache<ClassMethodArgs, Object>> delegateMap;
|
private final Provider<Cache<ClassMethodArgs, Object>> delegateMap;
|
||||||
Map<Class<?>, Class<?>> sync2Async;
|
Map<Class<?>, Class<?>> sync2Async;
|
||||||
|
@ -61,7 +67,9 @@ public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object>
|
||||||
Object asyncClient = asyncMap.get(from);
|
Object asyncClient = asyncMap.get(from);
|
||||||
checkState(asyncClient != null, "configuration error, sync client for " + from + " not found");
|
checkState(asyncClient != null, "configuration error, sync client for " + from + " not found");
|
||||||
try {
|
try {
|
||||||
return SyncProxy.proxy(syncClass, new SyncProxy(syncClass, asyncClient, delegateMap.get(), sync2Async));
|
return SyncProxy.proxy(syncClass, asyncClient, delegateMap.get(), sync2Async,
|
||||||
|
injector.getInstance(Key.get(new TypeLiteral<Map<String, Long>>() {
|
||||||
|
}, Names.named("TIMEOUTS"))));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Throwables.propagate(e);
|
Throwables.propagate(e);
|
||||||
assert false : "should have propagated";
|
assert false : "should have propagated";
|
||||||
|
|
|
@ -77,6 +77,7 @@ public class SyncProxyTest {
|
||||||
@Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS)
|
@Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS)
|
||||||
String take200MillisecondsAndOverride();
|
String take200MillisecondsAndOverride();
|
||||||
|
|
||||||
|
String takeXMillisecondsPropOverride(long ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ExecutorService executorService = Executors.newCachedThreadPool();
|
static ExecutorService executorService = Executors.newCachedThreadPool();
|
||||||
|
@ -159,6 +160,21 @@ public class SyncProxyTest {
|
||||||
return take200MillisecondsAndTimeout();
|
return take200MillisecondsAndTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<String> takeXMillisecondsPropOverride(final long ms) {
|
||||||
|
return Futures.makeListenable(executorService.submit(new Callable<String>() {
|
||||||
|
|
||||||
|
public String call() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(ms);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
}), executorService);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Sync sync;
|
private Sync sync;
|
||||||
|
@ -166,7 +182,8 @@ public class SyncProxyTest {
|
||||||
@BeforeTest
|
@BeforeTest
|
||||||
public void setUp() throws IllegalArgumentException, SecurityException, NoSuchMethodException {
|
public void setUp() throws IllegalArgumentException, SecurityException, NoSuchMethodException {
|
||||||
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
||||||
sync = SyncProxy.proxy(Sync.class, new SyncProxy(Sync.class, new Async(),cache, ImmutableMap.<Class<?>, Class<?>> of()));
|
sync = SyncProxy.proxy(Sync.class, new Async(),cache, ImmutableMap.<Class<?>, Class<?>> of(),
|
||||||
|
ImmutableMap.of("Sync.takeXMillisecondsPropOverride", 250L));
|
||||||
// just to warm up
|
// just to warm up
|
||||||
sync.string();
|
sync.string();
|
||||||
}
|
}
|
||||||
|
@ -198,6 +215,16 @@ public class SyncProxyTest {
|
||||||
assertEquals(sync.take200MillisecondsAndOverride(), "foo");
|
assertEquals(sync.take200MillisecondsAndOverride(), "foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTake200MillisecondsPropOverride() {
|
||||||
|
assertEquals(sync.takeXMillisecondsPropOverride(200), "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = RuntimeException.class)
|
||||||
|
public void testTake300MillisecondsPropTimeout() {
|
||||||
|
assertEquals(sync.takeXMillisecondsPropOverride(300), "foo");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testToString() {
|
public void testToString() {
|
||||||
assertEquals(sync.toString(), "Sync Proxy for: Async");
|
assertEquals(sync.toString(), "Sync Proxy for: Async");
|
||||||
|
@ -229,8 +256,8 @@ public class SyncProxyTest {
|
||||||
public void testWrongTypedException() throws IllegalArgumentException, SecurityException, NoSuchMethodException,
|
public void testWrongTypedException() throws IllegalArgumentException, SecurityException, NoSuchMethodException,
|
||||||
IOException {
|
IOException {
|
||||||
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
||||||
SyncProxy.proxy(SyncWrongException.class, new SyncProxy(SyncWrongException.class, new Async(),
|
SyncProxy.proxy(SyncWrongException.class, new Async(), cache, ImmutableMap.<Class<?>, Class<?>> of(),
|
||||||
cache, ImmutableMap.<Class<?>, Class<?>> of()));
|
ImmutableMap.<String, Long>of());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static interface SyncNoTimeOut {
|
private static interface SyncNoTimeOut {
|
||||||
|
@ -248,8 +275,30 @@ public class SyncProxyTest {
|
||||||
public void testNoTimeOutException() throws IllegalArgumentException, SecurityException, NoSuchMethodException,
|
public void testNoTimeOutException() throws IllegalArgumentException, SecurityException, NoSuchMethodException,
|
||||||
IOException {
|
IOException {
|
||||||
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
||||||
SyncProxy.proxy(SyncNoTimeOut.class, new SyncProxy(SyncNoTimeOut.class, new Async(),
|
SyncProxy.proxy(SyncNoTimeOut.class, new Async(),
|
||||||
cache, ImmutableMap.<Class<?>, Class<?>> of()));
|
cache, ImmutableMap.<Class<?>, Class<?>> of(), ImmutableMap.<String, Long>of());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Timeout(duration = 30, timeUnit = TimeUnit.SECONDS)
|
||||||
|
private static interface SyncClassOverride {
|
||||||
|
String getString();
|
||||||
|
|
||||||
|
String newString();
|
||||||
|
|
||||||
|
String getRuntimeException();
|
||||||
|
|
||||||
|
@Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS)
|
||||||
|
String takeXMillisecondsPropOverride(long ms);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expectedExceptions = RuntimeException.class)
|
||||||
|
public void testClassOverridePropTimeout() throws Exception {
|
||||||
|
Cache<ClassMethodArgs, Object> cache = CacheBuilder.newBuilder().build(CacheLoader.from(Functions.<Object>constant(null)));
|
||||||
|
final SyncClassOverride sync2 = SyncProxy.proxy(SyncClassOverride.class, new Async(),
|
||||||
|
cache, ImmutableMap.<Class<?>, Class<?>> of(), ImmutableMap.<String, Long>of("SyncClassOverride", 100L));
|
||||||
|
|
||||||
|
assertEquals(sync2.takeXMillisecondsPropOverride(200), "foo");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue