Bug 8510 - shutdown hook does not fire in forked java task under JDK1.4
Summary: shutdown hook does not fire in forked java task under JDK1.4
Status: RESOLVED INVALID
Alias: None
Product: Ant
Classification: Unclassified
Component: Core (show other bugs)
Version: 1.4.1
Hardware: PC All
: P3 blocker with 4 votes (vote)
Target Milestone: 1.6
Assignee: Ant Notifications List
URL: http://home.osm.net/020423.html
Keywords:
: 12796 22026 (view as bug list)
Depends on:
Blocks:
 
Reported: 2002-04-25 11:08 UTC by Stephen McConnell
Modified: 2008-10-28 07:03 UTC (History)
5 users (show)



Attachments
a small simple test case that shows the bug in action (5.20 KB, application/zip)
2004-12-07 18:09 UTC, Dave Sag
Details
An update to the test case that shows the dehaviour described by peter reilly (5.53 KB, application/zip)
2004-12-11 13:25 UTC, Dave Sag
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Stephen McConnell 2002-04-25 11:08:36 UTC
When aborting a forked java process the shutdown hook does not fire if the 
underlying JDK is version 1.4.0 (1.4beta 3 and earlier are ok). The forked java 
process will continue running after the main build task has completed.  A 
demonstration of this problem is included in a test application (source and 
buildfile) available under http://home.osm.net/020423.html
Comment 1 Steve Loughran 2002-04-26 07:10:53 UTC
Steve,
does this problem also arise when you run the java app from the command line? 
It may be a java 1.4 issue, cos when ant runs stuff ina  new jvm, it just 
execs java
Comment 2 Stephen McConnell 2002-04-26 14:32:18 UTC
1. running the demo app from the command line works fine 
2. running the demo app as a sub-process created by a parent 
   process where the parent is executed from the command line
   works fine
3. running the demo app from the Ant target <java ... fork="false"> 
   works fine
4. running the demo app from the Ant target <java ... fork="true"> 
   fails with the subprocess still in existance after the parent
   process has terminated

Given that process destruction happens when running from the command line and 
does not happen when running with the <java ... fork="true"/> suggests that 
this is an Ant bug and not a JVM bug (even through it feels like a JVM bug) - 
bottom line is that I have not been able to create the same fail condition 
outside of Ant.

Have updated the demo on http://home.osm.net/020423.html to demonstrate this.
Once built the non-fail command line demo can be run using:

   $ java -classpath test.jar Main
   
With the following output:

   main
   MAIN: sub-process created: java.lang.Win32Process@f4a24a
   MAIN: shutdown hook set

The hit ^C

   MAIN: shutdown hook fired
   MAIN: subprocess destroyed

Invoking <ant fork="true" ... > should result in similar behaviour.  I.e. the 
forked java sub-process should terminate - instead it continues until manually 
destroyed using the task manager.
Comment 3 Stefan Bodewig 2002-04-26 14:57:22 UTC
Could you please try a recent nightly build of Ant?

We've added a "ProcessDestroyer" after the 1.4.1 release that tries to kill
forked processes when Ant gets aborted.  I think this should fix your problem,
if the shutdown hook inside the VM that is running Ant gets time to fire, that
is.
Comment 4 Stephen McConnell 2002-04-26 16:46:23 UTC
Using Ant 1.5 alpha (CVS) results in the termination of the forked process 
(which is better) but this does not resolve the core problem.  The forked 
process shutdownhook is still not triggered.
Comment 5 Stephen McConnell 2002-05-07 01:36:46 UTC
Just a note concerning Ant 1.5 status - when invoking <java fork="true" ...> the
1.5 version does handle cleanup of the forked process however the when using
<java jar="whatever.jar" fork="true" ...> the process is not cleaned-up and
continues to execute independently.
Comment 6 Stephane Bailliez 2002-07-15 14:08:39 UTC
Does anyone has the slighest idea of what's going on here that avoid the 
shutdown hook to be triggered ? I can't find what could be the problem !

The last entry of Stephen also puzzles me, there is no difference in Ant 
between executing a java command with or without a jar, we are just building 
the command line.
Comment 7 Stephane Bailliez 2002-07-16 13:37:32 UTC
Concerning the last entry of the process continuing to run with -jar, it does 
not happen under JDK 1.3.1 but only with JDK 1.4
Comment 8 Hal Hildebrand 2002-07-16 15:45:08 UTC
5003 seems like it could be related, but I've always viewed this specific 
problem as a black hole of hell and wanted to stay as far away from the even 
horizon as I could.

