Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [albireo-dev] inhibitSizePropagationToAWT

Gordon Hirsch wrote:
> First, let me make sure I understand.
> ...
> I don't completely understand the purpose of the flag

Let's look at how bidirectional notification works in general.

Say, a system A (AWT) and a system B (SWT) keep track of a number or
size. When someone sets the value in A, it needs to propagate to B;
when someone sets the value in B, it needs to propagate to A. What you
*don't* want, is that the propagation A->B triggers a propagation B->A
which again triggers a notification A->B, etc. ad infinitum.

The simplest technique to avoid endless propagation is to have A or B
(or both) cache the previous value, and if the value being set is equal
to the previous value, the call does nothing. So you propagate only if
there's an actual change of the value.

This technique works fine when A and B just propagate what they have been
given. But here one part of the propagation is the parent.layout() call
on the SWT side. So A and B have to negotiate. It is possible - take
e.g. the case of a GridLayout - that A tells B "the new size is 10x10",
B's layout computes a size of 5x6, hence B tells A "the new size is 5x6",
then A discovers that this is below its minimum size of 10x10 and
answers "the new size is 10x10", etc. ad infinitum. The symptom is normally
a stack overflow, or - in our case - a ping-pong game between the event
queues of the two threads.

What can you do to avoid such situations? Either you come up with an
intelligent negotiation system, or you have to stop the propagation after
a certain number of rounds.

I don't see what a negotiation could look like here. AWT has its minimum
and maximum sizes, and the Layout classes do what they want.

So one has to stop the propagation after a certain number of rounds. One
could say, after 5 rounds. But since in this case there is normally
no convergence going on, there's no advantage of this against stopping
after 1 round.

Stopping after 1 round means that when A tells B, while B is reacting on
the notification from A, replies B->A are ignored. This is what the
inhibitSizePropagationToAWT flag implements.

It is better to stop the propagation, leading to a visually broken result
(e.g. part of the SwingControl's clientArea will not be filled by the
AWT EmbeddedFrame), than to go into an event ping-pong.

> As it stands now, this flag will prevent any layout() done by the 
> preferredSizeChanged() method.

No, this is not the case. The layout() is executed; it's only the back-
notification to AWT that is avoided.

You propose to allow this back-propagation, i.e. to stop the notification
after 2 rounds. But in general this is overkill. The only problem that
we have is at the SwingControl creation time, not afterwards.

> 1) SWT_AWT.new_Frame is called. It schedules the code that will set 
> frame size.
> 
> 2) The Swing contents of SwingControl are created and it decides what 
> the preferred sizes are. It updates the cached values and notifies the 
> SWT side about the change (which involves a Display.asyncExec().
> 
> 3) The code scheduled in (1) is run which sets the frame size. At this 
> point the composite is still 64x64 and this is the size used for the frame.
> 
> Correct?

And then 4) The SWT side reacts on 2) and sets the SWT size to 103x130.

That's the essence of what's going wrong.

The code which fetches the frame size from the Composite and the code
which sets it to the frame are in _different_ Runnables. That's where
the race condition lies.

Let's look at the Runnables involved.

*  In the SWT thread:
   A Runnable / action creates the SwingControl. After the constructor
   returns, the application asks for a layout of the view. This calls
   computeSize and then setBounds. This triggers a call to populate(),
   which - inside SWT_AWT.new_Frame() - enqueues a Runnable R-SWT-1
   which will fetch the SWT control's size and enqueue a Runnable R-AWT-1
   which will assign this size to the AWT frame. Then - still inside
   populate() - we enqueue a Runnable R-AWT-2 which will add the children
   Components to the frame.

*  Additionally, something appears to be triggering the RepaintManager,
   so that it enqueues a Runnable R-AWT-3 which will call frame.validate().

Now you have a Runnable R-SWT-1 in the SWT queue and a Runnable R-AWT-2
(and possibly R-AWT-3) in the AWT queue.
It's timing dependent which one runs first. If R-AWT-2 is run before
R-SWT-1, you certainly get the problem.

Here are a few cases that I observed. In them, R-SWT-1 is run first:

* A case that shows the problem (JDK 1.6):
  - SWT thread: Initial Runnable.
  - SWT thread: R-SWT-1.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.
  - AWT thread: R-AWT-1, sets the frame size to 64x64.
  - SWT thread: layout(), 103x130.

* Another case that shows the problem (JDK 1.6):
  - SWT thread: Initial Runnable.
  - AWT thread: R-AWT-3.
  - SWT thread: R-SWT-1.
  - AWT thread: updated component sizes.
  - SWT thread: layout(), 103x130.
  - AWT thread: R-AWT-1, sets the frame size to 64x64.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.

* A case that works (JDK 1.6):
  - SWT thread: Initial Runnable.
  - AWT thread: R-AWT-3.
  - SWT thread: R-SWT-1.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.
  - AWT thread: R-AWT-1, sets the frame size to 64x64.
  - SWT thread: layout(), 103x130.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.

* A case that works (JDK 1.5):
  - SWT thread: Initial Runnable.
  - SWT thread: R-SWT-1.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.
  - SWT thread: layout(), 103x130.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.

* Another case that works (JDK 1.5):
  - SWT thread: Initial Runnable.
  - AWT thread: R-AWT-3.
  - Simultaneously:
    - SWT thread: R-SWT-1.
    - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
      updated component sizes.
  - SWT thread: layout(), 103x130.
  - AWT thread: Frame.validate() called as reaction to COMPONENT_RESIZED event.
    updated component sizes.

These cases are all different - very timing dependent, apparently.
(I don't claim that I understand it. Why does the COMPONENT_RESIZED occur
sometimes twice, sometimes once? Why is the R-AWT-3 executed sometimes only?)

> Given what we now know, it's probably a bad idea for SWT_AWT to be 
> setting the frame size at all. In the long run, we may want to ask for 
> its removal?

Yes. Or at least, we can beg for an argument to new_Frame that controls
this. The argument is pretty simple: This multithreading is an application
business; SWT_AWT should offer the option to not interfere with the
threading done by the application.

Bruno


Back to the top