Improve CachingModelProvider to wrap a cache around the models it provides

This commit is contained in:
Kris De Volder
2017-07-27 13:09:12 -07:00
parent 593a74e12e
commit a92accde86
4 changed files with 53 additions and 18 deletions

View File

@@ -108,9 +108,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
) {
this.asts = asts;
this.astTypes = astTypes;
this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider);
this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider);
this.releasesProvider = new CachingModelProvider<>(releasesProvider);
this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider, CloudConfigModel.class);
this.stemcellsProvider = new CachingModelProvider<>(stemcellsProvider, StemcellsModel.class);
this.releasesProvider = new CachingModelProvider<>(releasesProvider, ReleasesModel.class);
TYPE_UTIL = f.TYPE_UTIL;
V2_TOPLEVEL_TYPE = createV2Schema();

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.bosh.models;
import java.lang.reflect.Proxy;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -35,9 +36,11 @@ public class CachingModelProvider<T> implements DynamicModelProvider<T> {
private Cache<Object, CompletableFuture<T>> cache = createCache();
private final DynamicModelProvider<T> delegate;
private Class<T> modelInterface;
public CachingModelProvider(DynamicModelProvider<T> delegate) {
public CachingModelProvider(DynamicModelProvider<T> delegate, Class<T> modelInterface) {
this.delegate = delegate;
this.modelInterface = modelInterface;
}
/**
@@ -72,15 +75,37 @@ public class CachingModelProvider<T> implements DynamicModelProvider<T> {
synchronized (this) {
cached = cache.get(key, () -> {
try {
return CompletableFuture.completedFuture(delegate.getModel(dc));
return CompletableFuture.completedFuture(wrapWithCachingProxy(delegate.getModel(dc)));
} catch (Throwable e) {
CompletableFuture<T> failed = new CompletableFuture<>();
failed.completeExceptionally(e);
return failed;
return failed(e);
}
});
}
return cached.get();
}
private static <T> CompletableFuture<T> failed(Throwable e) {
CompletableFuture<T> failed = new CompletableFuture<>();
failed.completeExceptionally(e);
return failed;
}
@SuppressWarnings("unchecked")
private T wrapWithCachingProxy(T model) {
Cache<String, CompletableFuture<Object>> attributesCache = CacheBuilder.newBuilder().build();
return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> {
//We only support caching results for methods that have no arguments (for now, its all we need).
if (m.getParameterTypes().length==0) {
return attributesCache.get(m.getName(), () -> {
try {
return CompletableFuture.completedFuture((T)m.invoke(model, a));
} catch (Throwable e) {
return failed(e);
}
}).get();
}
return m.invoke(model, a);
});
}
}

View File

@@ -12,9 +12,12 @@ package org.springframework.ide.vscode.bosh.models;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
@@ -23,24 +26,31 @@ import org.springframework.ide.vscode.commons.util.ExceptionUtil;
@SuppressWarnings("unchecked")
public class CachingModelProviderTest {
interface BoxModel {
String getContents();
}
@Test public void goodValuesAreCached() throws Exception {
DynamicModelProvider<String> modelProvider = mock(DynamicModelProvider.class);
when(modelProvider.getModel(any())).thenReturn("RESULT");
DynamicModelProvider<BoxModel> modelProvider = mock(DynamicModelProvider.class);
BoxModel model = mock(BoxModel.class);
when(modelProvider.getModel(any())).thenReturn(model);
when(model.getContents()).thenReturn("RESULT");
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider);
DynamicModelProvider<BoxModel> cached = new CachingModelProvider<>(modelProvider, BoxModel.class);
assertEquals("RESULT", cached.getModel(null));
assertEquals("RESULT", cached.getModel(null));
assertEquals("RESULT", cached.getModel(null));
assertEquals("RESULT", cached.getModel(null).getContents());
assertEquals("RESULT", cached.getModel(null).getContents());
assertEquals("RESULT", cached.getModel(null).getContents());
verify(modelProvider, times(1)).getModel(any());
verify(model, times(1)).getContents(); //model itself is also wrapped in a cache!
}
@Test public void timeoutExceptionsAreCached() throws Exception {
DynamicModelProvider<String> modelProvider = mock(DynamicModelProvider.class);
when(modelProvider.getModel(any())).thenThrow(new TimeoutException("timed out"));
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider);
DynamicModelProvider<String> cached = new CachingModelProvider<>(modelProvider, String.class);
for (int i = 0; i < 3; i++) {
try {
cached.getModel(null);

View File

@@ -52,7 +52,7 @@ public class SimpleDefinitionFinder<T extends SimpleLanguageServer> implements D
* currently pointed at in the current document using String.indexOf.
*/
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
try {
try {
TextDocument doc = server.getTextDocumentService().get(params);
if (doc != null) {
int offset = doc.toOffset(params.getPosition());