8510 looks like it could be solved by this patch, however.
Comment 9 Hal Hildebrand 2002-07-16 15:49:34 UTC
With regards to:

> Does anyone has the slighest idea of 
> what's going on here that avoid the 
> shutdown hook to be triggered ? I can't 
> find what could be the problem !

The problem is that when the InterruptedException is caught and ignored in the 
waitFor() method, the executing process is assumed to have completed and is 
removed from the shutdown hook.  This is not the case when the thread is 
interrupted.  The issue is that catching the InterruptedException, and 
silently ignoring it, is acting just like the process exited normally.  The 
execute() method has no way of knowing if the thread was interrupted - and 
therefore the process is still executing - of if the process exited.  So when 
the InterruptedException is thrown in the thread, and the waitFor() exits, it 
just removes the shutdown hook and assumes the process is done.  Therefore the 
process will just happily continue to execute and no one will ever kill it.
Comment 10 Hal Hildebrand 2002-07-16 15:51:35 UTC
Shoot.  Wrong bug.  Sorry for the noise.  I *thought* I was responding to bug 
10345.
Comment 11 Amar Mattey 2002-10-04 23:47:42 UTC
*** Bug 12796 has been marked as a duplicate of this bug. ***
Comment 12 Markus Kieninger 2002-10-23 11:43:52 UTC
I did some tests concerning the shutdown hook problem with jdk 1.4. 
Here are the results:

shutdown hook works fine with:

jdk 1.3.x + application + no ant + windows (nt 4.0 + 2000)
jdk 1.3.x + application + no ant + linux
jdk 1.4.x + application + no ant + windows (nt 4.0 + 2000)
jdk 1.4.x + application + no ant + linux
jdk 1.3.x + application + ant 1.5.x (java + fork) + windows (nt 4.0 + 2000)
jdk 1.3.x + application + ant 1.5.x (java + fork) + linux
jdk 1.4.x + application + ant 1.5.x (java + fork) + linux

shutdown hook does not work with:

jdk 1.4.x + application + ant 1.5.x (java + fork) + windows (nt 4.0 + 2000)

I tried several jdk versions (1.3.0, 1.3.1, 1.4.0, 1.4.1) and ant versions
(1.5.0, 1.5.1). Always with the result described above.

So I think they must have changed something in jdk 1.4.x (win) about the
shutdown (hook) procedure, which is not integrated in ant so far. I don't think
it is a bug in the jdk, because when I run the application without ant all works
fine.

Maybe one of you can verify my results and I hope this will help to solve the
problem.

Comment 13 Luoh Ren-Shan 2002-10-24 10:16:18 UTC
I make some experiments and make a "guess".
Maybe the guess is wrong, but hope the other information
will help to solve the problem.


 == Experiment 1: use JDK to invoke MyProg ==

"MyProg" is a program with shutdown hook installed.
Press "Ctrl+C" to stop it.

"Yes" means the showdown hook of "MyProg" is invoked.

java.exe MyProg
    JDK 1.3.1 Yes      (1.3.1_03-b03)
    JDK 1.4.0 Yes      (1.4.0-b92)


 == Experiment 2: use Ant 1.5.1 to invoke MyProg ==

Run "MyProg" with Ant. Press "Ctrl+C" to stop it.

Ant 1.5.1 has its own shotdown hook (added by
ProcessDestroyer.java) which call java.lang.Process.destroy()
on the process of "MyProg".

"Yes" means the showdown hook of "MyProg" is invoked.

Ant 1.5.1 -> MyProg
    JDK 1.3.1 *Yes*
    JDK 1.4.0  No


 == Experiment 3: use "Invoker" to invoke MyProg ==

"Invoker" is a program which launches "MyProg" with
System.exec().
"Invoker" has its own shotdown hook which call
java.lang.Process.destroy() on the process of "MyProg".

"Yes" means the showdown hook of "MyProg" been invoked.

java.exe Invoker -> MyProg
    JDK 1.3.1       JDK 1.4.0
        0  No           0  No
        1  No           1  No
        2 *Yes*         2  No

    case 0: Invoker call proc.destroy() directly
            (not in its shutdown hook)
    case 1: Invoker call proc.destroy() in its own
            shutdown hook triggered when the
            "main" thread completed.
    case 2: Invoker call proc.destroy() in its own
            shutdown hook triggered by Ctrl+C.


 == Code and Document ==

On Win32 systems java.lang.Process.destroy()
make system call TerminateProcess() in both
JDK 1.3.1 and 1.4.0 which unconditionally
cause a process to exit.
(src\win32\native\java\lang\Win32Process_md.c)

