Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 353686 - "Invalid thread access" exception when changing bound model from non-UI thread
Summary: "Invalid thread access" exception when changing bound model from non-UI thread
Status: CLOSED FIXED
Alias: None
Product: z_Archived
Classification: Eclipse Foundation
Component: Sapphire (show other bugs)
Version: unspecified   Edit
Hardware: All All
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: Konstantin Komissarchik CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-08-02 21:01 EDT by Alex Smirnoff CLA
Modified: 2021-11-19 09:21 EST (History)
2 users (show)

See Also:


Attachments
test project to reproduce the problem (13.79 KB, application/x-zip-compressed)
2011-08-02 21:05 EDT, Alex Smirnoff CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Alex Smirnoff CLA 2011-08-02 21:01:46 EDT
Build Identifier: 

Here is the exception:
org.eclipse.swt.SWTException: Invalid thread access
	at org.eclipse.swt.SWT.error(SWT.java:4282)
	at org.eclipse.swt.SWT.error(SWT.java:4197)
	at org.eclipse.swt.SWT.error(SWT.java:4168)
	at org.eclipse.swt.widgets.Widget.error(Widget.java:468)
	at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:359)
	at org.eclipse.swt.widgets.Label.setImage(Label.java:345)
	at org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator.refreshImageAndCursor(PropertyEditorAssistDecorator.java:495)
	at org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator.refresh(PropertyEditorAssistDecorator.java:445)
	at org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator.access$2(PropertyEditorAssistDecorator.java:348)
	at org.eclipse.sapphire.ui.assist.internal.PropertyEditorAssistDecorator$1.handlePropertyChangedEvent(PropertyEditorAssistDecorator.java:164)
	at org.eclipse.sapphire.modeling.ModelElement.notifyPropertyChangeListeners(ModelElement.java:1297)
	at org.eclipse.sapphire.modeling.ModelElement.notifyPropertyChangeListeners(ModelElement.java:1260)
	at sapphire.bug.test.internal.SimpleModel.refreshProperty(SimpleModel.java:93)
	at org.eclipse.sapphire.modeling.ModelElement.refresh(ModelElement.java:455)
	at org.eclipse.sapphire.modeling.ModelElement.refresh(ModelElement.java:448)
	at sapphire.bug.test.internal.SimpleModel.setName(SimpleModel.java:60)
	at sapphire.bug.test.NonDisplayThreadAccessTest.test(NonDisplayThreadAccessTest.java:25)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.pde.internal.junit.runtime.RemotePluginTestRunner.main(RemotePluginTestRunner.java:62)
	at org.eclipse.pde.internal.junit.runtime.PlatformUITestHarness$1.run(PlatformUITestHarness.java:47)
	at java.lang.Thread.run(Unknown Source)

Reproducible: Always

Steps to Reproduce:
1. Run the test in UI thread: no exception
2. Run the test in non UI thread: you'll see above exception
Comment 1 Alex Smirnoff CLA 2011-08-02 21:05:15 EDT
Created attachment 200766 [details]
test project to reproduce the problem
Comment 2 Konstantin Komissarchik CLA 2011-08-03 15:35:34 EDT
I could not figure out how to run test case to produce the failure. I tried importing the project and doing Run As -> Junit Plugin Test on the test class, but that test case passed without any fix.

In any case, the stack trace is all that I needed and I added asyncExec indirection at PropertyEditorAssistDecorator$1.handlePropertyChangedEvent.

Please verify.
Comment 3 Alex Smirnoff CLA 2011-08-03 18:05:18 EDT
Sorry, the test does not fail, just show the exceptions when changing the model. I am not able to intercept the exception it was intercepted in ModelElement.notifyPropertyChangeListeners().

To reproduce you need to uncheck "Run in UI thread" in Test tab in launch configuration. So test will run outside of UI thread. I think by default when you run "Run As -> Junit Plugin Test" it will be checked.

I have validated it again, now the same exception is surfacing from another end: 


