This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

Bug 22641

Summary: Shutdown hooks are not performed when the process is terminated from the IDE
Product: projects Reporter: cberger <cberger>
Component: AntAssignee: Tomas Stupka <tstupka>
Status: RESOLVED WONTFIX    
Severity: blocker CC: tskorka
Priority: P3    
Version: 3.x   
Hardware: PC   
OS: Windows ME/2000   
Issue Type: ENHANCEMENT Exception Reporter:
Bug Depends on: 68770    
Bug Blocks:    

Description cberger 2002-04-19 21:23:58 UTC
When I start my java application (pressing
Execute(F6) or debugging) everything works fine.
Unfortunately, I've not found any was to stop
CLEANLY the application from the debugger.
Control-C doesn't work from the terminal, and the
"Terminate Process" command of the IDE kill the
process hard. i.e. the Finalizer Threads of the
application are NOT run.
Comment 1 _ ttran 2002-04-22 17:49:59 UTC
this behavior is governed by the JVM.  If you kills the JVM process no
finalizers are guaranteed to run.  Actually no JVM impl promisses that
it will run all your finalizers.  There have been many technical
articles about this topic.

Marking this report as INVALID
Comment 2 cberger 2002-04-22 20:36:40 UTC
I'm sorry, I wrote the description too fast.
I wanted to talk about the shutdown hooks, not
the finalizers. The shutdown hooks
(Runtime.getRuntimme().addShutdownHook(Thread))
are supposed to run on JVM exit, and they are
not if the JVM is started from NetBeans, which
is bad.
Sorry for the confusion
Reopenning
Cedric



Comment 3 _ ttran 2002-05-07 09:58:09 UTC
No, I don't believe that NB can be blamed for it.  We just use
Runtime.exec() to spawn an external process.  If a java process
launched in this way fails to can the shutdown hooks, then please
blame the JDK not NB.
Comment 4 cberger 2002-05-07 11:16:16 UTC
Ok, I investigated a bit, and there is a JDK bug (4485742)
http://developer.java.sun.com/developer/bugParade/bugs/4485742.html
Which covers this subject. I've made some tests, and it is Windows
specific. (on Solaris, shutdown hooks are run).

BUT: From Sun (the JDK team, I mean...) Process.destroy() is platform
specific and should be only called on "last resort", and is not the
"normal way" to stop a process. Of course, one might wonder what the
normal way is...

Anyway, what is really needed in Some Way to exit a process cleanly
and running the shutdown hooks on Win32. One way or another. If you
run a Java process from the Windows Console, CTRL-C will do the trick.
CTRL-C does not work from the NetBeans terminal emulator. This is a bug.

I'm sure this can be solved on Win32 by starting the process through
either a native or java wrapper, which could then exit the process
properly.

Thanks to look at that more closely,
Cedric
Comment 5 cberger 2002-05-07 11:38:53 UTC
On a related note, I would say the same issue exist with the Debugger.
How do I debug the shutdown hooks? I can put a breakpoint on the
shutdown thread, but how do I fire them? Shouldn't there be some User
Interface for that inside the debugger pane?
Thanks,
Cedric
Comment 6 David Strupl 2002-06-03 16:09:47 UTC
I have checked the code in NB that does "Terminate" functionality on
external processes. We do call Process.destroy()
 and thus the behaviour is really the same as described in 
http://developer.java.sun.com/developer/bugParade/bugs/4485742.html

So there is no bug in NB code IMHO. If you suggest to wrap the
external execution in another process - it is certainly possible but
what code would you call from the wrapper if running the hooks is not
guaranteed? I am closing this as WONTFIX - if you think that a wrapper
approach should be attempted please reopen with type ENHANCEMENT (and
possibly contribute a patch).

This is my test class:

public class TestShutdownHooks {
    
    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new MyHook());
        Thread.sleep(10000);
    }
    
    private static class MyHook extends Thread {
        public void run() {
            System.out.println("Hook called.");
        }
    }
}

