added more advanced caching strategy for calculating live hovers after live hover hints already got created
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -15,17 +15,22 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.MemoizingProxy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.sun.tools.attach.VirtualMachine;
|
||||
import com.sun.tools.attach.VirtualMachineDescriptor;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
|
||||
public class LocalSpringBootAppCache {
|
||||
|
||||
private static final Duration EXPIRE_AFTER = Duration.ofMillis(500); //Limits rate at which we refresh list of apps
|
||||
private long nextRefreshAfter = Long.MIN_VALUE;
|
||||
|
||||
private ImmutableMap<VirtualMachineDescriptor, LocalSpringBootApp> apps = ImmutableMap.of();
|
||||
private ImmutableMap<VirtualMachineDescriptor, SpringBootApp> apps = ImmutableMap.of();
|
||||
|
||||
public synchronized Collection<SpringBootApp> getAllRunningJavaApps() {
|
||||
if (System.currentTimeMillis()>=nextRefreshAfter) {
|
||||
@@ -36,14 +41,17 @@ public class LocalSpringBootAppCache {
|
||||
|
||||
private void refresh() {
|
||||
List<VirtualMachineDescriptor> currentVms = VirtualMachine.list();
|
||||
ImmutableMap.Builder<VirtualMachineDescriptor, LocalSpringBootApp> newAppsBuilder = ImmutableMap.builder();
|
||||
ImmutableMap.Builder<VirtualMachineDescriptor, SpringBootApp> newAppsBuilder = ImmutableMap.builder();
|
||||
for (VirtualMachineDescriptor vm : currentVms) {
|
||||
LocalSpringBootApp existingApp = apps.get(vm);
|
||||
SpringBootApp existingApp = apps.get(vm);
|
||||
if (existingApp!=null) {
|
||||
newAppsBuilder.put(vm, existingApp);
|
||||
} else {
|
||||
try {
|
||||
newAppsBuilder.put(vm, new LocalSpringBootApp(vm));
|
||||
LocalSpringBootApp localApp = new LocalSpringBootApp(vm);
|
||||
MethodInterceptor handler = new MemoizingProxy.MemoizingProxyHandler(localApp, Duration.ofMillis(4500));
|
||||
SpringBootApp proxiedLocalApp = (SpringBootApp) Enhancer.create(SpringBootApp.class, handler);
|
||||
newAppsBuilder.put(vm, proxiedLocalApp);
|
||||
} catch (Exception e) {
|
||||
//Ignore problems attaching to a VM. We will try again on next polling loop, if vm still exists.
|
||||
//The most likely cause is that the VM already died since we obtained a reference to it.
|
||||
@@ -51,7 +59,7 @@ public class LocalSpringBootAppCache {
|
||||
}
|
||||
}
|
||||
HashSet<VirtualMachineDescriptor> oldVms = new HashSet<>(apps.keySet());
|
||||
ImmutableMap<VirtualMachineDescriptor, LocalSpringBootApp> newApps = newAppsBuilder.build();
|
||||
ImmutableMap<VirtualMachineDescriptor, SpringBootApp> newApps = newAppsBuilder.build();
|
||||
oldVms.removeAll(newApps.keySet());
|
||||
for (VirtualMachineDescriptor oldVm : oldVms) {
|
||||
apps.get(oldVm).dispose();
|
||||
|
||||
@@ -40,12 +40,12 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
|
||||
|
||||
@Override
|
||||
public String getPort() throws Exception {
|
||||
return port!=null ? port : super.getPort();
|
||||
return port != null ? port : super.getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() throws Exception {
|
||||
if (host!=null) {
|
||||
if (host != null) {
|
||||
return host;
|
||||
}
|
||||
return super.getHost();
|
||||
@@ -66,7 +66,7 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
|
||||
public boolean isSpringBootApp() {
|
||||
//For now, let's assume that, if its not a boot app, then we won't create a RemoteSpringBootApp instance for it.
|
||||
//The check that is here really only determines whether there's a process reachable at the remote jmx url.
|
||||
return getProcessID()!=null;
|
||||
return getProcessID() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,9 +82,9 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
|
||||
public String getProcessName() throws Exception {
|
||||
try {
|
||||
String command = getJavaCommand();
|
||||
if (command!=null) {
|
||||
if (command != null) {
|
||||
int space = command.indexOf(' ');
|
||||
if (space>=0) {
|
||||
if (space >= 0) {
|
||||
command = command.substring(0, space);
|
||||
}
|
||||
command = command.trim();
|
||||
|
||||
@@ -89,4 +89,43 @@ public class MemoizingProxy {
|
||||
return (T) enhancer.create(argumentTypes, args);
|
||||
}
|
||||
|
||||
|
||||
public static class MemoizingProxyHandler implements MethodInterceptor {
|
||||
|
||||
private final Object original;
|
||||
private final Cache<String, Result> cache;
|
||||
|
||||
public MemoizingProxyHandler(Object original, Duration cacheExpiresAfter) {
|
||||
this.original = original;
|
||||
this.cache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(cacheExpiresAfter.toMillis(), TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
if (Modifier.isPublic(method.getModifiers()) && (args == null || args.length < 2)) {
|
||||
synchronized (cache) {
|
||||
String mname = method.getName();
|
||||
|
||||
if (args != null && args.length == 1) {
|
||||
mname += "-" + args[0].toString();
|
||||
}
|
||||
|
||||
Result r = cache.get(mname, () -> new Result(() -> {
|
||||
try {
|
||||
return method.invoke(original, args);
|
||||
} catch (Throwable e) {
|
||||
throw ExceptionUtil.exception(e);
|
||||
}
|
||||
}));
|
||||
return r.get();
|
||||
}
|
||||
} else {
|
||||
return method.invoke(original, args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.handlers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -58,14 +59,15 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
private JavaProjectFinder projectFinder;
|
||||
private BootJavaLanguageServerComponents server;
|
||||
private AnnotationHierarchyAwareLookup<HoverProvider> hoverProviders;
|
||||
private RunningAppProvider runningAppProvider;
|
||||
|
||||
private Collection<SpringBootApp> runningSpringBootApps;
|
||||
|
||||
public BootJavaHoverProvider(BootJavaLanguageServerComponents server, JavaProjectFinder projectFinder,
|
||||
AnnotationHierarchyAwareLookup<HoverProvider> specificProviders, RunningAppProvider runningAppProvider) {
|
||||
this.server = server;
|
||||
this.projectFinder = projectFinder;
|
||||
this.hoverProviders = specificProviders;
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
this.runningSpringBootApps = Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -262,10 +264,11 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
private Hover provideHoverForMethodDeclaration(MethodDeclaration methodDeclaration, int offset, TextDocument doc,
|
||||
IJavaProject project) {
|
||||
SpringBootApp[] runningApps = getRunningSpringApps(project);
|
||||
|
||||
if (runningApps.length > 0) {
|
||||
for (HoverProvider provider : this.hoverProviders.getAll()) {
|
||||
Hover hover = provider.provideHover(methodDeclaration, offset, doc, project, runningApps);
|
||||
if (hover!=null) {
|
||||
if (hover != null) {
|
||||
//TODO: compose multiple hovers somehow instead of just returning the first one?
|
||||
return hover;
|
||||
}
|
||||
@@ -278,8 +281,10 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
ITypeBinding type = annotation.resolveTypeBinding();
|
||||
if (type != null) {
|
||||
logger.debug("Hover requested for "+type.getName());
|
||||
|
||||
SpringBootApp[] runningApps = getRunningSpringApps(project);
|
||||
if (runningApps.length > 0) {
|
||||
|
||||
for (HoverProvider provider : this.hoverProviders.get(type)) {
|
||||
Hover hover = provider.provideHover(exactNode, annotation, type, offset, doc, project, runningApps);
|
||||
if (hover!=null) {
|
||||
@@ -347,11 +352,18 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
|
||||
private SpringBootApp[] getRunningSpringApps(IJavaProject project) {
|
||||
try {
|
||||
return RunningAppMatcher.getAllMatchingApps(runningAppProvider.getAllRunningSpringApps(), project).toArray(new SpringBootApp[0]);
|
||||
Collection<SpringBootApp> allApps = this.runningSpringBootApps;
|
||||
Collection<SpringBootApp> allMatchingApps = RunningAppMatcher.getAllMatchingApps(allApps, project);
|
||||
|
||||
return allMatchingApps.toArray(new SpringBootApp[0]);
|
||||
} catch (Exception e) {
|
||||
logger.error("error getting all matching projects for project'" + project.getElementName() + "'", e);
|
||||
return new SpringBootApp[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void setRunningSpringApps(Collection<SpringBootApp> allRunningSpringBootApps) {
|
||||
this.runningSpringBootApps = allRunningSpringBootApps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.lsp4j.CodeLens;
|
||||
@@ -33,8 +34,12 @@ import org.springframework.ide.vscode.commons.languageserver.HighlightParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.MemoizingProxy;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import net.sf.cglib.proxy.Enhancer;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@@ -155,6 +160,26 @@ public class SpringLiveHoverWatchdog {
|
||||
}
|
||||
}
|
||||
|
||||
protected void update() {
|
||||
if (this.watchedDocs.size() > 0) {
|
||||
try {
|
||||
Collection<SpringBootApp> runningBootApps = runningAppProvider.getAllRunningSpringApps();
|
||||
Collection<SpringBootApp> cachedApps = createAppCaches(runningBootApps);
|
||||
|
||||
for (String docURI : watchedDocs) {
|
||||
IJavaProject project = identifyProject(docURI);
|
||||
SpringBootApp[] matchingApps = RunningAppMatcher.getAllMatchingApps(cachedApps, project).toArray(new SpringBootApp[0]);
|
||||
update(docURI, matchingApps);
|
||||
}
|
||||
|
||||
this.hoverProvider.setRunningSpringApps(cachedApps);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void update(String docURI, SpringBootApp[] runningBootApps) {
|
||||
if (highlightsEnabled) {
|
||||
try {
|
||||
@@ -175,20 +200,20 @@ public class SpringLiveHoverWatchdog {
|
||||
}
|
||||
}
|
||||
|
||||
protected void update() {
|
||||
if (this.watchedDocs.size() > 0) {
|
||||
try {
|
||||
Collection<SpringBootApp> runningBootApps = runningAppProvider.getAllRunningSpringApps();
|
||||
private Collection<SpringBootApp> createAppCaches(Collection<SpringBootApp> runningBootApps) {
|
||||
return runningBootApps.stream().map(app -> {
|
||||
MethodInterceptor handler = new MemoizingProxy.MemoizingProxyHandler(app, Duration.ofMillis(20000));
|
||||
SpringBootApp proxied = (SpringBootApp) Enhancer.create(SpringBootApp.class, handler);
|
||||
|
||||
for (String docURI : watchedDocs) {
|
||||
IJavaProject project = identifyProject(docURI);
|
||||
SpringBootApp[] matchingApps = RunningAppMatcher.getAllMatchingApps(runningBootApps, project).toArray(new SpringBootApp[0]);
|
||||
update(docURI, matchingApps);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
proxied.getProcessName();
|
||||
proxied.getProcessID();
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
|
||||
return proxied;
|
||||
}).filter(app -> app != null).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private IJavaProject identifyProject(String docURI) {
|
||||
|
||||
Reference in New Issue
Block a user