Bug 151576 - "Invalid Thread Access" when Generator.build() called from non-UI thread
Summary: "Invalid Thread Access" when Generator.build() called from non-UI thread
Status: RESOLVED FIXED
Alias: None
Product: z_Archived
Classification: Eclipse Foundation
Component: BIRT (show other bugs)
Version: 2.1.0   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 2.2.0 M1   Edit
Assignee: Yueqian Wang CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-07-24 07:35 EDT by Ivan Markov CLA
Modified: 2006-10-09 04:33 EDT (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ivan Markov CLA 2006-07-24 07:35:27 EDT
The likely reason being that besides calling methods of org.eclipse.swt.graphics.GC, methods on org.eclipse.swt.widgets.Display are also called, which is not allowed in a non-UI thread.

=====

Here's the discussion from the newsgroup:

David,

- The "Invalid Thread Access" happens for us too, with BIRT 2.1 (LATEST).
- Oddly, it didn't happen for us with BIRT 2.0

The stacktrace is below. Here's my take why it happens (I should probably
enter bug report):
Suppose we have 2 threads,
- the regular UI thread
- worker thread calling the Generator.build()

Now:
1. GENERALLY, SWT does allow painting (or measuring stuff) with a GC from a
second thread: this means: you CAN call ANY method of GC from within a
worker thread.
2. BUT: You cannot create a new GC instance from the second thread like
this: "new GC(display)" because this construction calls methods of object
display.

The reason?
- The "display" object which is passed is actually a "widget", and so the
widget rules apply: calling its methods can only be done from the thread
where it was created (i.e. our UI thread). This applies to calls such as
"new GC(display)", display.getDPI() (Ivo's problem), and in fact to all the
methods of class Display.

Options?
- One option is to do new Display() from within the 2nd worker thread, but
this only works on Windows.
- Another option is to do the a) "new GC(display)", b) display.getXXX() in
the UI thread, like the following pattern:

in Generator.build():

Display display = Display.getCurrent();
GC gc;
if(display == null) {
    // Bad, no display for this thread => we are not in (a) UI thread
    display.syncExec(new Runnable() {void run() { gc = new GC(display);}});
} else {
    gc = new GC(display);
}

OR:

Display display = Display.getCurrent();
int dpi;
if(display == null) {
    // Bad, no display for this thread => we are not in (a) UI thread
    display.syncExec(new Runnable() {void run() { dpi =
display.getDPI();}});
} else {
    dpi = display.getDPI();
}

Needless to say this will only work if the UI thread spins the message loop,
and it is best if everything you need from the display is taken at one step.
Probably you should also bug the SWT devs to explain you all the intricasies
of using GC from within worker thread.

Ivan

P.S. The stacktrace:

org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:3374)
at org.eclipse.swt.SWT.error(SWT.java:3297)
at org.eclipse.swt.SWT.error(SWT.java:3268)
at org.eclipse.swt.widgets.Display.error(Display.java:978)
at org.eclipse.swt.widgets.Display.checkDevice(Display.java:638)
at org.eclipse.swt.widgets.Display.getSystemFont(Display.java:2038)
at org.eclipse.swt.widgets.Display.internal_new_GC(Display.java:2174)
at org.eclipse.swt.graphics.GC.<init>(GC.java:132)
at org.eclipse.swt.graphics.GC.<init>(GC.java:99)
at
org.eclipse.birt.chart.device.swt.SwtTextMetrics.<init>(SwtTextMetrics.java:
67)
at
org.eclipse.birt.chart.device.swt.SwtDisplayServer.getTextMetrics(SwtDisplay
Server.java:256)
at org.eclipse.birt.chart.computation.Methods.computeBox(Methods.java:764)
at
org.eclipse.birt.chart.model.layout.impl.LabelBlockImpl.getPreferredSize(Lab
elBlockImpl.java:263)
at
org.eclipse.birt.chart.internal.layout.LayoutManager.doLayout_tmp(LayoutMana
ger.java:65)
at
org.eclipse.birt.chart.internal.layout.LayoutManager.doLayout(LayoutManager.
java:597)
at org.eclipse.birt.chart.factory.Generator.build(Generator.java:962)
at
com.sciant.cpgi.ccp.ui.internal.GenerateChartStateJob.run(GenerateChartState
Job.java:47)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:58)



