Community
Participate
Working Groups
Build ID: I20070921-0919 The new implementation of the BusyIndicator introduced in 3.4 M2 causes several exceptions, which, however, are swallowed in an empty catch block. You will catch these exceptions when Exception breakpoints for NPE and others are enabled. The problem obviously occurs when setBusy is called frequently. I have a search text field in my form in which every ModifyEvent triggers a new search, i.e. every character typed in will cause an update an therefore setBusy will be called in quick succession. If you have problems to reproduce the bug, I could try to cut my Forms code down to a small bug snippet. The exceptions thrown are as follows: org.eclipse.swt.SWTError: No more handles at org.eclipse.swt.SWT.error(SWT.java:3717) at org.eclipse.swt.SWT.error(SWT.java:3609) at org.eclipse.swt.SWT.error(SWT.java:3580) at org.eclipse.swt.graphics.Image.createGdipImage(Image.java:870) at org.eclipse.swt.graphics.GC.drawImage(GC.java:897) at org.eclipse.swt.graphics.GC.drawImage(GC.java:891) at org.eclipse.ui.internal.forms.widgets.BusyIndicator$2.run(BusyIndicator.java:123) java.lang.NullPointerException at org.eclipse.swt.graphics.Image.dispose(Image.java:1038) at org.eclipse.ui.internal.forms.widgets.BusyIndicator.clearImages(BusyIndicator.java:303) at org.eclipse.ui.internal.forms.widgets.BusyIndicator.access$0(BusyIndicator.java:299) at org.eclipse.ui.internal.forms.widgets.BusyIndicator$2.run(BusyIndicator.java:188) java.lang.NullPointerException at org.eclipse.swt.graphics.Image.createGdipImage(Image.java:865) at org.eclipse.swt.graphics.GC.drawImage(GC.java:897) at org.eclipse.swt.graphics.GC.drawImage(GC.java:891) at org.eclipse.ui.internal.forms.widgets.BusyIndicator$2.run(BusyIndicator.java:123) Exception in thread "Thread-72" java.lang.OutOfMemoryError: Java heap space at org.eclipse.swt.graphics.Image.getImageData(Image.java:1440) at org.eclipse.ui.internal.forms.widgets.BusyIndicator$2.run(BusyIndicator.java:122) Line 176 in org.eclipse.ui.internal.forms.widgets.BusyIndicator protected synchronized void createBusyThread() { ... busyThread = new Thread() { private Image timage; public void run() { try { ... } catch (Exception e) { <<< empty catch block swallows these exceptions } finally { ... } } ... }
See bug 179812 for clues - it appears that changes caused by it introduced this new problem.
This bug is caused by a concurrency problem. The problem is that the access to the 'imageCache' is not properly synchronized. 'imageCache' is accessed by the busy thread. When a new busy thread is started, the old one may still be running and disposing the images in the cache. That's the problem. The last commit introduced method clearImages(). Before these changes, the images were not disposed between calls to setBusy(...). You can reproduce the bug as follows: - Add the extension markup below to some plugin and paste the code below. - In Eclipse, add an exception breakpoint for NPE. - In the text field of the form, enter quickly some characters or hold down a key; after a short while, an execption should be thrown. ------------------------------------------------------------------------------ The following is one of several sequences that cause an exception: - setBusy(true) is called - setBusy(false) is called - the current 'busyThread' leaves its while loop and enters its 'finally' block in order to call clearImages() - while the the old 'busyThead' is still executing its 'finally' block, setBusy(true) is called again - a new busy thread is created (the 'old' busy thread may still be running at this time) - the new busy thread enters its while loop and calls getImage(...); it receives an image from the cache - now the 'old' busy thread, which is still executing its 'finally' block, disposes the image which the new busy thread just fetched from the cache - finally, the new busy thread tries to draw the image from the cache on screen and causes an exception ------------------------------------------------------------------------------ <extension point="org.eclipse.ui.views"> <view name="BusyIndicatorBug" class="bugs.BusyIndicatorBug" id="bugs.BusyIndicatorBug"/> </extension> ------------------------------------------------------------------------------ package bugs; import java.util.Random; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.part.ViewPart; public class BusyIndicatorBugView extends ViewPart { private Form form; private Text text; public BusyIndicatorBugView() { } @Override public void createPartControl(Composite parent) { FormToolkit toolkit = new FormToolkit(Display.getCurrent()); form = toolkit.createForm(parent); form.setText("Header"); toolkit.decorateFormHeading(form); Composite body = form.getBody(); toolkit.paintBordersFor(body); body.setLayout(new GridLayout()); text = toolkit.createText(body, "Enter text here"); text.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { startBackgroundJob(); } }); text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); } @Override public void setFocus() { text.setFocus(); } private void startBackgroundJob() { Job job = new Job("Bug Job") { @Override protected IStatus run(IProgressMonitor monitor) { setBusy(true); doSomeWork(); setBusy(false); return Status.OK_STATUS; } private void doSomeWork() { try { Thread.sleep(20 + new Random().nextInt(200)); } catch (InterruptedException e) { } } private void setBusy(final boolean b) { Display.getDefault().syncExec(new Runnable() { public void run() { form.setBusy(b); } }); } }; job.schedule(); } }
Created attachment 82347 [details] Patch against HEAD to fix this bug This patch fixes all problems described. The following changes were made: - added missing synchronization for access to the image cache - refactored the anonymous busy thread class into an internal class - moved field 'stop' to BusyThread class The last mentioned change is necessary because otherwise it may happen that a thread is not actually stopped, namely, if in the meantime a new busy thread is created (and thus stop is reset to false). In this case, two busy threads draw at the same time, causing some strange flickering. The patch has been thoroughly tested for some time. No further problems were encountered.
Providing patches for your own bugs! I like the way you operate. Thanks Stefan, I'll test this out.
Thanks Stefan! Adam can you review this patch?
Created attachment 82441 [details] Patch against HEAD to fix this bug (slightly updated) I had a quick glance at the code again. Before committing these changes, perhaps some 'final' keywords should be removed. These partly existed before because of the annonymous class, and partly resulted from the (automatic) refactoring. None of them is still necessary. Some 'private' keywords can also be removed. Moreover, I thought about moving the lines of code before 'new BusyThread(...)' directly into the BusyThread constructor; however, this does not work, since the code must run in the display thread. So these lines should not be moved.
Patch in comment 6 tested very well. Thanks again Stefan! +1
I'll commit this next week.
Fixed in HEAD.