From fb3af5c1e3a6b7fa6c9f62a387bb63626bdd0ab4 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Wed, 1 Nov 2017 14:26:32 -0700 Subject: [PATCH] Use guava cache to better manage apps from getAllRunning(Spring|Java)Apps --- concourse/pipeline.yml | 2 +- .../java/handlers/RunningAppProvider.java | 13 +-- .../commons/boot/app/cli/SpringBootApp.java | 107 ++++++++++++------ .../boot/app/cli/SpringBootAppCLI.java | 7 +- .../boot/app/cli/SpringBootAppTest.java | 10 +- .../commons/commons-util/pom.xml | 2 +- .../commons/java-properties/pom.xml | 2 +- 7 files changed, 87 insertions(+), 56 deletions(-) diff --git a/concourse/pipeline.yml b/concourse/pipeline.yml index 504d5051c..9919e759a 100644 --- a/concourse/pipeline.yml +++ b/concourse/pipeline.yml @@ -314,7 +314,7 @@ jobs: trigger: true - get: maven-cache - task: build-boot-java-vsix-snapshot - attempts: 3 #Because its a bit flaky with the maven bits + #attempts: 3 #Because its a bit flaky with the maven bits file: sts4/concourse/tasks/build-vsix.yml params: extension_id: vscode-boot-java diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/RunningAppProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/RunningAppProvider.java index 8378aa2ab..7ccf04f77 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/RunningAppProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/RunningAppProvider.java @@ -18,17 +18,8 @@ import com.google.common.collect.ImmutableList; public interface RunningAppProvider { - public static final RunningAppProvider DEFAULT = new RunningAppProvider() { - @Override public Collection getAllRunningSpringApps() throws Exception { - return SpringBootApp.getAllRunningSpringApps().values(); - } - }; - - public static final RunningAppProvider NULL = new RunningAppProvider() { - @Override public Collection getAllRunningSpringApps() throws Exception { - return ImmutableList.of(); - } - }; + public static final RunningAppProvider DEFAULT = SpringBootApp::getAllRunningSpringApps; + public static final RunningAppProvider NULL = () -> ImmutableList.of(); Collection getAllRunningSpringApps() throws Exception; } diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index 661326865..87c34b786 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -22,6 +22,9 @@ import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import javax.management.InstanceNotFoundException; import javax.management.MBeanServerConnection; @@ -32,13 +35,17 @@ import javax.management.remote.JMXServiceURL; import org.json.JSONArray; import org.json.JSONObject; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1; -import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; +import org.springframework.ide.vscode.commons.util.CollectorUtil; +import org.springframework.ide.vscode.commons.util.Futures; import org.springframework.ide.vscode.commons.util.Log; -import org.springframework.ide.vscode.commons.util.StringUtil; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; @@ -51,44 +58,65 @@ public class SpringBootApp { private VirtualMachine vm; private VirtualMachineDescriptor vmd; - /** - * @return Map that contains the boot apps, mapping the process ID -> boot app accessor object - */ - public static Map getAllRunningJavaApps() throws Exception { - Map result = new HashMap<>(); - List list = VirtualMachine.list(); - for (VirtualMachineDescriptor vmd : list) { - SpringBootApp app = new SpringBootApp(vmd); - result.put(app.getProcessID(), app); - } - - return result; - } - - /** - * @return Map that contains the boot apps, mapping the process ID -> boot app accessor object - */ - public static Map getAllRunningSpringApps() throws Exception { - Map result = new HashMap<>(); - List list = VirtualMachine.list(); - for (VirtualMachineDescriptor vmd : list) { - try { - SpringBootApp app = new SpringBootApp(vmd); - if (app.isSpringBootApp()) { - result.put(app.getProcessID(), app); + private static Callable> cached(Callable> provider) { + LoadingCache>> cache = CacheBuilder.newBuilder() + .expireAfterWrite(500, TimeUnit.MILLISECONDS) + .removalListener(removalNotification -> { + @SuppressWarnings("unchecked") + CompletableFuture> removed = (CompletableFuture>) removalNotification.getValue(); + if (!removed.isCompletedExceptionally()) { + try { + Collection apps = removed.get(); + for (SpringBootApp springBootApp : apps) { + springBootApp.dispose(); + } + } catch (Exception e) { + Log.log(e); } } - catch (Exception e) { - System.err.println("cannot attach to app: " + vmd.id()); + }) + .build(new CacheLoader>>() { + @Override public CompletableFuture> load(Object key) { + try { + return CompletableFuture.completedFuture(provider.call()); + } catch (Throwable e) { + return Futures.error(e); + } } - } + }); + return () -> { + Object key = SpringBootApp.class; //This key really doesn't matter, as long as we use the same non-null object each time. + return cache.get(key).get(); + }; + } - return result; + + private static Collection fetchRunningJavaApps() throws Exception { + List list = VirtualMachine.list(); + ImmutableList.Builder apps = ImmutableList.builder(); + for (VirtualMachineDescriptor vmd : list) { + apps.add(new SpringBootApp(vmd)); + } + return apps.build(); + } + + public static Collection getAllRunningJavaApps() throws Exception { + return cachedJavaAppsGetter.call(); + } + + private static Callable> cachedJavaAppsGetter = cached(SpringBootApp::fetchRunningJavaApps); + + /** + * @return Map that contains the boot apps, mapping the process ID -> boot app accessor object + */ + public static Collection getAllRunningSpringApps() throws Exception { + return getAllRunningJavaApps().stream().filter(SpringBootApp::isSpringBootApp).collect(CollectorUtil.toImmutableList()); } public SpringBootApp(VirtualMachineDescriptor vmd) throws Exception { this.vmd = vmd; this.vm = VirtualMachine.attach(vmd); + System.err.println("SpringBootApp created: "+this); } public String getProcessID() { @@ -105,7 +133,7 @@ public class SpringBootApp { return serviceUrl.getHost(); } - public boolean isSpringBootApp() throws Exception { + public boolean isSpringBootApp() { return !containsSystemProperty("sts4.languageserver.name") && ( isSpringBootAppClasspath() || @@ -435,7 +463,6 @@ public class SpringBootApp { _profiles = env.opt("profiles"); //Boot 1.5 } if (_profiles instanceof JSONArray) { - @SuppressWarnings("unchecked") JSONArray profiles = (JSONArray) _profiles; ImmutableList.Builder list = ImmutableList.builder(); for (Object object : profiles) { @@ -452,5 +479,19 @@ public class SpringBootApp { return null; } + public void dispose() { + if (vm!=null) { + System.err.println("SpringBootApp disposed: "+this); + try { + vm.detach(); + } catch (Exception e) { + } + vm = null; + } + if (vmd!=null) { + vmd = null; + } + } + } diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCLI.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCLI.java index 6162fde41..71b4704c5 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCLI.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCLI.java @@ -10,17 +10,16 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.boot.app.cli; -import java.util.Map; +import java.util.Collection; /** * @author Martin Lippert */ -@SuppressWarnings("restriction") public class SpringBootAppCLI { public static void main(String[] args) throws Exception { - Map allRunningJavaApps = SpringBootApp.getAllRunningJavaApps(); - for (SpringBootApp app : allRunningJavaApps.values()) { + Collection allRunningJavaApps = SpringBootApp.getAllRunningJavaApps(); + for (SpringBootApp app : allRunningJavaApps) { if (app.isSpringBootApp()) { printBootAppDetails(app); } diff --git a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java index 501ae697f..79b704a0f 100644 --- a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java +++ b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java @@ -90,7 +90,7 @@ public class SpringBootAppTest { private SpringBootApp getAppContaining(String nameFragment) { try { - return SpringBootApp.getAllRunningJavaApps().values().stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get(); + return SpringBootApp.getAllRunningJavaApps().stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get(); } catch (Exception e) { throw ExceptionUtil.unchecked(e); } @@ -107,17 +107,17 @@ public class SpringBootAppTest { } @Test public void getAllJavaApps() throws Exception { - Map allApps = SpringBootApp.getAllRunningJavaApps(); + Collection allApps = SpringBootApp.getAllRunningJavaApps(); for (String appName : appNames) { - Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); + Optional myProcess = allApps.stream().filter(app -> app.getProcessName().contains(appName)).findAny(); assertTrue(appName, myProcess.isPresent()); } } @Test public void getAllBootApps() throws Exception { - Map allApps = SpringBootApp.getAllRunningSpringApps(); + Collection allApps = SpringBootApp.getAllRunningSpringApps(); for (String appName : appNames) { - Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); + Optional myProcess = allApps.stream().filter(app -> app.getProcessName().contains(appName)).findAny(); assertTrue(myProcess.isPresent()); } } diff --git a/headless-services/commons/commons-util/pom.xml b/headless-services/commons/commons-util/pom.xml index 671de3ca2..8f9f6e1ad 100644 --- a/headless-services/commons/commons-util/pom.xml +++ b/headless-services/commons/commons-util/pom.xml @@ -37,7 +37,7 @@ com.google.guava guava - 18.0 + ${guava-version} javax.inject diff --git a/headless-services/commons/java-properties/pom.xml b/headless-services/commons/java-properties/pom.xml index 5232de5c0..6144f8027 100644 --- a/headless-services/commons/java-properties/pom.xml +++ b/headless-services/commons/java-properties/pom.xml @@ -22,7 +22,7 @@ com.google.guava guava - 18.0 + ${guava-version}