org.eclipse.jdt.ls.core.internal.ParentProcessWatcher.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jdt.ls.core.internal.ParentProcessWatcher.java

Source

/*******************************************************************************
 * Copyright (c) 2017 Red Hat Inc. and others.
 * 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:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.ls.core.internal;

import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.eclipse.core.runtime.Platform;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;

import com.google.common.io.Closeables;

/**
 * Watches the parent process PID and invokes exit if it is no longer available.
 * This implementation waits for periods of inactivity to start querying the PIDs.
 */
public final class ParentProcessWatcher implements Runnable, Function<MessageConsumer, MessageConsumer> {

    private static final long INACTIVITY_DELAY_SECS = 30 * 1000;
    private static final boolean isJava1x = System.getProperty("java.version").startsWith("1.");
    private static final int POLL_DELAY_SECS = 10;
    private volatile long lastActivityTime;
    private final LanguageServer server;
    private ScheduledFuture<?> task;
    private ScheduledExecutorService service;

    public ParentProcessWatcher(LanguageServer server) {
        this.server = server;
        service = Executors.newScheduledThreadPool(1);
        task = service.scheduleWithFixedDelay(this, POLL_DELAY_SECS, POLL_DELAY_SECS, TimeUnit.SECONDS);
    }

    @Override
    public void run() {
        if (!parentProcessStillRunning()) {
            JavaLanguageServerPlugin.logInfo("Parent process stopped running, forcing server exit");
            task.cancel(true);
            server.exit();
        }
    }

    /**
     * Checks whether the parent process is still running.
     * If not, then we assume it has crashed, and we have to terminate the Java Language Server.
     *
     * @return true if the parent process is still running
     */
    private boolean parentProcessStillRunning() {
        // Wait until parent process id is available
        final long pid = server.getParentProcessId();
        if (pid == 0 || lastActivityTime > (System.currentTimeMillis() - INACTIVITY_DELAY_SECS)) {
            return true;
        }
        String command;
        if (Platform.OS_WIN32.equals(Platform.getOS())) {
            command = "cmd /c \"tasklist /FI \"PID eq " + pid + "\" | findstr " + pid + "\"";
        } else {
            command = "ps -p " + pid;
        }
        Process process = null;
        boolean finished = false;
        try {
            process = Runtime.getRuntime().exec(command);
            finished = process.waitFor(POLL_DELAY_SECS, TimeUnit.SECONDS);
            if (!finished) {
                process.destroy();
                finished = process.waitFor(POLL_DELAY_SECS, TimeUnit.SECONDS); // wait for the process to stop
            }
            if (Platform.OS_WIN32.equals(Platform.getOS()) && finished && process.exitValue() > 1) {
                // the tasklist command should return 0 (parent process exists) or 1 (parent process doesn't exist)
                JavaLanguageServerPlugin
                        .logInfo("The tasklist command: '" + command + "' returns " + process.exitValue());
                return true;
            }
            return !finished || process.exitValue() == 0;
        } catch (IOException | InterruptedException e) {
            JavaLanguageServerPlugin.logException(e.getMessage(), e);
            return true;
        } finally {
            if (process != null) {
                if (!finished) {
                    process.destroyForcibly();
                }
                // Terminating or destroying the Process doesn't close the process handle on Windows.
                // It is only closed when the Process object is garbage collected (in its finalize() method).
                // On Windows, when the Java LS is idle, we need to explicitly request a GC,
                // to prevent an accumulation of zombie processes, as finalize() will be called.
                if (Platform.OS_WIN32.equals(Platform.getOS())) {
                    // Java >= 9 doesn't close the handle when the process is garbage collected
                    // We need to close the opened streams
                    if (!isJava1x) {
                        Closeables.closeQuietly(process.getInputStream());
                        Closeables.closeQuietly(process.getErrorStream());
                        try {
                            Closeables.close(process.getOutputStream(), false);
                        } catch (IOException e) {
                        }
                    }
                    System.gc();
                }
            }
        }
    }

    @Override
    public MessageConsumer apply(final MessageConsumer consumer) {
        //inject our own consumer to refresh the timestamp
        return message -> {
            lastActivityTime = System.currentTimeMillis();
            consumer.consume(message);
        };
    }
}