# HG changeset patch # User mbalao # Date 1541770459 10800 # Fri Nov 09 10:34:19 2018 -0300 # Node ID 661ea858f31809fe8bb1f1f4831313556c70dc48 # Parent 04d7e790aa2ea222d6a9f1e89d4a4488b57064ca 8204142: AWT hang occurs when sequenced events arrive out of sequence in multiple AppContexts Summary: Improvements on the synchronization of SequencedEvent events from different AppContexts Reviewed-by: serb diff --git a/src/java.desktop/share/classes/java/awt/SequencedEvent.java b/src/java.desktop/share/classes/java/awt/SequencedEvent.java --- a/src/java.desktop/share/classes/java/awt/SequencedEvent.java +++ b/src/java.desktop/share/classes/java/awt/SequencedEvent.java @@ -27,6 +27,7 @@ import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Iterator; import java.util.LinkedList; import sun.awt.AWTAccessor; import sun.awt.AppContext; @@ -56,6 +57,7 @@ private final AWTEvent nested; private AppContext appContext; private boolean disposed; + private final LinkedList pendingEvents = new LinkedList<>(); private static boolean fxAppThreadIsDispatchThread; private Thread fxCheckSequenceThread; @@ -81,6 +83,35 @@ }); } + private static final class SequencedEventsFilter implements EventFilter { + private final SequencedEvent currentSequencedEvent; + private SequencedEventsFilter(SequencedEvent currentSequencedEvent) { + this.currentSequencedEvent = currentSequencedEvent; + } + @Override + public FilterAction acceptEvent(AWTEvent ev) { + if (ev.getID() == ID) { + // Move forward dispatching only if the event is previous + // in SequencedEvent.list. Otherwise, hold it for reposting later. + synchronized (SequencedEvent.class) { + Iterator it = list.iterator(); + while (it.hasNext()) { + SequencedEvent iev = it.next(); + if (iev.equals(currentSequencedEvent)) { + break; + } else if (iev.equals(ev)) { + return FilterAction.ACCEPT; + } + } + } + } else if (ev.getID() == SentEvent.ID) { + return FilterAction.ACCEPT; + } + currentSequencedEvent.pendingEvents.add(ev); + return FilterAction.REJECT; + } + } + /** * Constructs a new SequencedEvent which will dispatch the specified * nested event. @@ -135,7 +166,8 @@ if (Thread.currentThread() instanceof EventDispatchThread) { EventDispatchThread edt = (EventDispatchThread) Thread.currentThread(); - edt.pumpEvents(ID, () -> !SequencedEvent.this.isFirstOrDisposed()); + edt.pumpEventsForFilter(() -> !SequencedEvent.this.isFirstOrDisposed(), + new SequencedEventsFilter(this)); } else { if (fxAppThreadIsDispatchThread) { fxCheckSequenceThread.start(); @@ -239,10 +271,6 @@ } disposed = true; } - // Wake myself up - if (appContext != null) { - SunToolkit.postEvent(appContext, new SentEvent()); - } SequencedEvent next = null; @@ -263,5 +291,9 @@ if (next != null && next.appContext != null) { SunToolkit.postEvent(next.appContext, new SentEvent()); } + + for(AWTEvent e : pendingEvents) { + SunToolkit.postEvent(appContext, e); + } } } diff --git a/test/jdk/java/awt/event/SequencedEvent/MultipleContextsFunctionalTest.java b/test/jdk/java/awt/event/SequencedEvent/MultipleContextsFunctionalTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/awt/event/SequencedEvent/MultipleContextsFunctionalTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8204142 + * @key headful + * @summary Deadlock when queueing SequencedEvent of different AppContexts + * @author Laurent Bourges + * @modules java.desktop/sun.awt + * @run main/othervm/timeout=30 MultipleContextsFunctionalTest + */ + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +public final class MultipleContextsFunctionalTest { + + private static final long serialVersionUID = 1L; + + private static final int NUM_WINDOW = 2; + private static final int INTERVAL = 50; + private static final int MAX_TIME = 10000; // 10s + private static final int TOLERANCE = 10000;// 10s + private static final int CHECK_LAPSE = 100; + private static final int MAX_COUNT = MAX_TIME / INTERVAL; + private static final int EXPECTED = MAX_COUNT * NUM_WINDOW; + private static final List WINDOWS = new ArrayList(); + + public static void main(String[] args) { + for (int i = 0; i < NUM_WINDOW; i++) { + createWin(i); + } + + int total = 0; + int waitingTime = MAX_TIME + TOLERANCE; + while (waitingTime > 0 && total != EXPECTED) { + try { + Thread.sleep(CHECK_LAPSE); + } catch (InterruptedException e) { + e.printStackTrace(); + } + waitingTime -= CHECK_LAPSE; + + total = 0; + for (TestWindow window : WINDOWS) { + total += window.getCounter(); + } + } + + // Failure if AWT hanging: assert + System.out.println("Total [" + total + "] - Expected [" + EXPECTED + "]"); + if (total == EXPECTED) { + System.out.println("Test PASSED"); + return; + } + System.out.println("Test FAILED"); + Runtime.getRuntime().halt(-1); + } + + private static void createWin(int tgNum) { + new Thread(new ThreadGroup("TG " + tgNum), + new Runnable() { + @Override + public void run() { + sun.awt.SunToolkit.createNewAppContext(); + + final AtomicReference ref = + new AtomicReference(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + final TestWindow window = new TestWindow(tgNum); + window.setVisible(true); + ref.set(window); + WINDOWS.add(window); + } + }); + + // Wait for window to show + TestWindow window = ref.get(); + while (window == null) { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + window = ref.get(); + } + window.enableTimer(true); + } + }).start(); + } + + private static final class TestWindow extends JFrame implements ActionListener { + + private final JButton btn; + private int counter = 0; + private final Timer t; + + TestWindow(final int num) { + super("Test Window [" + num + "]"); + setMinimumSize(new Dimension(300, 200)); + setLocation(100 + 400 * (num - 1), 100); + + setLayout(new BorderLayout()); + JLabel textBlock = new JLabel("Lorem ipsum dolor sit amet..."); + add(textBlock); + + btn = new JButton("TEST"); + add(btn, BorderLayout.SOUTH); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + pack(); + + t = new Timer(INTERVAL, this); + t.setRepeats(false); + } + + @Override + public void actionPerformed(ActionEvent e) { + this.toFront(); + btn.setText("TEST " + (++counter)); + this.toBack(); + if (counter < MAX_COUNT) { + enableTimer(true); + } else { + dispose(); + } + } + + void enableTimer(boolean enable) { + if (enable) { + t.start(); + } else { + t.stop(); + } + } + + int getCounter() { + return counter; + } + } +} diff --git a/test/jdk/java/awt/event/SequencedEvent/MultipleContextsUnitTest.java b/test/jdk/java/awt/event/SequencedEvent/MultipleContextsUnitTest.java new file mode 100644 --- /dev/null +++ b/test/jdk/java/awt/event/SequencedEvent/MultipleContextsUnitTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.AWTEvent; +import java.awt.event.InvocationEvent; +import java.lang.reflect.Constructor; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +import sun.awt.AppContext; +import sun.awt.SunToolkit; + +/** + * @test + * @bug 8204142 + * @author Sergey Bylokhov + * @key headful + * @modules java.desktop/sun.awt + * @run main/othervm/timeout=30 MultipleContextsUnitTest + */ +public final class MultipleContextsUnitTest { + + private static final int COUNT = 20; + private static final AppContext[] apps = new AppContext[COUNT]; + private static final CountDownLatch go = new CountDownLatch(1); + private static final CountDownLatch end = new CountDownLatch(COUNT); + + private static volatile int createSENumber = 0; + private static volatile int dispatchSENumber = 0; + + public static void main(final String[] args) throws Exception { + for (int i = 0; i < COUNT; i++) { + Thread t = testThread(i); + t.start(); + t.join(); + } + + for (AppContext app : apps) { + SunToolkit.postEvent(app, new InvocationEvent(new Object(), () -> { + try { + go.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + })); + } + + // eventOne - created first, but posted last + AWTEvent eventOne = getSequencedEvent(); + { + // eventTwo and eventThree - posted in the reverse order + AppContext app = apps[1]; + AWTEvent eventTwo = getSequencedEvent(); + AWTEvent eventThree = getSequencedEvent(); + SunToolkit.postEvent(app, eventThree); + SunToolkit.postEvent(app, eventTwo); + SunToolkit.postEvent(app, new InvocationEvent(new Object(), () -> { + System.err.println(AppContext.getAppContext()); + end.countDown(); + })); + } + + for (int i = 2; i < apps.length; i++) { + // eventTwo and eventThree - posted in the correct order + AppContext app = apps[i]; + + AWTEvent eventTwo = getSequencedEvent(); + SunToolkit.postEvent(app, eventTwo); + + AtomicReference called1 = new AtomicReference(false); + AtomicReference called2 = new AtomicReference(false); + int num1 = createSENumber; + SunToolkit.postEvent(app, new InvocationEvent(new Object(), () -> { + if (dispatchSENumber < num1) { + throw new RuntimeException("Dispatched too early"); + } + called1.set(true); + if (called2.get()) { + throw new RuntimeException("Second event is called before first"); + } + })); + AWTEvent eventThree = getSequencedEvent(); + SunToolkit.postEvent(app, eventThree); + int num2 = createSENumber; + SunToolkit.postEvent(app, new InvocationEvent(new Object(), () -> { + if (dispatchSENumber < num2) { + throw new RuntimeException("Dispatched too early"); + } + called2.set(true); + if (!called1.get()) { + throw new RuntimeException("First event is not called before second"); + } + System.err.println(AppContext.getAppContext()); + end.countDown(); + })); + } + + + + // eventOne should flush all EDT + SunToolkit.postEvent(apps[0], eventOne); + SunToolkit.postEvent(apps[0], new InvocationEvent(new Object(), () -> { + System.err.println(AppContext.getAppContext()); + end.countDown(); + })); + + go.countDown(); + + System.err.println("Start to wait"); + end.await(); + System.err.println("End to wait"); + } + + private static Thread testThread(int index) { + final ThreadGroup group = new ThreadGroup("TG " + index); + return new Thread(group, () -> { + apps[index] = SunToolkit.createNewAppContext(); + }); + } + + private static AWTEvent getSequencedEvent() + { + int num = createSENumber++; + + InvocationEvent wrapMe = new InvocationEvent(new Object(), () -> { + if (num != dispatchSENumber++) { + System.err.println("num: " + num); + System.err.println("dispatchSENumber: " + dispatchSENumber); + throw new RuntimeException("Wrong order"); + } + }); + + try { + /* + * SequencedEvent is a package private class, which cannot be instantiated + * by importing. So use reflection to create an instance. + */ + Class seqClass = (Class) Class.forName("java.awt.SequencedEvent"); + Constructor + seqConst = seqClass.getConstructor(AWTEvent.class); + seqConst.setAccessible(true); + return seqConst.newInstance(wrapMe); + } catch (Throwable err) { + throw new RuntimeException("Unable to instantiate SequencedEvent",err); + } + } +} diff --git a/test/jdk/java/awt/event/SequencedEvent/SequencedEventTest.java b/test/jdk/java/awt/event/SequencedEvent/SequencedEventTest.java deleted file mode 100644 --- a/test/jdk/java/awt/event/SequencedEvent/SequencedEventTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @bug 8152974 - * @key headful - * @modules java.desktop/sun.awt - * @summary AWT hang occurs when sequenced events arrive out of sequence - * @run main SequencedEventTest - */ -import sun.awt.AppContext; -import sun.awt.SunToolkit; - -import java.awt.Robot; -import java.awt.Point; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.AWTEvent; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.InputEvent; -import java.lang.reflect.Constructor; -import java.util.concurrent.CountDownLatch; - -import javax.swing.JFrame; -import javax.swing.JButton; -import javax.swing.SwingUtilities; -import javax.swing.JTextArea; - -public class SequencedEventTest extends JFrame implements ActionListener { - private JButton spamMeButton; - private static Robot robot; - private static SequencedEventTest window; - private static AppContext context; - - public static void main(String[] args) throws Exception { - SwingUtilities.invokeAndWait(() -> { - window = new SequencedEventTest(); - window.setVisible(true); - }); - - robot = new Robot(); - robot.waitForIdle(); - - Point pt = window.spamMeButton.getLocationOnScreen(); - Dimension d = window.spamMeButton.getSize(); - - robot.mouseMove(pt.x + d.width / 2, pt.y + d.height / 2); - robot.waitForIdle(); - robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); - robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); - /* - *Cannot have robot.waitForIdle() here since it will block the test forever, - * in the case of failure and the test will timeout. - */ - - try { - /* - * Wait for 2 seconds, and then see if all the sequenced events are dispatched. - */ - Thread.sleep(2000); - AWTEvent ev = Toolkit.getDefaultToolkit().getSystemEventQueue(). - peekEvent(java.awt.event.FocusEvent.FOCUS_LAST + 1); - - if (ev != null) - throw new RuntimeException("Test case failed, since all the sequenced events" + - "are not flushed!" + ev); - } catch (InterruptedException e) { - throw new RuntimeException("Test case failed." + e.getMessage()); - } - - /* - * In the case of failure, the cleanup job cannot be executed, since it - * will block the test. - */ - System.out.println("Test case succeeded."); - context.dispose(); - SwingUtilities.invokeAndWait(() -> window.dispose()); - } - - public SequencedEventTest() { - super("Test Window"); - - setLayout(new FlowLayout()); - JTextArea textBlock = new JTextArea("Lorem ipsum dolor sit amet..."); - add(textBlock); - - spamMeButton = new JButton("Press me!"); - spamMeButton.addActionListener(this); - add(spamMeButton); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - pack(); - } - - @Override - public void actionPerformed(ActionEvent e) { - if(e.getSource() == spamMeButton) { - AWTEvent eventOne = getSequencedEvent(); - AWTEvent eventFour = getSequencedEvent(); - ThreadGroup tg = new ThreadGroup("TestThreadGroup" ); - CountDownLatch latch = new CountDownLatch(1); - Thread t = new Thread(tg, () -> { - context = SunToolkit.createNewAppContext(); - AWTEvent eventTwo = getSequencedEvent(); - AWTEvent eventThree = getSequencedEvent(); - - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(eventThree); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new ActionEvent(this, 0, null)); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new ActionEvent(this, 1, null)); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(eventTwo); - - latch.countDown(); - }); - - t.start(); - try { - latch.await(); - }catch (InterruptedException ex) { - throw new RuntimeException("Test case failed."); - } - - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(eventFour); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new ActionEvent(this, 2, null)); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(new ActionEvent(this, 3, null)); - Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(eventOne); - - try { - t.join(); - } catch (InterruptedException ex) { - throw new RuntimeException("Test case failed."); - } - } - } - - private AWTEvent getSequencedEvent() - { - AWTEvent wrapMe = new AWTEvent(this, AWTEvent.RESERVED_ID_MAX) {}; - - try { - /* - * SequencedEvent is a package private class, which cannot be instantiated - * by importing. So use reflection to create an instance. - */ - Class seqClass = (Class) Class.forName("java.awt.SequencedEvent"); - Constructor seqConst = seqClass.getConstructor(AWTEvent.class); - seqConst.setAccessible(true); - return seqConst.newInstance(wrapMe); - } catch (Throwable err) { - throw new RuntimeException("Unable to instantiate SequencedEvent",err); - } - } -}