diff --git a/src/main/org/apache/tools/ant/taskdefs/ExecTask.java b/src/main/org/apache/tools/ant/taskdefs/ExecTask.java index 4d90045b5..d949de471 100644 --- a/src/main/org/apache/tools/ant/taskdefs/ExecTask.java +++ b/src/main/org/apache/tools/ant/taskdefs/ExecTask.java @@ -88,7 +88,7 @@ public class ExecTask extends Task { private File dir; protected boolean failOnError = false; protected boolean newEnvironment = false; - private Integer timeout = null; + private Long timeout = null; private Environment env = new Environment(); protected Commandline cmdl = new Commandline(); private FileOutputStream fos = null; @@ -104,7 +104,7 @@ public class ExecTask extends Task { /** * Timeout in milliseconds after which the process will be killed. */ - public void setTimeout(Integer value) { + public void setTimeout(Long value) { timeout = value; } @@ -377,7 +377,7 @@ public class ExecTask extends Task { if (timeout == null) { return null; } - return new ExecuteWatchdog(timeout.intValue()); + return new ExecuteWatchdog(timeout.longValue()); } /** diff --git a/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java b/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java index e2e1bae0e..50bbf4e1a 100644 --- a/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java +++ b/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java @@ -1,7 +1,7 @@ /* * The Apache Software License, Version 1.1 * - * Copyright (c) 2000-2001 The Apache Software Foundation. All rights + * Copyright (c) 2000-2002 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,21 +62,28 @@ import org.apache.tools.ant.Project; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.CommandlineJava; import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.TimeoutObserver; +import org.apache.tools.ant.util.Watchdog; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.io.PrintStream; -/* +/** * * @author thomas.haas@softwired-inc.com * @author Stefan Bodewig */ -public class ExecuteJava { +public class ExecuteJava implements Runnable, TimeoutObserver { private Commandline javaCommand = null; private Path classpath = null; private CommandlineJava.SysProperties sysProperties = null; + private Method main = null; + private Long timeout = null; + private Throwable caught = null; + private boolean timedOut = false; + private Thread thread = null; public void setJavaCommand(Commandline javaCommand) { this.javaCommand = javaCommand; @@ -99,9 +106,15 @@ public class ExecuteJava { public void setOutput(PrintStream out) { } + /** + * @since 1.19, Ant 1.5 + */ + public void setTimeout(Long timeout) { + this.timeout = timeout; + } + public void execute(Project project) throws BuildException{ final String classname = javaCommand.getExecutable(); - final Object[] argument = { javaCommand.getArguments() }; AntClassLoader loader = null; try { @@ -120,21 +133,41 @@ public class ExecuteJava { target = loader.forceLoadClass(classname); AntClassLoader.initializeClass(target); } - final Method main = target.getMethod("main", param); - main.invoke(null, argument); + main = target.getMethod("main", param); + + if (timeout == null) { + run(); + } else { + thread = new Thread(this, "ExecuteJava"); + Watchdog w = new Watchdog(timeout.longValue()); + w.addTimeoutObserver(this); + synchronized (this) { + thread.start(); + w.start(); + try { + wait(); + } catch (InterruptedException e) {} + if (timedOut) { + project.log("Timeout: killed the sub-process", + Project.MSG_WARN); + } else { + thread = null; + w.stop(); + } + } + } + + if (caught != null) { + throw caught; + } + } catch (NullPointerException e) { throw new BuildException("Could not find main() method in " + classname); } catch (ClassNotFoundException e) { throw new BuildException("Could not find " + classname + ". Make sure you have it in your classpath"); - } catch (InvocationTargetException e) { - Throwable t = e.getTargetException(); - if (!(t instanceof SecurityException)) { - throw new BuildException(t); - } - else { - throw (SecurityException)t; - } - } catch (Exception e) { + } catch (SecurityException e) { + throw e; + } catch (Throwable e) { throw new BuildException(e); } finally { if (loader != null) { @@ -146,4 +179,43 @@ public class ExecuteJava { } } } + + /** + * @since 1.19, Ant 1.5 + */ + public void run() { + final Object[] argument = { javaCommand.getArguments() }; + try { + main.invoke(null, argument); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (!(t instanceof InterruptedException)) { + caught = t; + } /* else { swallow, probably due to timeout } */ + } catch (Throwable t) { + caught = t; + } finally { + synchronized (this) { + notifyAll(); + } + } + } + + /** + * @since 1.19, Ant 1.5 + */ + public synchronized void timeoutOccured(Watchdog w) { + if (thread != null) { + timedOut = true; + thread.interrupt(); + } + notifyAll(); + } + + /** + * @since 1.19, Ant 1.5 + */ + public boolean killedProcess() { + return timedOut; + } } diff --git a/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java index 3bd15c946..c49971464 100644 --- a/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java +++ b/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -98,7 +98,7 @@ public class ExecuteWatchdog implements TimeoutObserver { * * @param timeout the timeout for the process in milliseconds. It must be greather than 0. */ - public ExecuteWatchdog(int timeout) { + public ExecuteWatchdog(long timeout) { watchdog = new Watchdog(timeout); watchdog.addTimeoutObserver(this); } diff --git a/src/main/org/apache/tools/ant/taskdefs/Java.java b/src/main/org/apache/tools/ant/taskdefs/Java.java index 6c2cd0d44..2bd2bb4d5 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Java.java +++ b/src/main/org/apache/tools/ant/taskdefs/Java.java @@ -91,6 +91,7 @@ public class Java extends Task { private PrintStream outStream = null; private boolean failOnError = false; private boolean append = false; + private Long timeout = null; /** * Do the execution. @@ -309,6 +310,15 @@ public class Java extends Task { this.append = append; } + /** + * Timeout in milliseconds after which the process will be killed. + * + * @since 1.37, Ant 1.5 + */ + public void setTimeout(Long value) { + timeout = value; + } + protected void handleOutput(String line) { if (outStream != null) { outStream.println(line); @@ -336,6 +346,7 @@ public class Java extends Task { exe.setJavaCommand(command.getJavaCommand()); exe.setClasspath(command.getClasspath()); exe.setSystemProperties(command.getSystemProperties()); + exe.setTimeout(timeout); if (out != null) { try { outStream = @@ -366,10 +377,11 @@ public class Java extends Task { if (out == null) { exe = new Execute(new LogStreamHandler(this, Project.MSG_INFO, Project.MSG_WARN), - null); + createWatchdog()); } else { fos = new FileOutputStream(out.getAbsolutePath(), append); - exe = new Execute(new PumpStreamHandler(fos), null); + exe = new Execute(new PumpStreamHandler(fos), + createWatchdog()); } exe.setAntRun(project); @@ -395,7 +407,11 @@ public class Java extends Task { exe.setCommandline(command); try { - return exe.execute(); + int rc = exe.execute(); + if(exe.killedProcess()) { + log("Timeout: killed the sub-process",Project.MSG_WARN); + } + return rc; } catch (IOException e) { throw new BuildException(e, location); } @@ -427,4 +443,17 @@ public class Java extends Task { public void clearArgs() { cmdl.clearJavaArgs(); } + + /** + * Create the Watchdog to kill a runaway process. + * + * @since 1.37, Ant 1.5 + */ + protected ExecuteWatchdog createWatchdog() throws BuildException { + if (timeout == null) { + return null; + } + return new ExecuteWatchdog(timeout.longValue()); + } + } diff --git a/src/main/org/apache/tools/ant/util/TimeoutObserver.java b/src/main/org/apache/tools/ant/util/TimeoutObserver.java index 09551c5c2..dd38b8202 100644 --- a/src/main/org/apache/tools/ant/util/TimeoutObserver.java +++ b/src/main/org/apache/tools/ant/util/TimeoutObserver.java @@ -57,7 +57,7 @@ package org.apache.tools.ant.util; /** * Interface for classes that want to be notified by Watchdog. * - * @since 1.5 + * @since Ant 1.5 * * @see org.apache.tools.ant.util.Watchdog * diff --git a/src/main/org/apache/tools/ant/util/Watchdog.java b/src/main/org/apache/tools/ant/util/Watchdog.java index fcafc446b..358fa2589 100644 --- a/src/main/org/apache/tools/ant/util/Watchdog.java +++ b/src/main/org/apache/tools/ant/util/Watchdog.java @@ -113,7 +113,7 @@ public class Watchdog implements Runnable { long now; while (!stopped && until > (now = System.currentTimeMillis())) { try { - wait(until - now); + wait(until - now); } catch (InterruptedException e) {} } if (!stopped) { diff --git a/src/testcases/org/apache/tools/ant/taskdefs/ExecuteJavaTest.java b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteJavaTest.java new file mode 100644 index 000000000..80ec943b6 --- /dev/null +++ b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteJavaTest.java @@ -0,0 +1,148 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +package org.apache.tools.ant.taskdefs; + +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Commandline; + +import java.io.File; + +import junit.framework.TestCase; + +/** + * Simple testcase for the ExecuteJava class - mostly stolen from + * ExecuteWatchdogTest. + * + * @author Stephane Bailliez + * @author Stefan Bodewig + */ +public class ExecuteJavaTest extends TestCase { + + private final static int TIME_OUT = 5000; + + private final static int CLOCK_ERROR=200; + private final static int TIME_OUT_TEST=TIME_OUT-CLOCK_ERROR; + + private ExecuteJava ej; + private Project project; + + public ExecuteJavaTest(String name) { + super(name); + } + + protected void setUp(){ + ej = new ExecuteJava(); + ej.setTimeout(new Long(TIME_OUT)); + project = new Project(); + project.setBasedir("."); + ej.setClasspath(new Path(project, getTestClassPath())); + } + + private Commandline getCommandline(int timetorun) throws Exception { + Commandline cmd = new Commandline(); + cmd.setExecutable(TimeProcess.class.getName()); + cmd.createArgument().setValue(String.valueOf(timetorun)); + return cmd; + } + + public void testNoTimeOut() throws Exception { + Commandline cmd = getCommandline(TIME_OUT/2); + ej.setJavaCommand(cmd); + ej.execute(project); + assertTrue("process should not have been killed", !ej.killedProcess()); + } + + // test that the watchdog ends the process + public void testTimeOut() throws Exception { + Commandline cmd = getCommandline(TIME_OUT*2); + ej.setJavaCommand(cmd); + long now = System.currentTimeMillis(); + ej.execute(project); + long elapsed = System.currentTimeMillis() - now; + assertTrue("process should have been killed", ej.killedProcess()); + + assertTrue("elapse time of "+elapsed + +" ms is less than timeout value of "+TIME_OUT_TEST+" ms", + elapsed >= TIME_OUT_TEST); + assertTrue("elapse time of "+elapsed + +" ms is greater than run value of "+(TIME_OUT*2)+" ms", + elapsed < TIME_OUT*2); + } + + + /** + * Dangerous method to obtain the classpath for the test. This is + * severely tighted to the build.xml properties. + */ + private static String getTestClassPath(){ + String classpath = System.getProperty("build.tests"); + if (classpath == null) { + System.err.println("WARNING: 'build.tests' property is not available !"); + classpath = System.getProperty("java.class.path"); + } + + // JDK 1.1 needs classes.zip in -classpath argument + if (Project.getJavaVersion() == Project.JAVA_1_1) { + classpath += File.pathSeparator + + System.getProperty("java.home") + + File.separator + "lib" + + File.separator + "classes.zip"; + } + + return classpath; + } + +} diff --git a/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java index e131ae5c7..2e1bff759 100644 --- a/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java +++ b/src/testcases/org/apache/tools/ant/taskdefs/ExecuteWatchdogTest.java @@ -95,13 +95,13 @@ public class ExecuteWatchdogTest extends TestCase { classpath = System.getProperty("java.class.path"); } - // JDK 1.1 needs classes.zip in -classpath argument - if (Project.getJavaVersion() == Project.JAVA_1_1) { - classpath += File.pathSeparator - + System.getProperty("java.home") - + File.separator + "lib" - + File.separator + "classes.zip"; - } + // JDK 1.1 needs classes.zip in -classpath argument + if (Project.getJavaVersion() == Project.JAVA_1_1) { + classpath += File.pathSeparator + + System.getProperty("java.home") + + File.separator + "lib" + + File.separator + "classes.zip"; + } return classpath; } @@ -197,14 +197,4 @@ public class ExecuteWatchdogTest extends TestCase { assertEquals(0, process.exitValue()); assertTrue("process should not have been killed", !watchdog.killedProcess()); } - - public static class TimeProcess { - public static void main(String[] args) throws Exception { - int time = Integer.parseInt(args[0]); - if (time < 1) { - throw new IllegalArgumentException("Invalid time: " + time); - } - Thread.sleep(time); - } - } } diff --git a/src/testcases/org/apache/tools/ant/taskdefs/TimeProcess.java b/src/testcases/org/apache/tools/ant/taskdefs/TimeProcess.java new file mode 100644 index 000000000..b1de8e022 --- /dev/null +++ b/src/testcases/org/apache/tools/ant/taskdefs/TimeProcess.java @@ -0,0 +1,71 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.taskdefs; + +/** + * Helper class for ExecuteWatchdogTest and ExecuteJavaTest. + * + *

Used to be an inner class of ExecuteWatchdogTest. + * + * @author Stephane Bailliez + */ +public class TimeProcess { + public static void main(String[] args) throws Exception { + int time = Integer.parseInt(args[0]); + if (time < 1) { + throw new IllegalArgumentException("Invalid time: " + time); + } + Thread.sleep(time); + } +}