Bug - Fixed AWT TabPane-related IndexOutOfBoundsException on exit

DaveK

New member
Hi mafia team.

I see this exception thrown and caught within what I assume is the UI thread, intermittently but reasonably often (one time in five-ten?) on exiting mafia:

Code:
KoLmafia v16.9
Released on April 25, 2015

Currently Running on Windows 7
Local Directory is C:\Eclipse\workspaces\KoLMafia\kolmafia
Using Java 1.7.0_65

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 5, Size: 5
	at java.util.ArrayList.rangeCheck(ArrayList.java:635)
	at java.util.ArrayList.get(ArrayList.java:411)
	at javax.swing.JTabbedPane.getTabComponentAt(JTabbedPane.java:2395)
	at javax.swing.plaf.basic.BasicTabbedPaneUI.calculateTabWidth(BasicTabbedPaneUI.java:1750)
	at tab.CloseTabPaneUI.calculateTabWidth(CloseTabPaneUI.java:270)
	at tab.CloseTabPaneUI$TabbedPaneScrollLayout.calculateTabRects(CloseTabPaneUI.java:1322)
	at javax.swing.plaf.basic.BasicTabbedPaneUI$TabbedPaneLayout.calculateLayoutInfo(BasicTabbedPaneUI.java:2512)
	at tab.CloseTabPaneUI$TabbedPaneScrollLayout.layoutContainer(CloseTabPaneUI.java:1150)
	at java.awt.Container.layout(Container.java:1503)
	at java.awt.Container.doLayout(Container.java:1492)
	at java.awt.Container.validateTree(Container.java:1688)
	at java.awt.Container.validate(Container.java:1623)
	at tab.CloseTabPaneUI.ensureCurrentLayout(CloseTabPaneUI.java:806)
	at tab.CloseTabPaneUI.paint(CloseTabPaneUI.java:658)
	at javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
	at javax.swing.JComponent.paintComponent(JComponent.java:778)
	at javax.swing.JComponent.paint(JComponent.java:1054)
	at javax.swing.JComponent.paintToOffscreen(JComponent.java:5219)
	at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1529)
	at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1452)
	at javax.swing.RepaintManager.paint(RepaintManager.java:1249)
	at javax.swing.JComponent._paintImmediately(JComponent.java:5167)
	at javax.swing.JComponent.paintImmediately(JComponent.java:4978)
	at javax.swing.RepaintManager$3.run(RepaintManager.java:808)
	at javax.swing.RepaintManager$3.run(RepaintManager.java:796)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:796)
	at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:769)
	at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:718)
	at javax.swing.RepaintManager.access$1100(RepaintManager.java:62)
	at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1677)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:312)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
	at java.awt.EventQueue.access$200(EventQueue.java:103)
	at java.awt.EventQueue$3.run(EventQueue.java:694)
	at java.awt.EventQueue$3.run(EventQueue.java:692)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)

I'm running SVN HEAD in Eclipse, and this has been occurring for as long as I've been using mafia - a year or so? I'd assume it's some kind of race condition, but I know basically nothing about the structure of the code, so I'm asking if anyone else sees it, or has any debugging suggestions for tracking it down?
 

DaveK

New member
From a quick browse of the call stack at the time when that error is thrown, and reasoning from general Swing design principles, it seems to me that the most likely cause would be if something was removing a tab from a thread other than the EDT. Does that suggest anywhere to look?
 

DaveK

New member
Got it! :)

