/******************************************************************************* * Copyright (c) 2015 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.launch.process; import java.io.IOException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.Callable; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.IProcessFactory; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamsProxy; import org.eclipse.debug.core.model.RuntimeProcess; import org.springframework.ide.eclipse.boot.core.BootActivator; import org.springframework.ide.eclipse.boot.launch.BootLaunchConfigurationDelegate; import org.springframework.ide.eclipse.boot.launch.util.SpringApplicationLifeCycleClientManager; import org.springframework.ide.eclipse.boot.launch.util.SpringApplicationLifecycleClient; import org.springframework.ide.eclipse.boot.util.DumpOutput; import org.springframework.ide.eclipse.boot.util.RetryUtil; public class BootProcessFactory implements IProcessFactory { /** * This flag enables dumping the process output onto System.out. This meant to help in * diagnosing problems during CI build test execution. */ public static boolean ENABLE_OUTPUT_DUMPING = false; private static final boolean DEBUG = false; private static void debug(String string) { if (DEBUG) { System.out.println(string); } } @Override public IProcess newProcess(ILaunch launch, final Process process, final String label, Map<String, String> attributes) { final int jmxPort = getJMXPort(launch); final long timeout = getNiceTerminationTimeout(launch); RuntimeProcess rtProcess = new RuntimeProcess(launch, process, label, attributes) { SpringApplicationLifeCycleClientManager clientMgr = new SpringApplicationLifeCycleClientManager(jmxPort); @Override public void terminate() throws DebugException { if (!terminateNicely()) { //Let eclipse try it more aggressively. try { super.terminate(); } catch (DebugException e) { //Try even harder to destroy the process if (!destroyForcibly()) { throw e; } } } } @Override protected IStreamsProxy createStreamsProxy() { IStreamsProxy streams = super.createStreamsProxy(); if (ENABLE_OUTPUT_DUMPING) { streams.getOutputStreamMonitor().addListener(new DumpOutput("%out: ")); streams.getErrorStreamMonitor().addListener(new DumpOutput("%err: ")); } return streams; } private boolean destroyForcibly() { try { Method m = Process.class.getDeclaredMethod("destroyForcibly"); m.invoke(process); return true; } catch (Exception e) { BootActivator.log(e); //ignore... probably means we are not running on Java 8 VM and so we can't use 'destroyForcibly'. } return false; } private boolean terminateNicely() { if (isTerminated()) { return true; } if (jmxPort>0) { try { debug("Trying to terminate nicely: "+label); SpringApplicationLifecycleClient client = clientMgr.getLifeCycleClient(); if (client!=null) { debug("Asking JMX client to 'stop'"); client.stop(); debug("Asking JMX client to 'stop' -> SUCCESS"); } else { //This case happens if process is already being terminated in another way. // This happens for debug processes where debug target kills vm before // asking process to terminate. debug("PROBLEM? Couldn't get JMX client"); throw new IOException("Couldn't get JMX client."); } //Wait for a bit, but not forever until process dies. RetryUtil.retry(100, timeout, new Callable<Void>() { public Void call() throws Exception { debug("process exited?"); process.exitValue(); //throws if process not ready debug("process exited? -> YES"); return null; } }); debug("SUCCESS terminate nicely: "+label); return true; } catch (Exception e) { //ignore... nice termination failed. //BootActivator.log(e); } finally { clientMgr.disposeClient(); } } return false; } }; return rtProcess; } private long getNiceTerminationTimeout(ILaunch launch) { return BootLaunchConfigurationDelegate.getTerminationTimeoutAsLong(launch); } private int getJMXPort(ILaunch launch) { return BootLaunchConfigurationDelegate.getJMXPortAsInt(launch); } }