In the JavaDoc of java.lang.Runtime,
  "In rare circumstances the virtual machine may abort,
   that is, stop running without shutting down cleanly.
   This occurs when the virtual machine is terminated
   externally, for example with the SIGKILL signal on
   Unix or the TerminateProcess call on Win32.".


 == My guess ==

Process.destroy() which calls TerminateProcess() should
give "MyProg" no chance to run its shutdown hook.
There are two exceptions: (marked with '*')

    JDK 1.3.1 + Ant 1.5.1 + MyProg (with Ctrl+C)
    JDK 1.3.1 + Invoker + MyProg (with Ctrl+C)

I "guess" that JDK 1.3.1 also send "Ctrl+C" to MyProg
which cause it to run the shutdown hook before killed
by TerminateProcess() from Ant and Invoker. But I have
no evidence.



 == Code: MyProg.java ==

import java.io.FileWriter;
import java.io.IOException;

public class MyProg
{
    public static void log(String s)
    {
        try
        {
            FileWriter fw = new FileWriter("MyProg.txt", true);
            fw.write(s + "\n");
            fw.flush();
            fw.close();
        } catch (IOException e) { }
    }

    public static void main(String[] args) throws Exception
    {
        log("MyProg: main() : " + System.getProperty("java.runtime.version"));
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run()
            {
                log("MyProg: hook invoked");
            }});
        log("MyProg: hook installed");
        while (true) ;
    }

}


 == Code: Invoker.java ==

public class Invoker
{
    public static Process exec()
    {
        try
        {
            System.out.println("before exec");
            Process proc = Runtime.getRuntime().exec("java MyProg");
            System.out.println("after exec, sleep 5 sec");
            Thread.currentThread().sleep(5 * 1000);
            System.out.println("ready");
            return proc;
        } catch (Exception e) { e.printStackTrace(); return null; }
    }

    public static void addHook(final Process proc)
    {
        System.out.println("enter addHook");
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run()
            {
                System.out.println("Invoker: hook invoked");
                System.out.println("before destory");
                proc.destroy();
                System.out.println("after destory");
            }});
        System.out.println("leave addHook");
    }

    public static void destroyDirectly()
    {
        Process proc = exec();
        System.out.println("before destroy");
        proc.destroy();
        System.out.println("after destroy");
    }

    public static void destroyFromHook1()
    {
        Process proc = exec();
        addHook(proc);
    }

    public static void destroyFromHook2()
    {
        Process proc = exec();
        addHook(proc);
        System.out.println("waiting for ctrl+c");
        while (true) ;
    }


    public static void main(String[] args) throws Exception
    {
        switch (Integer.parseInt(args[0]))
        {
            case 0 :
                destroyDirectly();
                break;
            case 1 :
                destroyFromHook1();
                break;
            case 2 :
                destroyFromHook2();
                break;

        }
    }
}

 == build.xml ==
 
<project name="A" default="A" basedir=".">
    <target name="A">
        <java classname="MyProg" fork="yes">
            <classpath>
                <pathelement location="."/>
            </classpath>
        </java>
    </target>
</project> 
Comment 14 Steve Loughran 2002-10-24 16:06:33 UTC
If 1.4.x really does use ::TerminateProcess() then that is kind of brutal and
potentially dirty. I wonder if we can do some very low level stuff to work out
exactly what goes on.

