NullPointerException in invokeLater while running through Java Webstart
Asked Answered
D

5

18

After upgraded from JRE 1.7.0_21 to 1.7.0_25-b15 my application started to throw NullPointerException in SwingUtilities.invokeLater(...) when it is run from Java WebStart. Surprisingly when it is executed as a standalone application (outside JWS), it works great.

Here is the top of the stack:

Exception in thread "AWT-EventQueue-2" java.lang.NullPointerException
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
at AppletView$8.setBaseUnits(AppletView.java:536)
    (...)

To get you full picture: the method setBaseUnits(..) is called as a callback from RMI by remote server. The full stack trace is quite long.

Is there something in security model that changed in RMI or JWS that could break things ? If so I would expect some security exception, but it could be something that is not correctly detected in JRE and leads to NPE.

Any suggestions are appreciated.


---- Update1:

There are similar issues with JRE 1.7.0_25 update probably regarding some security changes and AppContext objects: https://forums.oracle.com/message/11080621 https://forums.oracle.com/thread/2552799 . I tried suggested fix: https://forums.oracle.com/message/11082162#11082162 but without any success.

I can see 3 AWT-EventQueue threads in my application with numbers from 0 to 2. It looks like JRE creates additional event queues for different application contexts if program is started by JWS. There are 3 AppContext and 3 EVTs in JWS and there is only one context and EVT if program is executed from IDE.


---- Update2:

There is a workaround as suggested by guruman below (thanks a lot). Unfortunately all the calls to the SwingUtilities.invokeLater(..) from RMI threads must be replaced, and the program starts to depend on Sun JRE internal API.

I am still looking for more general approach not specific to Sun JRE. I think it is a JRE bug. Maybe it could be patched somehow: AppContext should not be null in RMI thread.


---- Update3:

I've made a simple test case to show the problem. It consists 4 files. To run this test case one need to sign the destination jar (TestCase.jar). First of all specify correct codebase in launch.jnlp, then run the server by Java Web Start (eg. using javaws launch.jnlp). A following frame should appear on the screen:

The server application frame after start

Then the RMI client could be executed. After successful execution the frame should consist:

The server application frame after successful RMI call

but if You try to execute the server using JWS You will get the following exception in the client program (the exception is propagated from RMI server to RMI client):

Exception in thread "main" java.lang.NullPointerException
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
    at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
    at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
    at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
    at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
    at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
    at testcase.RmiServiceImpl.callBack(RmiServiceImpl.java:70)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
    at sun.rmi.transport.Transport$1.run(Transport.java:177)
    at sun.rmi.transport.Transport$1.run(Transport.java:174)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
    at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
    at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
    at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
    at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
    at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
    at com.sun.proxy.$Proxy0.callBack(Unknown Source)
    at testcase.RmiClient.main(RmiClient.java:22)

