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:
Kris De Volder
2017-06-28 11:02:46 -07:00
committed by GitHub
2 changed files with 123 additions and 0 deletions

View File

@@ -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;

View File

@@ -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");
}
}