I admit that if you do Ctrl-C from the console the string is printed
unlike "Terminate" from the IDE. But I don't know what can we do about
it (if #4485742 is not solved).
Comment 7 cberger 2002-06-06 11:20:35 UTC
Ok, as suggestedm, I'm reopening this as ENHANCEMENT, with patches!

To exit the process cleanly, and run the finalizers, one needs
to call the function Runtime.exit() inside the target VM. to do
that, there need to be some kind of communications between the
IDE and the target program. I've written here a pure-java version
of this approach, using a very simple TCP/IP communication
protocol between the IDE and the running program. Other options
are possible do do the same thing, including writing a similar
proxy in native code, or using JPDA to invoke Runtime.exit().

Four classes follows:

- TestShutdownHooks: a slightly modified version of your test case
- SimulateIDE: Simulare the IDE and launch the target program twice,
  once exiting cleanly the program, and the other aborting it.
- ClientProxy: The new entry point of the target program, which will
  itself call the real program entry point
- ServerProxy: a subclass of Process which with additional exit()
  and abort() functions.

Running SimulateIDE should give the followinfg 
  CHILD: arg[0]: 1
  CHILD: arg[1]: 2
  CHILD: arg[2]: 3
  CHILD: Hook called.
  CHILD: arg[0]: 4
  CHILD: arg[1]: 5
  CHILD: arg[2]: 6

I know nothing about the NetBeans code, so I cannot write complete
patches, so what should be done is:
- add menu entries for "Exit process" and "Abort process"
- make sure that the proxy code is added to the bootclasspath
 (-Xbootclasspath:/a,)

I believe my code is safe. What do you think?
Thanks for looking at that issue.
Cedric


package to.berger.nb;
public class TestShutdownHooks {
    public static void main(String[] args) throws Exception {
        Runtime.getRuntime().addShutdownHook(new MyHook());
        for(int i = 0; i < args.length; i++)
            System.out.println("arg["+i+"]: "+args[i]);
        Thread.sleep(10000);
    }
    private static class MyHook extends Thread {
        public void run() {
            System.out.println("Hook called.");
        }
    }
}

package to.berger.nb;
import java.io.*;
public class SimulateIDE {
    public static void main(String[] args) throws Exception {
        ServerProxy sp;
        sp = ServerProxy.execJava(
            "to.berger.nb.TestShutdownHooks",
            new String[] { "1", "2", "3" });
        new ReadThread(sp).start();
        Thread.sleep(2000);
        sp.exit();
        sp.waitFor();
        sp = ServerProxy.execJava(
            "to.berger.nb.TestShutdownHooks",
            new String[] { "4", "5", "6" });
        new ReadThread(sp).start();
        Thread.sleep(2000);
        sp.abort();
        sp.waitFor();        
    }
    static class ReadThread extends Thread {
        BufferedReader reader;
        ReadThread(ServerProxy sp) {
            reader = new BufferedReader(
                new InputStreamReader(sp.getInputStream()));
        }
        public void run() {
            try {
                for(;;) {
                    String s = reader.readLine();
                    if(s == null)
                        break;
                    System.out.println("CHILD: "+s);
                }
            } catch(Exception ex) {}
        }
    }
}

package to.berger.nb;
public class ClientProxy {
    public static void main(String args[]) throws Exception {
        int port = Integer.parseInt(args[1]);
        final java.net.Socket so = new java.net.Socket(
            java.net.InetAddress.getLocalHost(), port);        
        if(so.getInputStream().read() != 's')
            throw new java.io.IOException("handshake failed");
        Thread t = new Thread("IDE Proxy") {
            public void run() {
                try {
                    for(;;) {
                        switch(so.getInputStream().read()) {
                            case 'x':
                                Runtime.getRuntime().exit(0);
                                break;
                            case 'a': 
                                Runtime.getRuntime().halt(0);
                                break;
                        }
                    }
                } catch(java.io.IOException ex) { /* die */ }
            }
        };
        t.setDaemon(true);
        t.start();
        String[] nargs = new String[args.length-2];
        System.arraycopy(args, 2, nargs, 0, nargs.length);
        Class klass = Class.forName(args[0]);
        java.lang.reflect.Method m = klass.getDeclaredMethod(
            "main", new Class[] { nargs.getClass() });
        m.invoke(null, new Object[] { nargs });
    }
}

package to.berger.nb;
import java.net.*;
import java.io.*;
public class ServerProxy extends Process {   
    private Process p;
    private Socket so;
    private ServerProxy(Process p, Socket so) {
        this.p = p; 
        this.so = so;
    }
    
    public void destroy() { p.destroy(); }
    public int exitValue() { return p.exitValue(); }
    public InputStream getErrorStream() { return p.getErrorStream(); }
    public InputStream getInputStream() { return p.getInputStream(); }
    public OutputStream getOutputStream() { return p.getOutputStream(); }
    public int waitFor() throws InterruptedException { return
p.waitFor(); }

    /**
     * exit the process cleanly - after running shutdown threads
     */
    public void exit() throws IOException {
        so.getOutputStream().write('x');
    }

    /**
     * abort the process - without running shutdown threads
     */
    public void abort() throws IOException {
        so.getOutputStream().write('a');
    }
    
    private static ServerSocket sso;
    static synchronized ServerProxy execJava(
        String klass, String[] args) throws IOException 
    {
        if(sso == null)
            sso = new ServerSocket(0);
        String[] nargs = new String[args.length+4];
        System.arraycopy(args, 0, nargs, 4, args.length);
        nargs[0] = "java";
        nargs[1] = "to.berger.nb.ClientProxy";
        nargs[2] = klass;
        nargs[3] = Integer.toString(sso.getLocalPort());
        Process p = Runtime.getRuntime().exec(nargs);
        Socket so = sso.accept();
        so.getOutputStream().write('s');
        return new ServerProxy(p, so);
    }
}

 
Comment 8 David Strupl 2002-06-06 14:06:47 UTC
Cool!!!

Your code could be wrapped in a (small) module. I hope it is not very
hard to create such a module. You can follow these steps:

1. Download (or autoupdate) an appropriate version of the apisupport
module (http://apisupport.netbeans.org/) (category Apisupport on the
update center)

2. mount a fresh directory and create a package structure you want
your classes to be in

3. Create a new module (New --> NetBeans Extensions --> IDE Plug-in
module) (probably in the root of the newly mounted fs)

4. create new executor (New --> NetBeans Extensions --> Execution API
--> External Executor) (cannot be in default package, use the package
created in step 2.)

5. Edit the generated code -- ad your classes etc.
You can find inspiration in class
org.netbeans.modules.java.JavaProcessExecutor
(this is the one used by default)

6. after compiling the class, Ctrl-C on your executor class and paste
it under node describing your module into category ServiceTypes

7. compile and execute the newly created module - it will
automatically install your executor

8. go to Tools --> Options --> Debugging and Executing --> Execution
types --- it should be there or you can create a new instance by
clicking Execution Types --> New ...

9. set the new executor for the files you want to execute with it

10. contribute your module back to NetBeans.org !

It is a bit of work, I know. But before the idea is tested as a
standalone module I wouldn't dare to put it directly into
JavaProcessExecutor.

If you have any questions, please don't hesitate to ask either here or
on nbdev mailing list.
Comment 9 Marek Grummich 2002-07-22 08:31:07 UTC
Target milestone was changed from '3.4' to TBD.
Comment 10 Marek Grummich 2002-07-22 08:36:03 UTC
Target milestone was changed from '3.4' to TBD.
Comment 11 David Strupl 2002-07-23 15:27:45 UTC
If the enhancement is ready in 4.0 timeframe it can go in. But it is
not ready --> so marking the target milestone as future.
Comment 12 Marian Mirilovic 2003-03-13 13:42:43 UTC
Changed owner David S. -> David K.
Comment 13 Marian Mirilovic 2004-01-14 15:24:49 UTC
changing owner dkonecny -> pnejedly
Comment 14 Jesse Glick 2004-03-16 17:22:00 UTC
May no longer apply when using Ant to run the process, TBD.
Comment 15 Antonin Nebuzelsky 2008-02-19 14:00:03 UTC
Reassigning to new module owner Tomas Holy.
Comment 16 Jesse Glick 2010-05-04 23:14:44 UTC
I doubt we want to use a launcher wrapper; more danger of problems than is justified by this issue.

Bug #68770 might provide a way to send a cleaner process termination signal.

But a java.lang.Process.exit() call would be most welcome.
Comment 17 Martin Balin 2016-07-07 08:37:20 UTC
This old bug may not be relevant anymore. If you can still reproduce it in 8.2 development builds please reopen this issue.

Thanks for your cooperation,
NetBeans IDE 8.2 Release Boss
Comment 18 shoujin 2017-05-12 17:22:33 UTC
I have just got this too. Very annoying.