So here they are the test case files:

  1. JNLP file definition launch.jnlp:

    TestCase digital_infinity TestCase TestCase
  2. RMI interface definition (RmiService.java):

    package testcase;
    public interface RmiService extends java.rmi.Remote { void callBack() throws java.rmi.RemoteException; }

  3. RMI service code and the service main class:

    package testcase;

    import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.SwingUtilities;

    /** */ public class RmiServiceImpl extends java.rmi.server.UnicastRemoteObject implements RmiService {

     final static int PORT = 1099;
    
     static JFrame frame;
     static JTextField textField;
    
     public RmiServiceImpl() throws RemoteException {
         super(PORT);
     }
    
     /**
      * @param args the command line arguments
      */
     public static void main(String[] args) throws Exception {
         Registry reg;
         RmiServiceImpl service = new RmiServiceImpl();
         try {
             reg = LocateRegistry.getRegistry(PORT);
             reg.rebind("test", service);
         } catch (RemoteException ex) {
             reg = LocateRegistry.createRegistry(PORT);
             reg.rebind("test", service);
         }
         SwingUtilities.invokeAndWait(new Runnable() {
             @Override
             public void run() {
                 frame = new JFrame("Test App");
                 textField = new JTextField("Before call to callBack");
                 frame.getContentPane().add(textField);
                 frame.pack();
                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                 frame.setVisible(true);
             }
         });
     }
    
     /** RMI callback */
     public void callBack() {
         Runnable rn = new Runnable() {
             public void run() {
                 textField.setText("CallBack succesfully called.");
                 frame.pack();
             }
         };
         SwingUtilities.invokeLater(rn);
     }
    

    }

  4. Simple client code:

    package testcase;

    import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;

    public class RmiClient { public static void main(String[] args) throws Exception { //now we trying to communicate with object through RMI Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT); //after got the registry, lookup the object and finally do call RmiService serv = (RmiService) reg.lookup("test"); serv.callBack(); } }


---- Update4:

JRE Bug I submitted: https://bugs.java.com/bugdatabase/view_bug?bug_id=8019272

Other related bugs:

Dorotea answered 24/6, 2013 at 12:16 Comment(15)
Why create a second event queue?Phallicism
@Phallicism I'm never used JWS, maybe this thread could be interestingTaboret
@Phallicism There is only one event queue. Why do you ask about the second ?Dorotea
@Taboret thanks, your link about AppContexts looks very interesting indeed.Dorotea
@Dorotea because in Exception is about AWT-EventQueue-2, please to ensure us that SecondaryLoop isn't used,Taboret
@Taboret Oh, I did not notice that before (AWT-EventQueue-2). There is not any explicit declaration of second event queue. Maybe the RMI callback creates the second queue implicitly. This is not my intention to create any additional AWT queues.Dorotea
@Phallicism I saw, read this thread carrefully, before with my upvote, not sure then I notified :-), interesting exception with multiple of AWT QueuesTaboret
@mKorbel: On closer examination, I also see the extra queues when running via JWS, but the application runs normally. digital: See also Initial Threads.Phallicism
Is it possible that the code has always been vulnerable to NullPointerException, but the condition has never occurred in previous JVM versions? Perhaps there is a race condition somewhere amongst these threads. Such problems may not always cause exceptions at runtime, but as external circumstances change, the problems can start to appear, even if the code that throws the exception has not changed in a long time.Kathrinkathrine
@ByronHawkins Yes,I know that it is possible to have a race condition, but I think it is not the case. The main() function constructs GUI in invokeLater(). While GUI construction another invokeLater() is called with RMI initialization, so it goes to the end of the queue. I believe it is acceptable to have a invokeLater() in another invokeLater(). The RMI callback can be accepted when GUI is finally constructed. Also my problem is quite similar to this:<#17223804>. I think it's rather not a circumstance.Dorotea
A little correction to my previous comment: the main() method calls invokeAndWait(..) not invokeLater().Dorotea
@Phallicism Thanks for the link to the Initial Threads tutorial. My application is written due to the rules outlined in it. I hope so at least. I still don't understand the reasons of additional EVT-s existence in my case. I know they could be created for applets, but my application is a RIA started by JWS.Dorotea
@digital_infinity: FWIW, even a plain old swing application with one queue shows the extra queues when running via JWS.Phallicism
FWIW, I've submitted a bug including sample code with Oracle and am awaiting a response from them. I'm sure it's probably already been submitted but I can't see that in their bug DB. (Bug ID: 9004463 ) Not visible yet.Iniquity
@Iniquity Thanks for reporting the bug. Your bug ID is still not visible. The FAQ on the Oracle bug database says that bug must wait for a review and could possibly be not visible due to security reasons. Maybe that's the case.Dorotea
J
5

The problem occurs in the Webstart environment. Before Webstart version of Java 7u25 the AppContext was set on the system thread group. Yet it is set on the main thread group.

If you have a thread based on a thread group where its parent or grandparent is not the main thread group it has no sun.awt.AppContext.

You should create your thread based on the thread group of the security manager if one exists.

Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
                                    ? System.getSecurityManager().getThreadGroup()
                                    : Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);
