NullPointerException dans invokeLater en cours D'exécution via Java Webstart

après avoir passé de JRE 1.7.0_21 à 1.7.0_25-b15, mon application a commencé à lancer NullPointerException dans Swinguitilities.invokeLater(...) lorsqu'il est lancé à partir de Java WebStart. Étonnamment quand il est exécuté comme une application autonome (en dehors de JWS), il fonctionne très bien.

voici le haut de la pile:

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.setBaseUnits(AppletView.java:536)
    (...)

pour obtenir une image complète: la méthode setBaseUnits(..) est appelé comme un callback depuis RMI par le serveur distant. Le tracé complet de la pile est tout à fait long.

y a-t-il quelque chose dans le modèle de sécurité qui a changé dans le RMI ou le JWS qui pourrait casser des choses ? Si c'est le cas, je m'attendrais à une exception de sécurité, mais cela pourrait être quelque chose qui n'est pas correctement détecté dans JRE et conduit à NPE.

toute suggestion est appréciée.


---- Update1:

il y a des problèmes similaires avec JRE 1.7.0_25 update probablement concernant certains changements de sécurité et AppContext objet: https://forums.oracle.com/message/11080621 https://forums.oracle.com/thread/2552799 . J'ai essayé la suggestion de correction: https://forums.oracle.com/message/11082162#11082162 mais sans succès.

je vois 3 threads AWT-EventQueue dans mon application avec des nombres de 0 à 2. Il semble que JRE crée des files d'événements supplémentaires pour différents contextes d'application si le programme est lancé par JWS. Il y a 3 AppContext et 3 EVTs dans Témoins de jéhovah et il y a un seul contexte et EVT si le programme est exécuté à partir de l'IDE.


---- Update2:

il y a une solution de contournement comme suggéré par guruman ci-dessous (merci beaucoup). Malheureusement tous les appels à la SwingUtilities.invokeLater(..) des threads RMI doivent être remplacés, et le programme commence à dépendre de L'API interne Sun JRE.

je suis toujours à la recherche d'une approche plus générale non spécifique à Sun JRE. Je pense que C'est un JRE bug. Peut-être qu'il pourrait être corrigé d'une façon ou d'une autre: AppContext ne devrait pas être null dans le thread RMI.


---- Update3:

j'ai fait un cas de test simple pour montrer le problème. Il se compose de 4 fichiers. Pour exécuter ce cas de test, il faut signer le pot de destination (TestCase.pot.) Tout d'abord spécifier correct code dans le lancement.jnlp, puis lancer le serveur par Java Web Start (par ex. à l'aide de javaws lancement.jnlp). Un cadre suivant doit apparaître sur le écran:

The server application frame after start

alors le client RMI pourrait être exécuté. Après une exécution réussie, le cadre devrait consister en:

The server application frame after successful RMI call

mais si vous essayez d'exécuter le serveur en utilisant JWS vous obtiendrez l'exception suivante dans le programme client (l'exception est propagée du serveur RMI au client RMI):

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.run(Transport.java:177)
    at sun.rmi.transport.Transport.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)

alors voici les fichiers de cas de test:

1) Définition du fichier JNLP lancer.jnlp:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/home/user/NetBeansProjects/TestCase/dist/" href="launch.jnlp" spec="1.0+">
    <information>
        <title>TestCase</title>
        <vendor>digital_infinity</vendor>
        <homepage href=""/>
        <description>TestCase</description>
        <description kind="short">TestCase</description>
    </information>
<security>
  <all-permissions/>
</security>
    <update check="always"/>
    <resources>
        <j2se version="1.7+"/>
        <jar href="TestCase.jar" main="true"/>
    </resources>
    <application-desc main-class="testcase.RmiServiceImpl">
    </application-desc>
</jnlp>

2) définition de l'interface RMI (RmiService.java):

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

3) code de service RMI et classe principale de service:

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) un 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 que j'ai soumis: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019272

Autres bugs:

17
demandé sur digital_infinity 2013-06-24 16:16:08

5 réponses

le problème se produit dans L'environnement Webstart. Avant la version Webstart de Java 7u25, L'AppContext était défini sur le groupe de threads système. Pourtant, il est défini sur le groupe de thread principal.

si vous avez un thread basé sur un groupe de threads dont le parent ou le grand-parent n'est pas le groupe de threads principal, il n'a pas de soleil.awt.AppContext.

vous devez créer votre thread basé sur le groupe de threads du gestionnaire de sécurité s'il en existe un.

Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
                                    ? System.getSecurityManager().getThreadGroup()
                                    : Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);
5
répondu guruman 2013-06-25 08:42:48

j'ai trouvé ce que je crois être une meilleure solution à ce bug.

je viens d'ajouter le code suivant avant d'appeler SwingUtilities ou n'importe quelle méthode de Composant liée à Swing. Il crée un nouveau AppContext pour le Thread RMI (le thread RMI doit être le Thread courant lors de l'exécution du code ci-dessous).

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

en raison des besoins de mon application, j'ai pu l'ajouter sur une seule méthode qui utilisait Swinguitilities, mais vous pouvez avoir besoin de l'ajouter à chaque méthode sur votre RMI appelable Objet.

le code ne doit être lancé qu'une seule fois, alors vérifiez le comportement de votre application.

10
répondu Mauricio Rocha 2013-11-20 17:28:30

Voici une solution de contournement pour JDK-8019274, empaqueté dans une classe utilitaire. Pour nous, invokeAndWait () était toujours un problème. Cet exemple a l' correction existante pour invokeLater () et une nouvelle correction pour invokeAndWait ().

Notes:

  • vous devrez inclure le jnlp.jar dans votre projet.
  • Appel de la fonction init() au début de votre méthode main (), avant d'appeler invokeLater()
  • remplacez tous vos appels à SwingUtilities invokeLater() et invokeAndWait() par ceux-ci appels

(avis de non responsabilité: C'est à partir de notre produit. Certains aspects de cette solution peuvent ne pas s'appliquer à vous.)

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;
    }
}
3
répondu uinerd 2013-09-27 15:08:29

Java 7u65 qui est sorti hier (2014-07-15) prétend avoir corrigé ce problème ou un problème très similaire, dans JDK-8019724. Je suis en train de tester pour le savoir - un pilote d'un de nos fournisseurs ne fonctionne pas sous Java Web Start, et il nous a gardé sur Java 6.

ETA: Oui, On dirait que cela résout nos problèmes!

2
répondu skiphoppy 2014-07-16 16:39:42

cela se passe toujours sur Mac Sierra. Mais je suis capable de le contourner par l'appel:

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

juste avant mon premier Swinguitilities.invokeLater(...) appeler.

0
répondu Brian Sutton 2018-07-19 16:32:44