diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java index 7e776553c..7e7bf68d0 100644 --- a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/CloudFoundryManifestLanguageServer.java @@ -13,6 +13,7 @@ package org.springframework.tooling.cloudfoundry.manifest.ls; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; import java.net.URI; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -38,6 +39,8 @@ import org.osgi.framework.Bundle; @SuppressWarnings("restriction") public class CloudFoundryManifestLanguageServer extends ProcessStreamConnectionProvider { + private static LanguageServerProcessReaper processReaper = new LanguageServerProcessReaper(); + private LanguageServer languageServer; private URI rootPath; @@ -85,6 +88,7 @@ public class CloudFoundryManifestLanguageServer extends ProcessStreamConnectionP public void stop() { removeLanguageServer(this); super.stop(); + processReaper.removeProcess(this.getProcess()); } @Override @@ -108,6 +112,25 @@ public class CloudFoundryManifestLanguageServer extends ProcessStreamConnectionP return null; } + @Override + public void start() throws IOException { + super.start(); + processReaper.addProcess(getProcess()); + } + + private Process getProcess() { + try { + //The super class is doesn't provide a way to get at the process without using reflection... + // This method can be removed if / when the super-class provides a getProcess method we can call. + Field processField = ProcessStreamConnectionProvider.class.getDeclaredField("process"); + processField.setAccessible(true); + Process process = (Process) processField.get(this); + return process; + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + return null; + } + } + protected String getLanguageServerJARLocation() { String languageServer = "manifest-yaml-language-server-" + Constants.LANGUAGE_SERVER_VERSION; diff --git a/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/LanguageServerProcessReaper.java b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/LanguageServerProcessReaper.java new file mode 100644 index 000000000..f50b3d46a --- /dev/null +++ b/eclipse-language-servers/org.springframework.tooling.cloudfoundry.manifest.ls/src/org/springframework/tooling/cloudfoundry/manifest/ls/LanguageServerProcessReaper.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2017 Spring IDE Developers + * 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: + * Spring IDE Developers - initial API and implementation + *******************************************************************************/ +package org.springframework.tooling.cloudfoundry.manifest.ls; + +import java.util.Iterator; +import java.util.LinkedList; + +import org.eclipse.core.runtime.Platform; + +/** + * A 'last resort cleanup' utility for LSP server processes. This class, when instantiated + * registers a JVM shutdown hook. When the hook is executed it kills of all tracked processes + * with the most aggressive process termination method available through the JRE. + *
+ * This is meant only as a kind of 'fail-safe' as it is expected that lps4e does proper + * house-keeping and cleans up these processes automatically and cleanly. So... unless + * something goes wrong inside lsp4e then the `destroyProcesses` method really should + * just be iterating an empty collection of processes. I.e. the typical debug output + * on shutdown should look something like this: + * + *
+ * LanguageServerProcessReaper: Destroying errant processes... + * LanguageServerProcessReaper: Number of alive processes = 0 + * LanguageServerProcessReaper: Destroying errant processes... DONE + *+ * + * @author Kris De Volder + */ +public class LanguageServerProcessReaper extends Thread { + + private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder"); + + private static void debug(String string) { + if (DEBUG) { + System.out.println("LanguageServerProcessReaper: "+string); + } + } + + LinkedList