"Ivo Bosticky" <ivo@catapult.com> wrote in message
news:e9p3dp$nhv$1@utils.eclipse.org...
> David,
>
> I pasted the stack trace below.
>
> Just so that you know, we're using BIRT Chart Engine 2.0.1 in standalone
> mode - so not latest release.
>
> If it would make it easier, I can give you my BIRT Chart Canvas code to
have
> a look at (it should be easy for me to convert it into a stand-alone
> application).
>
> Thanks
> Ivo
>
> Jul 20, 2006 5:04:29 PM org.eclipse.birt.chart.exception.ChartException
> logThis
> SEVERE: Exception
> org.eclipse.birt.chart.exception.ChartException: $NO-RB$ Invalid thread
> access
>         at
org.eclipse.birt.chart.factory.Generator.build(Generator.java:867)
>         at
>
com.catapult.pesq.ui.swt.detailed_analysis.BirtChartCanvas$Updater.completeU
pdate(BirtChartCanvas.java:143)
>         at
>
com.catapult.pesq.ui.swt.detailed_analysis.BirtChartCanvas$Updater.run(BirtC
hartCanvas.java:99)
>         at java.lang.Thread.run(Thread.java:595)
> Caused by: org.eclipse.swt.SWTException: Invalid thread access
>         at org.eclipse.swt.SWT.error(SWT.java:2942)
>         at org.eclipse.swt.SWT.error(SWT.java:2865)
>         at org.eclipse.swt.SWT.error(SWT.java:2836)
>         at org.eclipse.swt.widgets.Display.error(Display.java:995)
>         at org.eclipse.swt.widgets.Display.checkDevice(Display.java:626)
>         at org.eclipse.swt.widgets.Display.getDPI(Display.java:1403)
>         at
>
org.eclipse.birt.chart.device.swt.SwtDisplayServer.getDpiResolution(SwtDispl
ayServer.java:196)
>         at
>
org.eclipse.birt.chart.computation.withaxes.PlotWith2DAxes.<init>(PlotWith2D
Axes.java:89)
>         at
org.eclipse.birt.chart.factory.Generator.build(Generator.java:860)
>         ... 3 more
> org.eclipse.birt.chart.exception.ChartException: $NO-RB$ Invalid thread
> access
>         at
org.eclipse.birt.chart.factory.Generator.build(Generator.java:867)
>         at
>
com.catapult.pesq.ui.swt.detailed_analysis.BirtChartCanvas$Updater.completeU
pdate(BirtChartCanvas.java:143)
>         at
>
com.catapult.pesq.ui.swt.detailed_analysis.BirtChartCanvas$Updater.run(BirtC
hartCanvas.java:99)
>         at java.lang.Thread.run(Thread.java:595)
> Caused by: org.eclipse.swt.SWTException: Invalid thread access
>         at org.eclipse.swt.SWT.error(SWT.java:2942)
>         at org.eclipse.swt.SWT.error(SWT.java:2865)
>         at org.eclipse.swt.SWT.error(SWT.java:2836)
>         at org.eclipse.swt.widgets.Display.error(Display.java:995)
>         at org.eclipse.swt.widgets.Display.checkDevice(Display.java:626)
>         at org.eclipse.swt.widgets.Display.getDPI(Display.java:1403)
>         at
>
org.eclipse.birt.chart.device.swt.SwtDisplayServer.getDpiResolution(SwtDispl
ayServer.java:196)
>         at
>
org.eclipse.birt.chart.computation.withaxes.PlotWith2DAxes.<init>(PlotWith2D
Axes.java:89)
>         at
org.eclipse.birt.chart.factory.Generator.build(Generator.java:860)
>         ... 3 more
>
> David Michonneau wrote:
>
> > Hi Ivo,
> >
> > Can you post the stack trace? How do you start the non-UI thread?
> >
> > Thanks,
> >
> > David
> >
> > "Ivo Bosticky" <ivo@catapult.com> wrote in message
> > news:e9n9sv$lml$1@utils.eclipse.org...
> >> Hi David,
> >>
> >> I was able to render our chart into an Image, and then draw this image
on
> >> the SWT Canvas each time it requires a repaint. With this approach most
> >> of the repaint delays have been eliminated, and our problem has been
> >> solved.
> >>
> >> I still can't get around the initial repaint delay, because I don't
seem
> >> to
> >> be able to get the Chart to build and render in a non-SWT thread. I get
> >> an Invalid Access Exception while calling
> >> IDeviceRenderer.getDisplayServer().getDpiResolution() while scaling. If
> >> the
> >> scaling is taken out then I get the same exception from
> >> Generator.build(...)
> >>
> >> Thank you for your help.
> >> Ivo
> >>
> >>
> >>
> >>
> >> David Michonneau wrote:
> >>
> >>> Hi Ivo,
> >>>
> >>> First, you can try using Generator.refresh() and not
Generator.build(),
> >>> if
> >>> the only thing that changes is the data. Typically what takes time is
> >>> not the generation, but the rendering itself. To make it faster, you
> >>> want to minimize the rendering time for each datapoint, which implies:
> >>>
> >>> - a simple datapoint marker graphic with a plain color
> >>> - No datapoint label (use a tooltip instead)
> >>> - No X Axis label (or skip every 1000 labels if you want to show 10
> >>> labels
> >>> in a 10000 points chart for instance).
> >>>
> >>> You can try upgrading to 2.1 too, the performance should be better.
> >>>
> >>> As for making the UI responsive, it's indeed a good idea to generate
the
> >>> chart into an image in a separate thread then update the UI with the
new
> >>> image. It's like double-buffering and will also avoid any flickering,
> >>> you can look in the SWT chart examples of the examples plugin to see
how
> >>> to do
> >>> it.
> >>>
> >>> Thanks,
> >>>
> >>> David
> >>>
> >>> "Ivo Bosticky" <ivo@catapult.com> wrote in message
> >>> news:e9kmnn$spc$1@utils.eclipse.org...
> >>>> Hi,
> >>>>
> >>>> We're rendering Charts onto a SWT Canvas in our RCP application.
> >>>> Performance is sufficient for simple charts, but when the number
> >>>> of data-points increases, screen repaints slow down. At 10000 data
> >>>> points, it takes several seconds each time the Canvas needs to
> >>>> be repainted. Since the SWT thread is doing the generation
> >>>> and rendering, the GUI appears unresponsive, and this is turning
> >>>> into a usability issue. It's possible that the number of data
> >>>> points will increase to the 100,000 level in the future, at
> >>>> which stage the rendering will take too long for users to
> >>>> tolerate without a progress dialog. I had a look at the Chart FAQ
> >>>> and also into IDeviceRenderer, and I suspect it should be possible
> >>>> to generate the chart once into an Image, and then use this cached
> >>>> Image to update the SWT Canvas, but I'm not sure exactly how to
> >>>> go about doing this. Does someone out there have experience with this
> >>>> issue and how to go about resolving it?
> >>>>
> >>>> Thanks,
> >>>> Ivo
> >>>>
> >>>>
> >>
>
Comment 1 Yueqian Wang CLA 2006-10-09 03:39:16 EDT
Add thread protection for the creation of GC
Comment 2 Ivan Markov CLA 2006-10-09 04:04:51 EDT
Could you elaborate a bit on what exactly is fixed? In other words:
1. Will it be possible to call Generatior.build() from a non-UI thread, using my workarounds?
2. Or you've just forbidden it completely (not very nice)?

Comment 3 Yueqian Wang CLA 2006-10-09 04:17:43 EDT
Now I call Display.syncExec() to create a new GC. So you can call the Generator.build()  from non-UI thread. If there are any other questions, please tell me.
Comment 4 Ivan Markov CLA 2006-10-09 04:21:29 EDT
Great! 

You've also syncExec'd the call to
"Display.getDPI()" which used to happen outside of new GC(), at
org.eclipse.birt.chart.device.swt.SwtDisplayServer.getDpiResolution(SwtDispl
ayServer.java:196)

for Ivo. Right?
Comment 5 Yueqian Wang CLA 2006-10-09 04:33:27 EDT
Thank you for your reminder. Thread protection has been also added to SwtDisplayServer.getDpiResolution().