Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8181786

Extra runLater causes impossible states to be possible using javafx.embed.singleThread=true

    Details

    • Subcomponent:
    • Resolved In Build:
      b23
    • CPU:
      x86_64
    • OS:
      windows_7

      Backports

        Description

        FULL PRODUCT VERSION :
        java version "1.8.0_141"
        Java(TM) SE Runtime Environment (build 1.8.0_141-b11)
        Java HotSpot(TM) Client VM (build 25.141-b11, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows [Version 6.1.7601]

        A DESCRIPTION OF THE PROBLEM :
        I realize the javax.embed.singleThread=true is "experimental", but it seems to be widely adopted as people start to transition over to JavaFX.

        We are setting up some automated GUI testing and we noticed something odd happening. When we traced it back, it was because an accidental double-click was happening on a button. This button brought up a modal dialog.

        The bug is that, when running with the JavaFX system initialized, there is an extra "run later" happening in the swing event loop. This allows the modal dialog opening to be delayed one cycle; thereby letting the second click to also trigger the action. In the attached example, this causes the dialog to be opened twice.

        The impact of this bug is that it makes it difficult to transition over to JavaFX since using JavaFX in part of the application causes older parts to be vulnerable since states that were impossible before (e.g. two input dialogs) are now possible.

        Occasionally, the following exception occurs:

        Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.Thread cannot be cast to java.awt.EventDispat
                at java.awt.SequencedEvent.dispatch(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
        ...


        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        Compile the attached program and run it from the command line like this:
        "c:\Program Files (x86)\Java\jre1.8.0_121\bin\java.exe" -Djavafx.embed.singleThread=true -jar dist\JavaFXDoubleClick.jar

        Then run it from the command line like this:
        "c:\Program Files (x86)\Java\jre1.8.0_121\bin\java.exe" -Djavafx.embed.singleThread=false -jar dist\JavaFXDoubleClick.jar

        When the singleThread=true, this will open up two dialogs, but when it is false, the expected result of one dialog will open.
        The sleep is included in the test program so that it is easier to reproduce.
        In our application it is easily reproducible since we have enough logic surrounding window placement and such that it take takes that extra few milliseconds which allows the error to occur.

        The stack trace is listed in https://bugs.openjdk.java.net/browse/JDK-8088132, but the exception is not the problem I want to emphasize. The major problem is this extra cycle.

        The code path is in EventQueue.java.
        protected void dispatchEvent(final AWTEvent event) has code to handle the fx dispatcher. This eventually gets to a call to Platform.runLater() in the SwingFXUtils.FXDispatcher


        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        Modality would work as expected. Only one dialog would ever show
        ACTUAL -
        It is possible to get two modal dialogs

        ERROR MESSAGES/STACK TRACES THAT OCCUR :
        Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.Thread cannot be cast to java.awt.EventDispat
                at java.awt.SequencedEvent.dispatch(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
                at java.awt.EventQueue$3$1.run(Unknown Source)
                at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
                at java.security.AccessController.doPrivileged(Native Method)
                at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
                at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
                at com.sun.glass.ui.win.WinApplication._enterNestedEventLoopImpl(Native Method)
                at com.sun.glass.ui.win.WinApplication._enterNestedEventLoop(WinApplication.java:218)
                at com.sun.glass.ui.Application.enterNestedEventLoop(Application.java:511)
                at com.sun.glass.ui.EventLoop.enter(EventLoop.java:107)
                at com.sun.javafx.tk.quantum.QuantumToolkit.enterNestedEventLoop(QuantumToolkit.java:583)
                at javafx.embed.swing.SwingFXUtils$FwSecondaryLoop.lambda$enter$63(SwingFXUtils.java:332)
                at com.sun.javafx.application.PlatformImpl.runAndWait(PlatformImpl.java:317)
                at com.sun.javafx.application.PlatformImpl.runAndWait(PlatformImpl.java:307)
                at javafx.embed.swing.SwingFXUtils$FwSecondaryLoop.enter(SwingFXUtils.java:331)
                at java.awt.Dialog.show(Unknown Source)
                at java.awt.Component.show(Unknown Source)
                at java.awt.Component.setVisible(Unknown Source)
                at java.awt.Window.setVisible(Unknown Source)
                at java.awt.Dialog.setVisible(Unknown Source)
                at javafxdoubleclick.JavaFXDoubleClick.showDialog(JavaFXDoubleClick.java:70)
                at javafxdoubleclick.JavaFXDoubleClick.lambda$new$1(JavaFXDoubleClick.java:48)
                at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
                at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
                at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
                at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
                at javax.swing.plaf.basic.BasicButtonListener.focusLost(Unknown Source)
                at java.awt.Component.processFocusEvent(Unknown Source)
                at java.awt.Component.processEvent(Unknown Source)
                at java.awt.Container.processEvent(Unknown Source)
                at java.awt.Component.dispatchEventImpl(Unknown Source)
                at java.awt.Container.dispatchEventImpl(Unknown Source)
                at java.awt.Component.dispatchEvent(Unknown Source)
                at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source)
                at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source)
                at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
                at java.awt.Component.dispatchEventImpl(Unknown Source)
                at java.awt.Container.dispatchEventImpl(Unknown Source)
                at java.awt.Component.dispatchEvent(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
                at java.awt.EventQueue$3.run(Unknown Source)
                at java.awt.EventQueue$3.run(Unknown Source)
                at java.security.AccessController.doPrivileged(Native Method)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.awt.EventQueue$4.run(Unknown Source)
                at java.awt.EventQueue$4.run(Unknown Source)
                at java.security.AccessController.doPrivileged(Native Method)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.awt.EventQueue.dispatchEvent(Unknown Source)
                at java.awt.SentEvent.dispatch(Unknown Source)
                at java.awt.DefaultKeyboardFocusManager$DefaultKeyboardFocusManagerSentEvent.dispatch(Unknown Source)
                at java.awt.DefaultKeyboardFocusManager.sendMessage(Unknown Source)
                at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source)
                at java.awt.Component.dispatchEventImpl(Unknown Source)
                at java.awt.Container.dispatchEventImpl(Unknown Source)
                at java.awt.Window.dispatchEventImpl(Unknown Source)
                at java.awt.Component.dispatchEvent(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
                at java.awt.EventQueue$3.run(Unknown Source)
                at java.awt.EventQueue$3.run(Unknown Source)
                at java.security.AccessController.doPrivileged(Native Method)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.awt.EventQueue$4.run(Unknown Source)
                at java.awt.EventQueue$4.run(Unknown Source)
                at java.security.AccessController.doPrivileged(Native Method)
                at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
                at java.awt.EventQueue.dispatchEvent(Unknown Source)
                at java.awt.SequencedEvent.dispatch(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
                at java.awt.EventQueue$3$1.run(Unknown Source)
                at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
                at java.security.AccessController.doPrivileged(Native Method)
                at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
                at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
                at com.sun.glass.ui.win.WinApplication._enterNestedEventLoopImpl(Native Method)
                at com.sun.glass.ui.win.WinApplication._enterNestedEventLoop(WinApplication.java:218)
                at com.sun.glass.ui.Application.enterNestedEventLoop(Application.java:511)
                at com.sun.glass.ui.EventLoop.enter(EventLoop.java:107)
                at com.sun.javafx.tk.quantum.QuantumToolkit.enterNestedEventLoop(QuantumToolkit.java:583)
                at javafx.embed.swing.SwingFXUtils$FwSecondaryLoop.lambda$enter$63(SwingFXUtils.java:332)
                at com.sun.javafx.application.PlatformImpl.runAndWait(PlatformImpl.java:317)
                at com.sun.javafx.application.PlatformImpl.runAndWait(PlatformImpl.java:307)
                at javafx.embed.swing.SwingFXUtils$FwSecondaryLoop.enter(SwingFXUtils.java:331)
                at java.awt.Dialog.show(Unknown Source)
                at java.awt.Component.show(Unknown Source)
                at java.awt.Component.setVisible(Unknown Source)
                at java.awt.Window.setVisible(Unknown Source)
                at java.awt.Dialog.setVisible(Unknown Source)
                at javafxdoubleclick.JavaFXDoubleClick.showDialog(JavaFXDoubleClick.java:70)
                at javafxdoubleclick.JavaFXDoubleClick.lambda$new$1(JavaFXDoubleClick.java:48)
                at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
                at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
                at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
                at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
                at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
                at java.awt.Component.processMouseEvent(Unknown Source)
                at javax.swing.JComponent.processMouseEvent(Unknown Source)
                at java.awt.Component.processEvent(Unknown Source)
                at java.awt.Container.processEvent(Unknown Source)
                at java.awt.Component.dispatchEventImpl(Unknown Source)
                at java.awt.Container.dispatchEventImpl(Unknown Source)
                at java.awt.Component.dispatchEvent(Unknown Source)
                at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
                at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
                at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
                at java.awt.Container.dispatchEventImpl(Unknown Source)
                at java.awt.Window.dispatchEventImpl(Unknown Source)
                at java.awt.Component.dispatchEvent(Unknown Source)
                at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
                at java.awt.EventQueue.access$500(Unknown Source)
                at java.awt.EventQueue$3$1.run(Unknown Source)
                at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
                at java.security.AccessController.doPrivileged(Native Method)
                at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
                at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
                at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
                at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
                at java.lang.Thread.run(Unknown Source)


        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------
        /*
         * To change this license header, choose License Headers in Project Properties.
         * To change this template file, choose Tools | Templates
         * and open the template in the editor.
         */
        package javafxdoubleclick;

        import java.awt.FlowLayout;
        import java.lang.reflect.Method;
        import javax.swing.JButton;
        import javax.swing.JDialog;
        import javax.swing.JFrame;
        import javax.swing.JLabel;
        import javax.swing.JPanel;
        import javax.swing.SwingUtilities;

        /**
         *
         * @author ocMAClaassen
         */
        public final class JavaFXDoubleClick extends JFrame {
        private boolean odd = true;

        /**
        * @param args the command line arguments
        */
        public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new JavaFXDoubleClick());
        }
        private JavaFXDoubleClick() {
        try {
        Class<?> clazz;
        clazz = Class.forName("com.sun.javafx.application.PlatformImpl");
        Method m = clazz.getMethod("startup", Runnable.class);
        Runnable r = new Runnable() {
        @Override
        public void run() {
        }
        };
        m.invoke(null, new Object[]{r});
        }
        catch (Exception ex) {
        ex.printStackTrace();
        }

        JPanel panel = new JPanel(new FlowLayout());
        JButton button = new JButton("Double Click Me");
        button.addActionListener((e) -> showDialog());
        panel.add(button);
        setContentPane(panel);
        panel.setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(200, 100);
        setVisible(true);
        }
        private void showDialog() {
        System.err.println(System.getProperty("java.version") + " " + System.getProperty("javafx.embed.singleThread"));
        JDialog dialog = new JDialog(this);
        dialog.setModal(true);
        JPanel panel = new JPanel();
        panel.add(new JLabel("Modal Dialog"));
        dialog.setContentPane(panel);
        dialog.pack();
        if (!odd)
        dialog.setLocation(400, 300);
        else
        dialog.setLocation(200, 300);
        odd = !odd;
        sleep(100);
        dialog.setVisible(true);

        }
        private void sleep(int millis) {
        try {
        Thread.sleep(millis);
        }
        catch (InterruptedException ex) {
        ex.printStackTrace();;
        }

        }
        }

        ---------- END SOURCE ----------

        SUBMITTED WORKAROUND :
        We have a utility that we use to create our buttons, which gave us an opportunity to surround the actionPreformed call to be wrapped in a try / finally block that can keep a flag preventing the subsequent calls from actually doing anything. However, this doesn't feel like a very satisfying solution

          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  azvegint Alexander Zvegintsev (Inactive)
                  Reporter:
                  webbuggrp Webbug Group
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  11 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved: