Merge pull request #10 from spring-projects/lsp-process-reaper
Implement a 'fallback' lsp process reaper to cleanup errant processes.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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:
|
||||
*
|
||||
* <pre>
|
||||
* LanguageServerProcessReaper: Destroying errant processes...
|
||||
* LanguageServerProcessReaper: Number of alive processes = 0
|
||||
* LanguageServerProcessReaper: Destroying errant processes... DONE
|
||||
* </pre>
|
||||
*
|
||||
* @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<Process> processes = new LinkedList<>();
|
||||
{
|
||||
Runtime.getRuntime().addShutdownHook(this);
|
||||
}
|
||||
|
||||
public synchronized void addProcess(Process process) {
|
||||
debug("added process: "+process);
|
||||
if (process!=null) {
|
||||
processes.add(process);
|
||||
}
|
||||
garbageCollect();
|
||||
}
|
||||
|
||||
/**
|
||||
* If processes are managed reasonably by lsp4e then we don't expect the list of
|
||||
* actually 'alive' processes to keep on growing. This checks for and removes
|
||||
* processes that are already dead.
|
||||
*/
|
||||
protected void garbageCollect() {
|
||||
Process process;
|
||||
Iterator<Process> iter = processes.iterator();
|
||||
while (iter.hasNext()) {
|
||||
process = iter.next();
|
||||
if (!process.isAlive()) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
debug("Number of alive processes = "+processes.size());
|
||||
}
|
||||
|
||||
public synchronized void removeProcess(Process process) {
|
||||
debug("removeProcess "+process);
|
||||
garbageCollect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
destroyProcesses();
|
||||
}
|
||||
|
||||
private synchronized void destroyProcesses() {
|
||||
debug("Destroying errant processes... ");
|
||||
garbageCollect();
|
||||
for (Process process : processes) {
|
||||
try {
|
||||
debug("Destroying process "+process);
|
||||
process.destroyForcibly();
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
debug("Destroying errant processes... DONE");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user