org.eclipse.swt.SWTException: Invalid thread access
	at org.eclipse.swt.SWT.error(SWT.java:4282)
	at org.eclipse.swt.SWT.error(SWT.java:4197)
	at org.eclipse.swt.SWT.error(SWT.java:4168)
	at org.eclipse.swt.widgets.Widget.error(Widget.java:468)
	at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:359)
	at org.eclipse.swt.widgets.Control.setEnabled(Control.java:3266)
	at org.eclipse.sapphire.ui.renderers.swt.PropertyEditorRenderer.handlePropertyChangedEvent(PropertyEditorRenderer.java:485)
	at org.eclipse.sapphire.ui.renderers.swt.PropertyEditorRenderer$1.handlePropertyChangedEvent(PropertyEditorRenderer.java:198)
	at org.eclipse.sapphire.modeling.ModelElement.notifyPropertyChangeListeners(ModelElement.java:1299)
	at org.eclipse.sapphire.modeling.ModelElement.notifyPropertyChangeListeners(ModelElement.java:1262)
	at sapphire.bug.test.internal.SimpleModel.refreshProperty(SimpleModel.java:93)
	at org.eclipse.sapphire.modeling.ModelElement.refresh(ModelElement.java:457)
	at org.eclipse.sapphire.modeling.ModelElement.refresh(ModelElement.java:450)
	at sapphire.bug.test.internal.SimpleModel.setName(SimpleModel.java:60)
	at sapphire.bug.test.NonDisplayThreadAccessTest.test(NonDisplayThreadAccessTest.java:28)
Comment 4 Alex Smirnoff CLA 2011-08-03 18:26:35 EDT
Within the same line of thinking, I think, code in ModelElement.notifyPropertyChangeListeners should not wrap listeners methods execution with try/catch, instead bubble runtime exceptions up to clients directly and live it up to them to decide what to do. Clients may supply their own listeners, so it should be up to them how to handle runtime exceptions.

And it will help to fail this Junit test :-)
Comment 5 Konstantin Komissarchik CLA 2011-08-03 18:31:18 EDT
> Within the same line of thinking, I think, code in
> ModelElement.notifyPropertyChangeListeners should not wrap listeners methods
> execution with try/catch, instead bubble runtime exceptions up to clients
> directly and live it up to them to decide what to do. Clients may supply their
> own listeners, so it should be up to them how to handle runtime exceptions.

The try/catch in notify prevents one flaky listener from causing the rest of the listeners from being invoked and potentially corrupting the model in the process. It's a stability feature as we are limiting the impact of the exception. So while it would help fail the JUnit tests, it isn't a good change.
Comment 6 Alex Smirnoff CLA 2011-08-03 18:59:12 EDT
From the other hand, it will be harder to troubleshoot the problems in listeners, especially, if it is originated in third-party code. EMF does not have runtime protection too.
Comment 7 Konstantin Komissarchik CLA 2011-08-03 19:12:02 EDT
> From the other hand, it will be harder to troubleshoot the problems in
> listeners, especially, if it is originated in third-party code.

Don't see how. At runtime, the problem ends up being logged either way. If you are in the debugger, you can still trap the exception. In fact, I would say that it is actually easier to troubleshot when impact of the exception is limited. You certainly don't get a long chain of follow-on exceptions that are caused by data structure corruption from the original exception and require investigation to determine if they are related to the original failure.

> EMF does not have runtime protection too.

I wouldn't use EMF as a model of runtime stability. Either you do everything right or it just blows up in your face. No thread safety, no guarding against misbehaving third-party code, etc.

I think that the real reason that you don't see more guards when calling out to third-party code is that it is easier on the framework writer to just let exception propagate and then blame third party code for any subsequent issues in the framework. The other reason is that there is a largely mistaken belief that try/catch blocks are expensive.
Comment 8 Konstantin Komissarchik CLA 2011-08-03 19:18:35 EDT
> To reproduce you need to uncheck "Run in UI thread" in Test tab in launch
> configuration.

I did not know about this checkbox or that it would run in the UI thread on its own accord. Thanks for the tip. 

Try now.
Comment 9 Alex Smirnoff CLA 2011-08-03 19:41:22 EDT
Verified. Thanks Kosta.
Comment 10 Konstantin Komissarchik CLA 2011-08-03 19:43:38 EDT
Closing. yrw