Code:
KoLmafia [Java Application]	
	net.sourceforge.kolmafia.KoLmafia at localhost:57777 (Suspended)	
		Daemon System Thread [Attach Listener] (Suspended)	
		Daemon System Thread [Signal Dispatcher] (Suspended)	
		Daemon System Thread [Finalizer] (Suspended)	
		Daemon System Thread [Reference Handler] (Suspended)	
		Daemon System Thread [Java2D Disposer] (Suspended)	
		Daemon System Thread [AWT-Windows] (Suspended)	
		Daemon Thread [Thread-2] (Suspended)	
		System Thread [AWT-Shutdown] (Suspended)	
			Object.wait(long) line: not available [native method]	
			Object.wait() line: 503	
			AWTAutoShutdown.run() line: 296 [local variables unavailable]	
			Thread.run() line: 745	
		Thread [AWT-EventQueue-0] (Suspended (breakpoint at line 635 in ArrayList))	
			ArrayList<E>.rangeCheck(int) line: 635	
			ArrayList<E>.get(int) line: 411	
			CloseTabbedPane(JTabbedPane).getTabComponentAt(int) line: 2395	
			CloseTabPaneEnhancedUI(BasicTabbedPaneUI).calculateTabWidth(int, int, FontMetrics) line: 1750	
			CloseTabPaneEnhancedUI(CloseTabPaneUI).calculateTabWidth(int, int, FontMetrics) line: 270	
			CloseTabPaneUI$TabbedPaneScrollLayout.calculateTabRects(int, int) line: 1322	
			CloseTabPaneUI$TabbedPaneScrollLayout(BasicTabbedPaneUI$TabbedPaneLayout).calculateLayoutInfo() line: 2512	
			CloseTabPaneUI$TabbedPaneScrollLayout.layoutContainer(Container) line: 1150	
			CloseTabbedPane(Container).layout() line: 1503	
			CloseTabbedPane(Container).doLayout() line: 1492	
			CloseTabbedPane(Container).validateTree() line: 1688	
			CloseTabbedPane(Container).validate() line: 1623	
			CloseTabPaneEnhancedUI(CloseTabPaneUI).ensureCurrentLayout() line: 806	
			CloseTabPaneEnhancedUI(CloseTabPaneUI).paint(Graphics, JComponent) line: 658	
			CloseTabPaneEnhancedUI(ComponentUI).update(Graphics, JComponent) line: 161	
			CloseTabbedPane(JComponent).paintComponent(Graphics) line: 778	
			CloseTabbedPane(JComponent).paint(Graphics) line: 1054	
			CloseTabbedPane(JComponent).paintToOffscreen(Graphics, int, int, int, int, int, int) line: 5219	
			RepaintManager$PaintManager.paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int) line: 1529	
			RepaintManager$PaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1452	
			RepaintManager.paint(JComponent, JComponent, Graphics, int, int, int, int) line: 1249	
			CloseTabbedPane(JComponent)._paintImmediately(int, int, int, int) line: 5167	
			CloseTabbedPane(JComponent).paintImmediately(int, int, int, int) line: 4978	
			RepaintManager$3.run() line: 808	
			RepaintManager$3.run() line: 796	
			AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]	
			ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: 76	
			RepaintManager.paintDirtyRegions(Map<Component,Rectangle>) line: 796	
			RepaintManager.paintDirtyRegions() line: 769	
			RepaintManager.prePaintDirtyRegions() line: 718	
			RepaintManager.access$1100(RepaintManager) line: 62	
			RepaintManager$ProcessingRunnable.run() line: 1677	
			InvocationEvent.dispatch() line: 312	
			EventQueue.dispatchEventImpl(AWTEvent, Object) line: 733	
			EventQueue.access$200(EventQueue, AWTEvent, Object) line: 103	
			EventQueue$3.run() line: 694	
			EventQueue$3.run() line: 692	
			AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]	
			ProtectionDomain$1.doIntersectionPrivilege(PrivilegedAction<T>, AccessControlContext, AccessControlContext) line: 76	
			EventQueue.dispatchEvent(AWTEvent) line: 703	
			EventDispatchThread.pumpOneEventForFilters(int) line: 242	
			EventDispatchThread.pumpEventsForFilter(int, Conditional, EventFilter) line: 161	
			EventDispatchThread.pumpEventsForHierarchy(int, Conditional, Component) line: 150	
			EventDispatchThread.pumpEvents(int, Conditional) line: 146	
			EventDispatchThread.pumpEvents(Conditional) line: 138	
			EventDispatchThread.run() line: 91	
		Daemon System Thread [TimerQueue] (Suspended)	
		Thread [DestroyJavaVM] (Suspended)	
		Daemon Thread [LocalRelayCombatThread] (Suspended)	
		Daemon Thread [Thread-6] (Suspended)	
		Thread [CommandQueueHandler] (Suspended)	
		Thread [LocalRelayServer] (Suspended)	
		Thread [LocalRelayAgent0] (Suspended)	
		Thread [LocalRelayAgent1] (Suspended)	
		Thread [LocalRelayAgent3] (Suspended)	
		Thread [LocalRelayAgent2] (Suspended)	
		Thread [LocalRelayAgent4] (Suspended)	
		Thread [LocalRelayAgent5] (Suspended)	
		Daemon System Thread [Keep-Alive-Timer] (Suspended)	
		Thread [pool-1-thread-60] (Suspended)	
			CloseTabbedPane(Container).remove(int) line: 1194	
			CloseTabbedPane(JTabbedPane).removeTabAt(int) line: 999	
			CloseTabbedPane(JTabbedPane).removeAll() line: 1068	
			KoLDesktop.dispose() line: 229	
			LogoutManager.prepare() line: 87	
			KoLmafia.quit() line: 1933	
			NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
			NativeMethodAccessorImpl.invoke(Object, Object[]) line: 57	
			DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
			Method.invoke(Object, Object...) line: 606	
			InvocationListener.execute() line: 70	
			InvocationListener(ThreadedListener).run() line: 239	
			RequestThread$SequencedRunnable.run() line: 418	
			Executors$RunnableAdapter<T>.call() line: 471	
			FutureTask<V>.run() line: 262	
			ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) line: 1145	
			ThreadPoolExecutor$Worker.run() line: 615	
			Thread.run() line: 745	
	C:\Program Files (x86)\Java\jdk1.7.0_65\bin\javaw.exe (12 Jun 2015 23:58:28)
So yeah, basically what I guessed: if KoLDesktop.dispose() is going to remove tabs, either it should only be called from the event thread, or it should offload the work of removing tabs into a job on the event thread.

I still have this crash paused in Eclipse, and I'll hang on to it for a while in case you'd like any more info, but I'm going to want to shut it down and restart sometime before rollover today, so if there's anything you want from it, try and get back to me in the next ten or so hours!

Hope this helped.
 
Last edited:

DaveK

New member
How do you exit?
As I suspect you suspect, I generally use the Log Out menu option.

I used a few printlns to see what was happening when, and then by patching mafia like so....
Code:
### Eclipse Workspace Patch 1.0
#P kolmafia
Index: lib/tab/CloseTabPaneUI.java
===================================================================
--- lib/tab/CloseTabPaneUI.java	(revision 15930)
+++ lib/tab/CloseTabPaneUI.java	(working copy)
@@ -1319,6 +1319,15 @@
 					totalHeight += CloseTabPaneUI.this.maxTabHeight;
 					rect.x = x;
 				}
+				java.lang.Thread.yield();
+				try
+				{
+					java.lang.Thread.sleep((i != 0) ? 13 : 47);
+				}
+				catch ( InterruptedException e )
+				{
+					// Don't care, just trying to interleave threads
+				}
 				rect.width = CloseTabPaneUI.this.calculateTabWidth( tabPlacement, i, metrics );
 				totalWidth = rect.x + rect.width;
 				CloseTabPaneUI.this.maxTabWidth = Math.max( CloseTabPaneUI.this.maxTabWidth, rect.width );
Index: lib/tab/CloseTabbedPane.java
===================================================================
--- lib/tab/CloseTabbedPane.java	(revision 15930)
+++ lib/tab/CloseTabbedPane.java	(working copy)
@@ -374,4 +374,32 @@
 	{
 		return false;
 	}
+
+	/**
+     * Removes all the tabs and their corresponding components
+     * from the <code>tabbedpane</code>.
+     *
+     * @see #addTab
+     * @see #removeTabAt
+     */
+    public void removeAll() {
+        setSelectedIndex(-1);
+
+        int tabCount = getTabCount();
+        // We invoke removeTabAt for each tab, otherwise we may end up
+        // removing Components added by the UI.
+        while (tabCount-- > 0) {
+        	java.lang.Thread.yield();
+            removeTabAt(tabCount);
+            try
+			{
+				java.lang.Thread.sleep(3);
+			}
+			catch ( InterruptedException e )
+			{
+				// Don't care, just giving things a chance to run in order to
+				// maximize our chance of triggering the race condition.
+			}
+        }
+    }
 }
...I was able to have the race condition hit virtually every time. I never saw it happen when I tried exiting by clicking the main frame's close gadget, but almost always when using the menu item to log out or exit.

After your patch at r.15938 the race was fixed, of course.

Thanks for your help, we can close this one now! :)
 
Top