Jessamyn answered 25/6, 2013 at 8:42 Comment(7)
interesting insight - but a real bummer: it breaks existing code in security restricted contexts ..Defer
I don't create the RMI threads. They are created by RMI environment after exporting the objects. Is there any way to alter the RMI thread creation ? Without this I need to create a separate mechanism for all invokeLater() from RMI in the application. I mean my thread with some input queue of Runnable objects that calls invokeLater() with the objects put in the queue.Dorotea
If you cannot change the thread group of your threads than you could call sun.awt.SunToolkit#invokeLaterOnAppContext(AppContext, Runnable) instead of SwingUtilities#invokeLater(Runnable).Jessamyn
You can get all AppContexts by calling sun.awt.AppContext#getContexts(). It returns a Set<AppContext> of all existing AppContexts.Jessamyn
@Jessamyn Ok I did as you suggested. The application saves the current AppContext in EVT: evtContext = AppContext.getAppContext(); and then instead of invokeLater it calls sun.awt.SunToolkit.invokeLaterOnAppContext(evtContext, runnable); . Unfortunately all the calls to SwingUtilities.invokeLater(..) from RMI threads must be replaced and program now uses internal Sun JRE proprietary API.Dorotea
just for the record, copied from deleted answer: have the same problem but with SwingUtilities.isEventDispatchThread() - any ideas for that? (@digital_infinity)Defer
The issue with SwingUtilities.isEventDispatchThread() is exactly the same problem symptoms, see linked question [https://mcmap.net/q/741482/-appcontext-is-null-from-rmi-thread-with-java-7-update-25/1326149]Dorotea
K
10

I found what I believe to be a better solution to this bug.

I just added the following code before calling SwingUtilities or any Swing related component method. It create a new AppContext for the RMI Thread (RMI thread must be the current Thread when running the code below).

if(AppContext.getAppContext() == null){
    SunToolkit.createNewAppContext();
}

Due to the needs of my application I was able to add it on a single method that was using SwingUtilities, but you may need to add it to every method on your RMI Callable Object.

The code needs to run only once, so check the behavior of your application.

Keese answered 20/11, 2013 at 17:28 Comment(1)
I experienced this same problem in a JavaFX application with WebStart when I used java.imageio.ImageIO to load some images. I was reusing the image processing code from a Swing library and converting the final result into an FX compatible image. Creating the AppContext code here helped fix the issue.Postmistress
J
5

The problem occurs in the Webstart environment. Before Webstart version of Java 7u25 the AppContext was set on the system thread group. Yet it is set on the main thread group.

If you have a thread based on a thread group where its parent or grandparent is not the main thread group it has no sun.awt.AppContext.

You should create your thread based on the thread group of the security manager if one exists.

Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
                                    ? System.getSecurityManager().getThreadGroup()
                                    : Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);
