JDK-6480024 : Stack overflow on mouse wheel moved
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 5.0,6
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: solaris_9,windows_xp
  • CPU: x86,sparc
  • Submitted: 2006-10-10
  • Updated: 2011-02-16
  • Resolved: 2007-08-22
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u10Fixed 7 b19Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.6.0-beta2"
Java(TM) SE Runtime Environment (build 1.6.0-beta2-b86)
Java HotSpot(TM) Client VM (build 1.6.0-beta2-b86, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Versione 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
If you add a MouseWheelListener to a JFrame, and a MouseListener (or MouseMoveListener) to any of its children, and then run the application and move the mouse wheel over the frame (it is not necessary to move it while over the specific child) the window freezes for a while and then fires a StackOverflowError.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. extend a class from JFrame
2. in the constructor, use addMouseWheelListener to add whatever listener you like (even an empty one)
3. create a control of any kind (a JPanel, for example) and add it to the frame using add
4. use the addMouseListener to the created control to add a listener of any kind (once more, it works even with an empty one)
5. run the application and move the mouse wheel over the frame

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The application handles events the right way, executing the mouseWheelMoved implementation of the frame listener when you move the mouse wheel.
ACTUAL -
The mouseWheelMoved implementation of the frame listener is never called, the application goes in an infinite loop and after a while a StackOverflowError is fired.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
	at java.awt.Container.getMouseEventTargetImpl(Unknown Source)
	at java.awt.Container.getMouseEventTarget(Unknown Source)
	at java.awt.Container.getMouseEventTarget(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.dispatchMouseWheelToAncestor(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.dispatchMouseWheelToAncestor(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.dispatchMouseWheelToAncestor(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.dispatchMouseWheelToAncestor(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)
(the same sequence of calls again and again)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

public class TestFrame extends JFrame
{
	public TestFrame()
	{
		initialize();
	}

	private void initialize()
	{
		this.setSize(200, 200);
		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		this.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e)
			{
			}
		});
		
		JPanel outputBox = new JPanel();
		outputBox.addMouseListener(new MouseInputAdapter() {});
				
		getContentPane().setLayout(new BorderLayout());
		getContentPane().add(outputBox, BorderLayout.CENTER);
		
		this.show();
	}
	public static void main(String[] args) 
	{
		TestFrame tf = new TestFrame();
	}
}

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

Comments
EVALUATION Component.dispatchMouseWheelToAncestor(MouseWheelEvent e) does following check: if (!(anc instanceof Window)) { anc = anc.getParent(); } else { break; } But if even the current value of arc variable is not Window, the next would Window and we enclose the circle. Instead the statement may check if (!(anc.getParent() instanceof Window)) {
05-07-2007

EVALUATION This is the repeatable dump. at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4249) at java.awt.LightweightDispatcher.processMouseEvent(Container.java:3942) at java.awt.LightweightDispatcher.dispatchEvent(Container.java:3843) at java.awt.Container.dispatchEventImpl(Container.java:2100) at java.awt.Window.dispatchEventImpl(Window.java:2442) at java.awt.Component.dispatchMouseWheelToAncestor(Component.java:4546) at java.awt.Component.dispatchEventImpl(Component.java:4287) at java.awt.Container.dispatchEventImpl(Container.java:2114) at java.awt.Component.dispatchEvent(Component.java:4231) Starting from the last entry, 1) JButton is getting the event with dispatchEvent() 2) As the JButton(or any other LW component inside any Window) is the child of Container, then invoking dispatchEventImpl() addresses the Container.dispatchEvent() 3) Now we see super.dispatchEventImpl(e); That means that we go back to the Component. 4) Now we come to if (id == MouseEvent.MOUSE_WHEEL && (!eventTypeEnabled(id)) && (peer != null && !peer.handlesWheelScrolling()) && (dispatchMouseWheelToAncestor((MouseWheelEvent)e))) { return; } 5) Looking on dispatchMouseWheelToAncestor((MouseWheelEvent)e) there is a anc.dispatchEventImpl(newMWE); invocation. Where anc - is the nearest ancestor accepting mouse wheel events. 6) Here anc variable is the Window and it just invoke super.dispatchEventImpl(e); 7) So we returned to the Container but now it has the LightweightDispatcher in it. if ((dispatcher != null) && dispatcher.dispatchEvent(e)) { 8) dispatchEvent() walks through the if-s and choose: ret = processMouseEvent(me); 9) That method finds the component under the mouse cursor private boolean processMouseEvent(MouseEvent e) { int id = e.getID(); Component mouseOver = // sensitive to mouse events nativeContainer.getMouseEventTarget(e.getX(), e.getY(), Container.INCLUDE_SELF); 10) And then case MouseEvent.MOUSE_WHEEL: [skip] retargetMouseEvent(mouseOver, id, e); Where mouseOver holds the JButton component. This encloses the loop - the same (actually cloned) event comes to the JButton again. The event transfering machinery works from the top to bottom in the component hierarchy: events are transfered from the toplevel(window, frame, dialog) to all their children, including LW components. The idea of the fix is to introduce optionality to the 10-th step: if the component under the mouse cursor is unable to handle the event (enableEvent() for that event type returns false) or not interested in them we shouldn't post the event to that component. Judging from the above we could introduce optionality to the 10-th step: if the component under the mouse cursor is unable to handle the event (enableEvent() for that event type returns false) or not interested in them we shouldn't post the event to that component. This obviously needs extensive testing.
21-03-2007

SUGGESTED FIX http://sa.sfbay.sun.com/projects/awt_data/7/6480024/ +++ Component.java 2007-07-23 17:33:16.000000000 +0400 @@ -4560,11 +4560,16 @@ e.isPopupTrigger(), e.getScrollType(), e.getScrollAmount(), e.getWheelRotation()); ((AWTEvent)e).copyPrivateDataInto(newMWE); - anc.dispatchEventImpl(newMWE); + // When dispatching a wheel event to + // ancestor, there is no need trying to find descendant + // lightweights to dispatch event to. + // If we dispatch the event to toplevel ancestor, + // this could encolse the loop: 6480024. + anc.dispatchEventToSelf(newMWE); } } return true; }
21-03-2007

WORK AROUND Add mouse wheel listener to the lightweight container: outputBox.addMouseWheelListener(new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e){} });
19-03-2007

EVALUATION There are two GUI instances to represent the issue: a JPanel and a Frame. Once Panel get a WheelEvent then it tries to share it with it's ancestor. This machinery doesn't work very well on linux (see 4616935 for more info). But if ancestor has LightweightDispatcher installed then it retarget the event back to Panel. This encloses the loop. I'd say that issue should be addressed to LightweightDispatcher. It should decide if this event has already processed by some lightweight component inside assigned Container and consume the wheel event. Container.retargetMouseEvent() obtain Frame as source and Panel as target.
17-10-2006

EVALUATION Infinite loop is caused by LightweightDispatcher, Component.dispatchEventImpl and Component.dispatchMouseWheelToAncestor methods. We should probably dispatch mouse wheel events more carefully in LightweightDispatcher, or not dispatch them to ancestor component. Adding MouseWheelListener to the component is significant as it leads to MouseWheelEvents to be enabled to the component. This check is then performed in Component.dispatchEventImpl and leads to the call to dispatchMouseWheelToAncestor.
12-10-2006