/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.maven.server.execution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Handler for native OS process * * @author Evgen Vidolob */ public class ProcessHandler implements Executor { private static final Logger LOG = LoggerFactory.getLogger(ProcessHandler.class); private final Process process; private final WaitForProcessEnd waitForProcess; private final ExecutorService executorService; private final ProcessListener listenerNotifier; private final TerminatingTaskRunner terminatingListener; private final CountDownLatch latch; private final List<ProcessListener> listeners = new CopyOnWriteArrayList<>(); private volatile ProcessState state = ProcessState.INITIAL; public ProcessHandler(Process process) { executorService = createExecutorService(); this.process = process; waitForProcess = new WaitForProcessEnd(process, this); listenerNotifier = createNotifier(); terminatingListener = new TerminatingTaskRunner(); addProcessListener(terminatingListener); latch = new CountDownLatch(1); } private ProcessListener createNotifier() { InvocationHandler invocationHandler = (proxy, method, args) -> { //call method over all listeners for (ProcessListener listener : listeners) { method.invoke(listener, args); } return null; }; return (ProcessListener)Proxy .newProxyInstance(ProcessListener.class.getClassLoader(), new Class[] {ProcessListener.class}, invocationHandler); } private ExecutorService createExecutorService() { return new ThreadPoolExecutor(10, Integer.MAX_VALUE, 1, TimeUnit.MINUTES, new SynchronousQueue<>(), (r -> new Thread(r, "Native process polled Thread"))); } public boolean isProcessTerminating() { return false; } public boolean isProcessTerminated() { return false; } public void addProcessListener(ProcessListener processListener) { listeners.add(processListener); } public void startNotify() { addProcessListener(new ProcessListener() { @Override public void onStart(ProcessEvent event) { try { OutputReader stdOutReader = createStdOutReader(); stdOutReader.start(); OutputReader stdErrReader = createStdErrReader(); stdErrReader.start(); waitForProcess.setEndCallback(exitCode -> { try { stdErrReader.stop(); stdOutReader.stop(); try { stdErrReader.waitFor(); stdOutReader.waitFor(); } catch (InterruptedException ignore) { } } finally { ProcessHandler.this.onProcessTerminated(exitCode); latch.countDown(); } }); } finally { removeProcessListener(this); } } @Override public void onText(ProcessEvent event, ProcessOutputType outputType) { } @Override public void onProcessTerminated(ProcessEvent event) { } @Override public void onProcessWillTerminate(ProcessEvent event) { } }); state = ProcessState.RUNNING; listenerNotifier.onStart(new ProcessEvent(this)); } private void onProcessTerminated(int exitCode) { terminatingListener.runTask(() -> { if (state == ProcessState.RUNNING) { state = ProcessState.TERMINATING; notifyOnTerminating(); } if (state == ProcessState.TERMINATING) { state = ProcessState.TERMINATED; listenerNotifier.onProcessTerminated(new ProcessEvent(ProcessHandler.this, exitCode)); } }); } private void removeProcessListener(ProcessListener listener) { listeners.remove(listener); } private OutputReader createStdErrReader() { return new OutputReader(new InputStreamReader(process.getErrorStream()), this, (s -> notifyOnText(s, ProcessOutputType.STDERR))); } private void notifyOnTerminating() { listenerNotifier.onProcessWillTerminate(new ProcessEvent(this)); } private void notifyOnText(String text, ProcessOutputType type) { listenerNotifier.onText(new ProcessEvent(this, text), type); } private OutputReader createStdOutReader() { return new OutputReader(new InputStreamReader(process.getInputStream()), this, (s -> { notifyOnText(s, ProcessOutputType.STDOUT); })); } @Override public Future<?> execute(Runnable runnable) { return executorService.submit(runnable); } public boolean isStarted() { return state != ProcessState.INITIAL; } public void destroyProcess() { terminatingListener.runTask(() -> { if (state == ProcessState.RUNNING) { state = ProcessState.TERMINATING; notifyOnTerminating(); try { closeStream(); } finally { process.destroy(); } } }); } private void closeStream() { try { process.getOutputStream().close(); } catch (IOException e) { LOG.error(e.getMessage(), e); } } public boolean waitFor() { try { latch.await(); return true; } catch (InterruptedException ignored) { return false; } } private enum ProcessState { INITIAL, RUNNING, TERMINATING, TERMINATED } private class TerminatingTaskRunner implements ProcessListener { private List<Runnable> tasks = new ArrayList<>(); @Override public void onStart(ProcessEvent event) { removeProcessListener(this); runAllTasks(); } public void runTask(Runnable task) { if (isStarted()) { task.run(); } else { synchronized (tasks) { tasks.add(task); } if (isStarted()) { runAllTasks(); } } } private void runAllTasks() { List<Runnable> taskList; synchronized (tasks) { taskList = new ArrayList<>(tasks); tasks.clear(); } taskList.forEach(Runnable::run); } @Override public void onText(ProcessEvent event, ProcessOutputType outputType) { } @Override public void onProcessTerminated(ProcessEvent event) { } @Override public void onProcessWillTerminate(ProcessEvent event) { } } }