Jessamyn answered 25/6, 2013 at 8:42 Comment(7)
interesting insight - but a real bummer: it breaks existing code in security restricted contexts ..Defer
I don't create the RMI threads. They are created by RMI environment after exporting the objects. Is there any way to alter the RMI thread creation ? Without this I need to create a separate mechanism for all invokeLater() from RMI in the application. I mean my thread with some input queue of Runnable objects that calls invokeLater() with the objects put in the queue.Dorotea
If you cannot change the thread group of your threads than you could call sun.awt.SunToolkit#invokeLaterOnAppContext(AppContext, Runnable) instead of SwingUtilities#invokeLater(Runnable).Jessamyn
You can get all AppContexts by calling sun.awt.AppContext#getContexts(). It returns a Set<AppContext> of all existing AppContexts.Jessamyn
@Jessamyn Ok I did as you suggested. The application saves the current AppContext in EVT: evtContext = AppContext.getAppContext(); and then instead of invokeLater it calls sun.awt.SunToolkit.invokeLaterOnAppContext(evtContext, runnable); . Unfortunately all the calls to SwingUtilities.invokeLater(..) from RMI threads must be replaced and program now uses internal Sun JRE proprietary API.Dorotea
just for the record, copied from deleted answer: have the same problem but with SwingUtilities.isEventDispatchThread() - any ideas for that? (@digital_infinity)Defer
The issue with SwingUtilities.isEventDispatchThread() is exactly the same problem symptoms, see linked question [https://mcmap.net/q/741482/-appcontext-is-null-from-rmi-thread-with-java-7-update-25/1326149]Dorotea
S
3

Here is a workaround for JDK-8019274, packaged in a utility class. For us, invokeAndWait() was still an issue. This example has the existing fix for invokeLater() and a new fix for invokeAndWait().

Notes:

  • You'll need to include the jnlp.jar in your project.
  • Call init() early in your main() method, before calling invokeLater()
  • Replace all your calls to SwingUtilities invokeLater() and invokeAndWait() with these calls

(Disclaimer: This is from our product. Some aspects of this solution may not apply to you.)

public class JreFix {
    private static String badVersionInfo = null;
    private static AppContext awtEventDispatchContext = null;
    private static AppContext mainThreadContext = null;
    private static Boolean isWebStart = null;
    private static BasicService basicService = null;
    private static IntegrationService integrationService = null;

    /**
     * Call this early in main().  
     */
    public static void init() {
        if (isWebstart() && isApplicableJvmType()) {
            String javaVersion = System.getProperty("java.version");

            if ("1.7.0_25".equals(javaVersion)) {
                badVersionInfo = "7u25";
            }
            else if ("1.7.0_40".equals(javaVersion)) {
                badVersionInfo = "7u40";
            }
            else if (javaVersion != null && "1.6.0_51".equals(javaVersion.substring(0,8))) {
                badVersionInfo = "6u51";
            }
            else if ("javaws-10.25.2.16".equals(System.getProperty("javawebstart.version"))) {
                badVersionInfo = "Web Start 10.25.2.16";
            }
        }

        if (badVersionInfo != null) {
            mainThreadContext = AppContext.getAppContext();
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        awtEventDispatchContext = AppContext.getAppContext();
                    }
                });
            }
            catch (Exception e) {
                displayErrorAndExit(null);
            }

            if (mainThreadContext == null || awtEventDispatchContext == null) {
                 displayErrorAndExit(null);
            }
        }
    }

    public static void invokeNowOrLater(Runnable runnable) {
        if (hasAppContextBug()) {
            invokeLaterOnAwtEventDispatchThreadContext(runnable);
        }
        else {
            SwingUtilities.invokeLater(runnable);
        }
    }

    public static void invokeNowOrWait(Runnable runnable) {
        if (hasAppContextBug()) {
            fixThreadAppContext(null);
        }

        try {
            SwingUtilities.invokeAndWait(runnable);
        } 
        catch (Exception e) {
            // handle it
        }
    }

    public static boolean hasAppContextBug() {
        return isJreWithAppContextBug() && AppContext.getAppContext() == null;
    }

    public static void invokeLaterOnAwtEventDispatchThreadContext(Runnable runnable) {
        sun.awt.SunToolkit.invokeLaterOnAppContext(awtEventDispatchContext, runnable);
    }

    public static void fixThreadAppContext(Component parent) {
        try {
            final Field field = AppContext.class.getDeclaredField("threadGroup2appContext");
            field.setAccessible(true);
            Map<ThreadGroup, AppContext> threadGroup2appContext = (Map<ThreadGroup, AppContext>)field.get(null);
            final ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
            threadGroup2appContext.put(currentThreadGroup, mainThreadContext);
        } 
        catch (Exception e) {
            displayErrorAndExit(parent);
        }

        if (AppContext.getAppContext() == null) {
             displayErrorAndExit(parent);
        }
    }

    private static boolean isJreWithAppContextBug() {
        return badVersionInfo != null;
    }

    private static void displayErrorAndExit(Component parent) {
        JLabel msgLabel = new JLabel("<html>" + 
                "Our application cannot run using <b>Web Start</b> with this version of Java.<p><p>" +
                "Java " + badVersionInfo + " contains a bug acknowledged by Oracle (JDK-8019274).");
        JOptionPane.showMessageDialog(parent, msgLabel, "Java Version Error", JOptionPane.ERROR_MESSAGE);
        System.exit(1);
    }

    private static boolean isApplicableJvmType() {
        String vendor = System.getProperty("java.vendor");
        String vmName = System.getProperty("java.vm.name");
        if (vendor != null && vmName != null) {
            return vmName.contains("Java HotSpot") &&
                    (vendor.equals("Oracle Corporation") || 
                     vendor.equals("Sun Microsystems Inc."));
        }

        return false;
    }

    private static boolean isWebstart() {
        if (isWebStart == null) {
            try { 
                basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService");             
                isWebStart = true;
            } 
            catch (UnavailableServiceException e) { 
                isWebStart = false;
            }           

            try {
                integrationService = (IntegrationService) ServiceManager.lookup("javax.jnlp.IntegrationService");
            } 
            catch (UnavailableServiceException e) {
            }
        }
        return isWebStart;
    }
}
Sulemasulf answered 27/9, 2013 at 15:8 Comment(0)
T
2

Java 7u65 which came out yesterday (2014-07-15) claims to have fixed this or a very similar issue, in JDK-8019724. I'm testing right now to find out - a driver from one of our vendors doesn't function under Java Web Start, and it has been keeping us on Java 6.

ETA: Yes, it looks like this resolves our problems!

Twentyfour answered 16/7, 2014 at 16:21 Comment(0)
S
0

This is still happening on Mac Sierra. But I'm able to workaround it by calling:

        if (sun.awt.AppContext.getAppContext() == null) {
          sun.awt.SunToolkit.createNewAppContext();
        }

just before my first SwingUtilities.invokeLater(...) call.

Strop answered 19/7, 2018 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.