Return-Path: Delivered-To: apmail-jakarta-commons-dev-archive@apache.org Received: (qmail 95757 invoked from network); 6 Sep 2002 02:03:55 -0000 Received: from unknown (HELO nagoya.betaversion.org) (192.18.49.131) by daedalus.apache.org with SMTP; 6 Sep 2002 02:03:55 -0000 Received: (qmail 9174 invoked by uid 97); 6 Sep 2002 02:04:33 -0000 Delivered-To: qmlist-jakarta-archive-commons-dev@jakarta.apache.org Received: (qmail 9119 invoked by uid 97); 6 Sep 2002 02:04:32 -0000 Mailing-List: contact commons-dev-help@jakarta.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Subscribe: List-Help: List-Post: List-Id: "Jakarta Commons Developers List" Reply-To: "Jakarta Commons Developers List" Delivered-To: mailing list commons-dev@jakarta.apache.org Received: (qmail 9108 invoked by uid 97); 6 Sep 2002 02:04:32 -0000 X-Antivirus: nagoya (v4218 created Aug 14 2002) Date: 6 Sep 2002 02:03:46 -0000 Message-ID: <20020906020346.23137.qmail@icarus.apache.org> From: patrickl@apache.org To: jakarta-commons-sandbox-cvs@apache.org Subject: cvs commit: jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher ChildMain.java LaunchTask.java Launcher.java ParentListener.java X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N X-Spam-Rating: daedalus.apache.org 1.6.2 0/1000/N patrickl 2002/09/05 19:03:46 Modified: daemon/src/java/org/apache/commons/launcher ChildMain.java LaunchTask.java Launcher.java ParentListener.java Log: Implement a more orderly termination of any synchronous child processes. Previously, the code used the Process.destroy() method which, on Windows, forcefully terminates the child process. As a result, the child process' shutdown hooks were never executed. With this revision, the Launcher will delete a "heartbeat" file and the child process, once it senses that the file has been deleted, will invoke System.exit() to shut itself down. This new approach will all the child process' shutdown hooks to execute. Revision Changes Path 1.11 +11 -3 jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/ChildMain.java Index: ChildMain.java =================================================================== RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/ChildMain.java,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- ChildMain.java 30 Aug 2002 00:15:17 -0000 1.10 +++ ChildMain.java 6 Sep 2002 02:03:46 -0000 1.11 @@ -63,6 +63,7 @@ import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.reflect.Method; @@ -109,6 +110,12 @@ "org.apache.commons.launcher.executableName"; /** + * The heartbeatFile system property name. + */ + public final static String HEARTBEAT_FILE_PROP_NAME = + "org.apache.commons.launcher.heartbeatFile"; + + /** * The miminizedWindowTitle system property name. */ public final static String MINIMIZED_WINDOW_TITLE_PROP_NAME = @@ -196,14 +203,15 @@ // Start the thread to check if the parent JVM exits. boolean waitForChild = false; if (System.getProperty(ChildMain.WAIT_FOR_CHILD_PROP_NAME) != null) { + waitForChild = true; // Swap in a non-blocking input stream for System.in since // reading System.in can block the entire process on some // Windows platforms if (windows) System.setIn(new NonBlockingInputStream(System.in)); - - waitForChild = true; - ParentListener heartbeat = new ParentListener(); + // Create the ParentListener thread + File heartbeatFile = new File(System.getProperty(ChildMain.HEARTBEAT_FILE_PROP_NAME)).getCanonicalFile(); + ParentListener heartbeat = new ParentListener(heartbeatFile); // Make the thread a daemon thread so that it does not // prevent this process from exiting when all of the // application's threads finish. 1.26 +54 -15 jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/LaunchTask.java Index: LaunchTask.java =================================================================== RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/LaunchTask.java,v retrieving revision 1.25 retrieving revision 1.26 diff -u -r1.25 -r1.26 --- LaunchTask.java 30 Aug 2002 00:15:17 -0000 1.25 +++ LaunchTask.java 6 Sep 2002 02:03:46 -0000 1.26 @@ -58,6 +58,7 @@ package org.apache.commons.launcher; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; @@ -114,9 +115,10 @@ public final static String TASK_NAME = "launch"; /** - * Cached synchronous child processes for all instances of this class. + * Cached instances of this class that are executing synchronous child + * processes. */ - private static ArrayList childProcesses = new ArrayList(); + private static ArrayList executingTasks = new ArrayList(); //------------------------------------------------------------------ Fields @@ -126,11 +128,6 @@ private boolean appendOutput = false; /** - * Cached synchronously executing child process. - */ - private Process childProc = null; - - /** * Cached classpath. */ private Path classpath = null; @@ -171,6 +168,11 @@ private Path filterClasspath = null; /** + * Cached heartbeatFile. + */ + private File heartbeatFile = null; + + /** * Cached main class name. */ private String mainClassName = null; @@ -238,13 +240,14 @@ //---------------------------------------------------------- Static Methods /** - * Get the synchronous child processes for all instances of this class. + * Get the instances of this class that are executing synchronous child + * processes. * * @return the instances of this class. */ - public static Process[] getChildProcesses() { + public static LaunchTask[] getExecutingTasks() { - return (Process[])childProcesses.toArray(new Process[childProcesses.size()]); + return (LaunchTask[])executingTasks.toArray(new LaunchTask[executingTasks.size()]); } @@ -577,6 +580,18 @@ } } + // Create the heartbeatFile. This file is used by to request the + // child JVM to terminate itself. + heartbeatFile = null; + if (filteredWaitForChild) { + File tmpDir = null; + String tmpDirName = (String)sysProps.get("java.io.tmpdir"); + if (tmpDirName != null) + tmpDir = new File(tmpDirName); + heartbeatFile = File.createTempFile(ChildMain.HEARTBEAT_FILE_PROP_NAME + ".", "", tmpDir); + sysProps.put(ChildMain.HEARTBEAT_FILE_PROP_NAME, heartbeatFile.getCanonicalPath()); + } + // Assemble child command String[] cmd = new String[5 + jvmArgs.size() + sysProps.size() + appArgs.size()]; int nextCmdArg = 0; @@ -654,12 +669,11 @@ if (Launcher.isStopped()) throw new BuildException(); Process proc = null; - synchronized (LaunchTask.childProcesses) { + synchronized (LaunchTask.executingTasks) { proc = Runtime.getRuntime().exec(cmd); // Add the synchronous child process if (filteredWaitForChild) { - childProc = proc; - LaunchTask.childProcesses.add(proc); + LaunchTask.executingTasks.add(this); } } if (filteredWaitForChild) { @@ -679,6 +693,7 @@ stdout.join(); stderr.join(); int exitValue = proc.exitValue(); + Launcher.setExitValue(exitValue); if (filteredFailOnError && exitValue != 0) throw new BuildException(Launcher.getLocalizedString("child.failed", this.getClass().getName()) + " " + exitValue); } @@ -694,11 +709,35 @@ throw new BuildException(Launcher.getLocalizedString("launch.task.stopped", this.getClass().getName())); else throw new BuildException(e); + } finally { + synchronized (LaunchTask.executingTasks) { + // Remove the synchronous child process + LaunchTask.executingTasks.remove(this); + } + if (heartbeatFile != null) + heartbeatFile.delete(); + heartbeatFile = null; } } /** + * Kill the currently executing synchronous child process. This is done + * by deleting the heartbeat file. The child process, in its + * {@link ParentListener#run()} method, will invoke + * {@link System#exit(int)}. This approach is used because it allows the + * child process to execute its shutdown hooks. {@link Process#destroy()} + * is not used because it will forcefully terminate the child process on + * Windows without executing child process' shutdown hooks. + */ + public void kill() { + + if (heartbeatFile != null) + heartbeatFile.delete(); + + } + + /** * Set the useArgs flag. Setting this flag to true will cause this * task to append all of the command line arguments used to start the * {@link Launcher#start(String[])} method to the arguments @@ -801,7 +840,7 @@ */ public void setDisplayminimizedwindow(boolean displayMinimizedWindow) { - this.displayMinimizedWindow = displayMinimizedWindow; + this.disposeMinimizedWindow = disposeMinimizedWindow; } @@ -823,7 +862,7 @@ */ public void setDisposeminimizedwindow(boolean disposeMinimizedWindow) { - this.disposeMinimizedWindow = disposeMinimizedWindow; + this.disposeMinimizedWindow = displayMinimizedWindow; } 1.18 +40 -22 jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/Launcher.java Index: Launcher.java =================================================================== RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/Launcher.java,v retrieving revision 1.17 retrieving revision 1.18 diff -u -r1.17 -r1.18 --- Launcher.java 31 Jul 2002 02:04:48 -0000 1.17 +++ Launcher.java 6 Sep 2002 02:03:46 -0000 1.18 @@ -112,13 +112,17 @@ //----------------------------------------------------------- Static Fields - /** * Cached bootstrap file. */ private static File bootstrapFile = null; /** + * Cached exit value + */ + private static int exitValue = 0; + + /** * Cached java command */ private static String javaCmd = null; @@ -198,7 +202,6 @@ //---------------------------------------------------------- Static Methods - /** * Get the started flag. * @@ -222,6 +225,18 @@ } /** + * Set the exit value of the last synchronous child process that finished + * executing. + * + * @param exitValue the exit value of the last synchronous child process + * that finished executing + */ + protected static synchronized void setExitValue(int exitValue) { + + Launcher.exitValue = exitValue; + } + + /** * Start the launching process. This method is essential the * main(String[]) method for this class except that this method * never invokes {@link System#exit(int)}. This method is designed for @@ -393,14 +408,21 @@ if (!project.getTargets().containsKey(target)) throw new IllegalArgumentException(target + " " + Launcher.getLocalizedString("invalid.target")); - // Execute the target + // Add the shutdown hook try { runtime.addShutdownHook(shutdownHook); - } catch (NoSuchMethodError nsme) { + } catch (Throwable t) { // Early JVMs do not support this method } + // Reset the exit value + Launcher.setExitValue(returnValue); + + // Execute the target project.executeTarget(target); + // Get the exit value of last synchronous child JVM + returnValue = Launcher.exitValue; + } catch (Throwable t) { // Log any errors returnValue = 1; @@ -418,7 +440,7 @@ // Remove the shutdown hook try { runtime.removeShutdownHook(shutdownHook); - } catch (NoSuchMethodError nsme) { + } catch (Throwable t) { // Early JVMs do not support this method } // Reset the class loader after running Ant @@ -430,11 +452,6 @@ } } - // Override return value with exit value of last synchronous child JVM - Process[] childProcesses = LaunchTask.getChildProcesses(); - if (childProcesses.length > 0) - returnValue = childProcesses[childProcesses.length - 1].exitValue(); - return returnValue; } @@ -476,7 +493,7 @@ try { - // Kill all of the synchronous child processes + // Kill all of tasks that are executing synchronous child processes killChildProcesses(); // Wait for the start() method to reset the start flag @@ -928,26 +945,27 @@ } /** - * Iterate through the list of synchronous child process launched by - * all of the {@link LaunchTask} instances. - */ - public static void killChildProcesses() { - - Process[] procs = LaunchTask.getChildProcesses(); - for (int i = 0; i < procs.length; i++) - procs[i].destroy(); + * Iterate through the {@link LaunchTask} instances that are executing + * synchronous child processes and invokes the {@link LaunchTask#kill()} + * method on each. + */ + private static void killChildProcesses() { + + LaunchTask[] tasks = LaunchTask.getExecutingTasks(); + for (int i = 0; i < tasks.length; i++) + tasks[i].kill(); } //----------------------------------------------------------------- Methods /** - * Wrapper to allow the {@link #killChildProcesses()} method to be - * invoked in a shutdown hook. + * Wrapper to allow the call the {@link #stop()} method to gracefully kill + * the synchronous child processes. */ public void run() { - Launcher.killChildProcesses(); + Launcher.stop(); } 1.6 +28 -4 jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/ParentListener.java Index: ParentListener.java =================================================================== RCS file: /home/cvs/jakarta-commons-sandbox/daemon/src/java/org/apache/commons/launcher/ParentListener.java,v retrieving revision 1.5 retrieving revision 1.6 diff -u -r1.5 -r1.6 --- ParentListener.java 27 Aug 2002 21:11:28 -0000 1.5 +++ ParentListener.java 6 Sep 2002 02:03:46 -0000 1.6 @@ -57,6 +57,7 @@ package org.apache.commons.launcher; +import java.io.File; import java.io.PrintStream; /** @@ -67,6 +68,26 @@ */ public class ParentListener extends Thread { + //------------------------------------------------------------------ Fields + + /** + * Cached heartbeat file. + */ + private File heartbeatFile = null; + + //------------------------------------------------------------ Constructors + + /** + * Validates and caches a lock file created by the parent JVM. + * + * @param heartbeatFile the lock file that the parent JVM will delete when + * it wants this process to exit + */ + public ParentListener(File heartbeatFile) { + + this.heartbeatFile = heartbeatFile; + + } //----------------------------------------------------------------- Methods @@ -77,22 +98,25 @@ * invoke {@link System#exit(int)}. This method must be executed * before {@link System#setErr(PrintStream)} is ever invoked. *

- * TODO: {@link System#err} never seems to close while System.in is - * being read on Windows platforms. Need to find a workaround for this - * case. + * In addition, we check if the heartbeat file has been deleted. If so, + * we assume the parent is requesting that this process terminate itself. */ public void run() { // Save System.err in case the application invokes System.setErr() // later + PrintStream out = System.out; PrintStream err = System.err; - while (!err.checkError()) { + while (!out.checkError() && !err.checkError() && heartbeatFile.exists()) { // Wait a while before the next loop yield(); try { sleep(3000); } catch (Exception e) {} } + + // Clean up before exiting + heartbeatFile.delete(); // Exit this process since the parent JVM has exited System.exit(0); -- To unsubscribe, e-mail: For additional commands, e-mail: