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 435ec7457..b2aa7a1d0 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 @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.commons.boot.app.cli; import java.io.File; import java.io.IOException; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -60,64 +61,22 @@ public class SpringBootApp { private VirtualMachine vm; private VirtualMachineDescriptor vmd; - private Supplier jmxConnect; - private static final Cache pidToJmxConnectUrl = CacheBuilder.newBuilder().build(); - - 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); - } - } - }) - .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(); - }; - } - - - private static Collection fetchRunningJavaApps() throws Exception { - List list = VirtualMachine.list(); - ImmutableList.Builder apps = ImmutableList.builder(); - for (VirtualMachineDescriptor vmd : list) { - apps.add(new SpringBootApp(vmd)); + private final Supplier jmxConnect = Suppliers.memoize(() -> { + try { + return vm.startLocalManagementAgent(); + } catch (Exception e) { + Log.log(e); + return null; } + }); - // Invalidate part of pidToJmxConnectUrl cache to remove old processes - Set oldProcesses = new HashSet<>(pidToJmxConnectUrl.asMap().keySet()); - oldProcesses.removeAll(list); - pidToJmxConnectUrl.invalidateAll(oldProcesses); - - return apps.build(); - } + private static SpringBootAppCache cache = new SpringBootAppCache(); public static Collection getAllRunningJavaApps() throws Exception { - return cachedJavaAppsGetter.call(); + return cache.getAllRunningJavaApps(); } - private static Callable> cachedJavaAppsGetter = cached(SpringBootApp::fetchRunningJavaApps); - /** * @return Map that contains the boot apps, mapping the process ID -> boot app accessor object */ @@ -128,7 +87,6 @@ public class SpringBootApp { public SpringBootApp(VirtualMachineDescriptor vmd) throws Exception { this.vmd = vmd; this.vm = VirtualMachine.attach(vmd); - this.jmxConnect = Suppliers.memoize(() -> getJmxConnectUrl()); System.err.println("SpringBootApp created: "+this); } @@ -140,17 +98,6 @@ public class SpringBootApp { return vmd.displayName(); } - private String getJmxConnectUrl() { - try { - return pidToJmxConnectUrl.get(vmd, () -> { - return vm.startLocalManagementAgent(); - }); - } catch (ExecutionException e) { - Log.log(e); - return null; - } - } - public String getHost() throws Exception { JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get()); return serviceUrl.getHost(); diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCache.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCache.java new file mode 100644 index 000000000..618d5d712 --- /dev/null +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppCache.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.boot.app.cli; + +import java.time.Duration; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import org.springframework.ide.vscode.commons.util.Log; + +import com.google.common.collect.ImmutableMap; +import com.sun.tools.attach.VirtualMachine; +import com.sun.tools.attach.VirtualMachineDescriptor; + +public class SpringBootAppCache { + + 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 apps = ImmutableMap.of(); + + public synchronized Collection getAllRunningJavaApps() { + if (System.currentTimeMillis()>=nextRefreshAfter) { + refresh(); + } + return apps.values(); + } + + private void refresh() { + List currentVms = VirtualMachine.list(); + HashSet oldVms = new HashSet<>(apps.keySet()); + ImmutableMap.Builder newApps = ImmutableMap.builder(); + for (VirtualMachineDescriptor vm : currentVms) { + oldVms.remove(vm); + SpringBootApp existingApp = apps.get(vm); + if (existingApp!=null) { + newApps.put(vm, existingApp); + } else { + try { + newApps.put(vm, new SpringBootApp(vm)); + } catch (Exception e) { + Log.log(e); + } + } + } + for (VirtualMachineDescriptor oldVm : oldVms) { + apps.get(oldVm).dispose(); + } + apps = newApps.build(); + nextRefreshAfter = System.currentTimeMillis() + EXPIRE_AFTER.toMillis(); + } +}