4

I'm trying to write a Swing application in Java that also runs the Google AppEngine Dev-Server (see Developing a Java Application that uses an AppEngine database) and am running into a strange problem with the Swing Eventloop.

I have the following two classes:

A debug-window, which will eventually receive log messages, etc:

public class DebugWindow {

    private static JFrame    debugWindow  = null;
    private static JTextArea debugContent = null;

    public static void show() {
        debugWindow = new JFrame("Debug");
        debugWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        debugContent = new JTextArea("Debug messages go here!");
        debugWindow.add(debugContent, BorderLayout.CENTER);
        debugWindow.pack();
        debugWindow.setVisible(true);
    }
}

A helper-class that loads the Google AppEngine Dev-Server:

// other imports
import com.google.appengine.tools.development.DevAppServerMain;

public class DevServer {
    public static void launch(final String[] args, boolean waitFor) {
        Logger logger = Logger.getLogger("");
        logger.info("Launching AppEngine server...");
        Thread server = new Thread() {
            @Override
            public void run() {
                try {
                    DevAppServerMain.main(args);  // run DevAppServer
                } catch (Exception e) { e.printStackTrace(); }
            }
        };
        server.setDaemon(true);  // shut down server when rest of app completes
        server.start();          // run server in separate thread
        if (!waitFor) return;    // done if we don't want to wait for server
        URLConnection cxn;
        try {
            cxn = new URL("http://localhost:8888").openConnection();
        } catch (IOException e) { return; }  // should never happen
        boolean running = false;
        while (!running) {
            try {
                cxn.connect();  // try to connect to server
                running = true;
            } catch (Exception e) {}
        }
        logger.info("Server running.");
    }
}

My main(...) method looks like this:

public static void main(final String[] args) throws Exception {
    DevServer.launch(args, true);  // launch and wait for AppEngine dev server
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            DebugWindow.show();  // create and show debug window
        }
    });
}

With this I'm getting some very strange behavior regarding the Swing Eventloop:

  1. First, the way Swing should work: If I comment out the line DevServer.launch(...) in main(...), the application launches, shows the debug window, keeps running, and when I close the debug window, it shuts down.
  2. If I add DevServer.launch(...) back in, it launches the server as expected, and then quits immediately (it probably also showed the debug window briefly, but it's too quick to see).
  3. If I move DevServer.launch(...) line after SwingUtilities.invokeLater(...), it shows the debug window, then launches the server, and when the server is up, it quits immediately.
  4. Now it get's really weird: If I change the line to DevServer.launch(args, false), i.e. I don't wait for the server to actually launch, but simply let my main(...) method complete immediately, the debug window shows, the server loads correctly, the application keeps running, but doesn't quit if I close the debug window?!
  5. If I then also change JFrame.DISPOSE_ON_CLOSE to JFrame.EXIT_ON_CLOSE, the debug window shows, the server loads correctly, the application keeps running, and it quits correctly if I close the debug window.

Any idea what is going on with the Swing event loop here? I'm stumped... Are there things that will cause the Swing event loop to terminate early (scenario 2 and 3)? Do multi-threaded applications prevent Swing from detecting the last disposed window (scenario 4)?

For reference, here is the source of the Google AppEngine Dev Server.

Community
  • 1
  • 1
Markus A.
  • 12,349
  • 8
  • 52
  • 116
  • When you catch an exception that "should never happen" like the `IOException` above, is always a good idea to rethrow it as a `RuntimeException`, just to prove your point. Shallowing is never a good idea – c.s. Jul 20 '13 at 10:51
  • @c.s. Agreed. I actually defined a `ShouldNeverHappenException` object for myself for exactly this reason, which I always use. I just removed it from the code sample, since I didn't want to post another class for completeness... – Markus A. Jul 20 '13 at 10:55

1 Answers1

1

Items #4 and #5 are actually expected behavior. A Java/Swing application doesn't stop when the last Swing window is disposed, but when the last thread stops executing. These two conditions are equivalent for single-threaded applications, but not for multi-threaded ones.

As for #1, #2 and #3: looking through the AppEngine Dev Server code, I noticed a fair amount of System.exit(int) calls in there. One of these is probably the culprit. If the code you're showing is all that's relevant, then the offending System.exit is likely called in response to the connection established after if (!waitFor) return; (due to #4)

Daan
  • 484
  • 4
  • 13
  • You're probably correct that #4 is caused by some thread held open by the Dev Server, but if #2 and #3 were caused by one of the `System.exit(...)`'s in the Dev Server, why would #4 and #5 not quit immediately as well? – Markus A. Jul 20 '13 at 09:44
  • #4 and #5 don't seem to actually connect to the Dev server. At least not in the code you've posted. My guess is that the Dev server startup (anything above `if (!waitFor) return;`) is fine, but something in the Dev server's connection handling (which is only triggered if `waitFor` is `true`) is causing it to issue a `System.exit` call. – Daan Jul 20 '13 at 09:54
  • The strange thing is that if I put a `System.out.println(...)` after `DevServer.launch(args, true)`, it does get executed, so the act of connecting alone doesn't kill the program. Also, if I put a `Thread.sleep(10000)` at the end of the `main(...)` method in case #2 or #3, the server will run for 10s and handle requests just fine, but as soon as the `main(...)` method actually terminates, the application quits. So the application shutdown is really triggered by the completion of the `main(...)` method as if the Swing event loop is no longer running... – Markus A. Jul 20 '13 at 09:59
  • The plot thickens: http://stackoverflow.com/questions/17760951/propagation-daemon-status-to-all-child-threads-in-java (answer by rahulserver). Looks like the Dev Server threads should then automatically also be marked as daemons. Looking through the Dev Server code, there also are only `setDaemon(true)` calls, i.e. nowhere should a thread be marked back to non-daemon status. This means there shouldn't be any lingering threads keeping the program running in case #4 either... Strange... – Markus A. Jul 20 '13 at 10:09
  • Strange indeed. I would be interested in seeing the output of `Thread.isDaemon()` from the EDT. It's a long shot, but if the daemon-ness of the Dev server thread somehow trickled down to the EDT, that might explain #1, #2 and #3. – Daan Jul 20 '13 at 10:32
  • Unfortunately, this also doesn't explain it. I checked the daemon-ness of the `main(...)`-thread and it doesn't change (still `false` at all time). The EDT from within the `run(...)` method of the `invokeLater(...)` call in scenario 2 also does not claim to be a daemon. – Markus A. Jul 20 '13 at 10:53