Fix for issue #253: allow users to override timeouts on sync interface

This commit is contained in:
ddurnev 2011-09-30 18:16:37 +04:00
parent 9cc9ecb229
commit cc5195f9e1
5 changed files with 116 additions and 5 deletions

View File

@ -230,4 +230,23 @@ public interface Constants {
*/
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.";
}

View File

@ -19,6 +19,7 @@
package org.jclouds.concurrent.internal;
import static com.google.common.base.Preconditions.checkState;
import static org.jclouds.Constants.PROPERTY_TIMEOUTS_PREFIX;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@ -32,6 +33,8 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import org.jclouds.concurrent.Timeout;
import org.jclouds.internal.ClassMethodArgs;
import org.jclouds.rest.annotations.Delegate;
@ -66,6 +69,13 @@ public class SyncProxy implements InvocationHandler {
private final Map<Class<?>, Class<?>> sync2Async;
private static final Set<Method> objectMethods = ImmutableSet.of(Object.class.getMethods());
@Inject
public void setProperties(@Named("CONSTANTS") Multimap<String, String> props) {
for (final Method method : timeoutMap.keySet()) {
overrideTimeout(declaring, method, props);
}
}
@Inject
public SyncProxy(Class<?> declaring, Object async,
@Named("sync") Cache<ClassMethodArgs, Object> delegateMap, Map<Class<?>, Class<?>> sync2Async)
@ -97,6 +107,7 @@ public class SyncProxy implements InvocationHandler {
} else {
timeoutMap.put(method, typeNanos);
}
methodMap.put(method, delegatedMethod);
} else {
syncMethodMap.put(method, delegatedMethod);
@ -139,6 +150,29 @@ public class SyncProxy implements InvocationHandler {
}
}
// override timeout by values configured in properties(in ms)
private void overrideTimeout(Class<?> declaring, Method method, Multimap<String, String> constants) {
if (constants == null) {
return;
}
final String classTimeouts = PROPERTY_TIMEOUTS_PREFIX + declaring.getSimpleName();
String strTimeout = Iterables.getFirst(constants.get(classTimeouts + "." + method.getName()), null);
if (strTimeout == null) {
strTimeout = Iterables.getFirst(constants.get(classTimeouts), null);
}
if (strTimeout != null) {
long timeout = 0l;
try {
timeout = Long.valueOf(strTimeout);
} catch (final Throwable t) {
timeout = 0l;
}
if (timeout > 0l) {
timeoutMap.put(method, TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS));
}
}
}
@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof SyncProxy))

View File

@ -62,8 +62,10 @@ public class ClientProvider<S, A> implements Provider<S> {
new TypeLiteral<Cache<ClassMethodArgs, Object>>() {
}, Names.named("sync")));
try {
return (S) SyncProxy.proxy(syncClientType, new SyncProxy(syncClientType, client,
delegateMap, sync2Async));
final SyncProxy syncProxy = new SyncProxy(syncClientType, client,
delegateMap, sync2Async);
injector.injectMembers(syncProxy);
return (S) SyncProxy.proxy(syncClientType, syncProxy);
} catch (Exception e) {
Throwables.propagate(e);
assert false : "should have propagated";

View File

@ -27,6 +27,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import com.google.inject.Injector;
import org.jclouds.concurrent.internal.SyncProxy;
import org.jclouds.internal.ClassMethodArgs;
@ -39,6 +40,8 @@ import com.google.common.cache.CacheLoader;
* @author Adrian Cole
*/
public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object> {
@Inject
Injector injector;
private final Cache<ClassMethodArgs, Object> asyncMap;
private final Provider<Cache<ClassMethodArgs, Object>> delegateMap;
Map<Class<?>, Class<?>> sync2Async;
@ -59,8 +62,10 @@ public class CreateClientForCaller extends CacheLoader<ClassMethodArgs, Object>
Object asyncClient = asyncMap.get(from);
checkState(asyncClient != null, "configuration error, sync client for " + from + " not found");
try {
return SyncProxy.proxy(syncClass, new SyncProxy(syncClass, asyncClient, delegateMap.get(),
sync2Async));
final SyncProxy syncProxy = new SyncProxy(syncClass, asyncClient, delegateMap.get(), sync2Async);
injector.injectMembers(syncProxy);
return SyncProxy.proxy(syncClass, syncProxy);
} catch (Exception e) {
Throwables.propagate(e);
assert false : "should have propagated";

View File

@ -29,6 +29,11 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.jclouds.concurrent.Futures;
import org.jclouds.concurrent.Timeout;
import org.jclouds.internal.ClassMethodArgs;
@ -44,6 +49,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Provides;
import javax.inject.Named;
import javax.inject.Singleton;
/**
* Tests behavior of ListenableFutureExceptionParser
*
@ -51,6 +59,21 @@ import com.google.inject.Provides;
*/
@Test(groups = "unit", singleThreaded = true)
public class SyncProxyTest {
Injector injector = Guice.createInjector(new AbstractModule() {
@SuppressWarnings("unused")
@Provides
@Singleton
@Named("CONSTANTS")
protected Multimap<String, String> constants() {
final Multimap<String, String> props = LinkedHashMultimap.create();
props.put("jclouds.timeouts.Sync.takeXMillisecondsPropOverride", "250");
return props;
}
@Override
protected void configure() {
}
});
@Test
void testConvertNanos() {
@ -77,6 +100,7 @@ public class SyncProxyTest {
@Timeout(duration = 300, timeUnit = TimeUnit.MILLISECONDS)
String take200MillisecondsAndOverride();
String takeXMillisecondsPropOverride(long ms);
}
static ExecutorService executorService = Executors.newCachedThreadPool();
@ -159,6 +183,21 @@ public class SyncProxyTest {
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;
@ -166,7 +205,9 @@ public class SyncProxyTest {
@BeforeTest
public void setUp() throws IllegalArgumentException, SecurityException, NoSuchMethodException {
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()));
final SyncProxy proxy = new SyncProxy(Sync.class, new Async(),cache, ImmutableMap.<Class<?>, Class<?>> of());
injector.injectMembers(proxy);
sync = SyncProxy.proxy(Sync.class, proxy);
// just to warm up
sync.string();
}
@ -198,6 +239,16 @@ public class SyncProxyTest {
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
public void testToString() {
assertEquals(sync.toString(), "Sync Proxy for: Async");