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.
Summary: | Shutdown hooks are not performed when the process is terminated from the IDE | ||
---|---|---|---|
Product: | projects | Reporter: | cberger <cberger> |
Component: | Ant | Assignee: | 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
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 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 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. 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 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 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). 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); } } 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. Target milestone was changed from '3.4' to TBD. Target milestone was changed from '3.4' to TBD. 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. Changed owner David S. -> David K. changing owner dkonecny -> pnejedly May no longer apply when using Ant to run the process, TBD. Reassigning to new module owner Tomas Holy. 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. 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 I have just got this too. Very annoying. |