Comment 15 Luoh Ren-Shan 2002-10-29 04:35:10 UTC
Both JDK 1.3.? (I don't know the exact version) and JDK 1.4.0
use the same code to destroy() a process.

JNIEXPORT void JNICALL
Java_java_lang_Win32Process_destroy(JNIEnv *env, jobject process)
{
    jboolean exc;
    jint handle = JNU_GetFieldByName(env, &exc, process, "handle", "I").i;
    if (exc) {
        return;
    }
    TerminateProcess((void *)handle, 1);
}

Maybe sometimes it returns before invoking ::TerminateProcess() ?

P.S. Is it legal to post the code here?
Comment 16 Conor MacNeill 2003-07-15 07:23:55 UTC
I have resolved some aspects of this bug, although it mainly applies to Unix
systems (more on that in a minute).

Under Ant 1.5 under Linux, the ProcessDestroyer does not wait for the
terminating processes to complete their shutdowns. If they have anything more
than a trivial shutdown process, it would get truncated as the Ant VM shuts
down. The ProcessDestroyer also blocked Execute.java in the remove() method as
it held the lock on the processes object. So even if the process managed to
generate output, it may not have made it to the Ant output log.

Under Windows and JDK 1.4, the process shutdown hooks are not fired. This is
marked by Sun as "not a bug". See
http://developer.java.sun.com/developer/bugParade/bugs/4485742.html
although http://developer.java.sun.com/developer/bugParade/bugs/4671966.html may
be able to change that.

JDK 1.3 appears to fire the shutdown hook on Windows but it is something of an
illusion as the shutdown is not allowed to complete. If you put this in the
given Demo class' run method

                System.err.println("DEMO shutdown hook fired");
                try {
                    PrintWriter pw
                        = new PrintWriter(new FileOutputStream("test.txt"));
                    Thread.currentThread().sleep(3000);
                    pw.println("Test");
                    pw.close();
                } catch (Exception e) {
                }
                System.err.flush();

you will find that test.txt is created but no content is written. This works
under Linux.

Note that the use of output or error streams to determine if the shutdown hook
has fired or not is suspect. The above file based approach is more conclusive.
This is because the output generated may still be within a couple of Ant stream
processing threads (daemons) which get terminated in the shutdown process.

The Main.java does not really demonstrate a problem. It gives no info about the
firing of the shutdown hook in the subprocess. In fact it has the same behaviour
as Ant - the subprocess shutdown is not fired. It is indeed a JVM bug^H^H^Hfeature.

I'm marking as fixed from Ant's point of view. You need to lobby Sun to address
the shutdown issues under windows.
Comment 17 Conor MacNeill 2003-08-08 03:41:07 UTC
*** Bug 22026 has been marked as a duplicate of this bug. ***
Comment 18 Dave Sag 2004-12-07 17:54:56 UTC
I am using ant 1.6.2 and have a working test case on MacOSX that clearly shows that a shutdown hook 
is never invoked when you ctrl-c an app run from ant's java task.
Comment 19 Dave Sag 2004-12-07 18:09:14 UTC
Created attachment 13672 [details]
a small simple test case that shows the bug in action

see the readme in the zip file.
Comment 20 Peter Reilly 2004-12-07 18:25:34 UTC
I tested your example problem and saw the same thing under linux.
However, I then read Conor's report

"Note that the use of output or error streams to determine if the shutdown hook
has fired or not is suspect. The above file based approach is more conclusive."

So, I changed the code to do:
	public class Shutter extends Thread {
		public void run() {
//			log.debug("clean shutdown");
                    System.err.println("DEMO shutdown hook fired");
                    try {
                        java.io.PrintWriter pw
                            = new java.io.PrintWriter(
                      new java.io.FileOutputStream("test.txt"));
                        Thread.currentThread().sleep(3000);
                        pw.println("Test");
                        pw.close();
                    } catch (Exception e) {
                    }
                    System.err.flush();
                    System.out.println("clean shutdown...");
		}
	}

And noted that clean shutdown... was not seen, but the file was created
and written to, and the DEMO shutdown hook fired message was seen.
Can you make the above changes and see if they fix the problem?
Comment 21 Dave Sag 2004-12-11 13:18:48 UTC
(In reply to comment #20)
Interesting.  I made the changes you described and lo - the file is written but i get no consol output at 
all.
The reason i first came across this bug is that a project I am woring on opens a connection to an SMPP 
server and the shutdown hook is supposed to close that connection if the user ctrl-c's the app.  this 
works fine if i run it from a shell script but not from ant.
I can see from my SMPP server logs that a connection is still left there dangling.
Is there some work-around for this?
Comment 22 Dave Sag 2004-12-11 13:25:48 UTC
Created attachment 13734 [details]
An update to the test case that shows the dehaviour described by peter reilly

this works but raises more questions - ie what can and can not be expected to
execute in a shutdown hook when you expect the app to be run from ant and the
user is able to ctrl-c.
perhaps the ant docs need to be updated to reflect this behaviour.
Comment 23 Peter Reilly 2004-12-13 18:55:01 UTC
The writing to std output/error for forked processes are handled by
thread objects reading the relativent streams. At shutdown time
these threads may be stopped before they have finished reading their
stream - so it is a timing and flushing behaviour dependent what
is actually written to the console.

This timing issue should not effect closing the connection to an SMPP
server. Maybe you could use the write to file techinque to debug your
problem. (Surely the connection should be closed automatticlly by the
OS when the ant process ends?)
Comment 24 Stefan Bodewig 2008-10-28 07:03:18 UTC
Closing it since the issues cited by Conor really point at a JDK not-a-bug.

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4671966 is still not closed AFAICS.