From 768bcc8c5356db797bae1f1cc0ff7ef6a971176e Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Thu, 8 Aug 2019 17:18:06 -0700 Subject: [PATCH] Workaround for ls processes being leaked by lps4e See: - https://bugs.eclipse.org/bugs/show_bug.cgi?id=549904 - https://www.pivotaltracker.com/story/show/167519460 --- .../commons/LanguageServerProcessReaper.java | 33 ++++++++++++------- ...4LanguageServerProcessStreamConnector.java | 9 +++-- .../ls/JavaProjectsServiceWithFallback.java | 2 ++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/LanguageServerProcessReaper.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/LanguageServerProcessReaper.java index e89310a73..2914db8d3 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/LanguageServerProcessReaper.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/LanguageServerProcessReaper.java @@ -11,8 +11,9 @@ package org.springframework.tooling.ls.eclipse.commons; import java.lang.reflect.Field; +import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; +import java.util.Map; import org.eclipse.core.runtime.Platform; import org.eclipse.lsp4e.server.ProcessStreamConnectionProvider; @@ -27,17 +28,17 @@ import org.eclipse.lsp4e.server.ProcessStreamConnectionProvider; * 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: 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) { @@ -59,15 +60,23 @@ public class LanguageServerProcessReaper extends Thread { } } - LinkedList processes = new LinkedList<>(); + Map processes = new HashMap<>(); { Runtime.getRuntime().addShutdownHook(this); } - - public synchronized void addProcess(Process process) { + + public synchronized void addProcess(String id, Process process) { debug("added process: "+process); if (process!=null) { - processes.add(process); + Process existingProcess = processes.get(id); + //workaround for this bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=549904 + if (existingProcess!=null) { + if (existingProcess.isAlive()) { + debug("Destroying leaked process: "+process); + existingProcess.destroyForcibly(); + } + } + processes.put(id, process); } garbageCollect(); } @@ -79,7 +88,7 @@ public class LanguageServerProcessReaper extends Thread { */ protected void garbageCollect() { Process process; - Iterator iter = processes.iterator(); + Iterator iter = processes.values().iterator(); while (iter.hasNext()) { process = iter.next(); if (!process.isAlive()) { @@ -102,7 +111,7 @@ public class LanguageServerProcessReaper extends Thread { private synchronized void destroyProcesses() { debug("Destroying errant processes... "); garbageCollect(); - for (Process process : processes) { + for (Process process : processes.values()) { try { debug("Destroying process "+process); process.destroyForcibly(); diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java index 93419a09b..e7d7a7d53 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageServerProcessStreamConnector.java @@ -16,7 +16,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Path; import java.util.List; +import java.util.UUID; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IProgressMonitor; @@ -40,8 +42,10 @@ public abstract class STS4LanguageServerProcessStreamConnector extends ProcessSt private static LanguageServerProcessReaper processReaper = new LanguageServerProcessReaper(); private Supplier consoles = null; + private final String connectorId; public STS4LanguageServerProcessStreamConnector(ServerInfo server) { + this.connectorId = UUID.randomUUID().toString(); this.consoles = LanguageServerConsoles.getConsoleFactory(server); } @@ -49,7 +53,7 @@ public abstract class STS4LanguageServerProcessStreamConnector extends ProcessSt public void start() throws IOException { super.start(); Process process = LanguageServerProcessReaper.getProcess(this); - processReaper.addProcess(process); + processReaper.addProcess(connectorId, process); if (consoles!=null) { Console console = consoles.get(); if (console!=null) { @@ -146,12 +150,11 @@ public abstract class STS4LanguageServerProcessStreamConnector extends ProcessSt protected IStatus run(IProgressMonitor arg0) { try { pipe(is, os); - os.write("==== Process Terminated====\n".getBytes(Charsets.UTF_8)); } catch (IOException e) { - e.printStackTrace(); } finally { try { + os.write("==== Process Terminated====\n".getBytes(Charsets.UTF_8)); os.close(); } catch (IOException e) { } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsServiceWithFallback.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsServiceWithFallback.java index 4bae271ba..a976b9d41 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsServiceWithFallback.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JavaProjectsServiceWithFallback.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.jdt.ls; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -79,6 +80,7 @@ public class JavaProjectsServiceWithFallback implements JavaProjectsService { this.mainServiceInitialized = this.server .onInitialized(main.initialize()) + .timeout(Duration.ofSeconds(5)) .toFuture(); this.server.onShutdown(() -> {