Bug 39401 - Swing interoperability - solution proposal
Summary: Swing interoperability - solution proposal
Status: RESOLVED FIXED
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 2.1   Edit
Hardware: PC Windows 2000
: P3 enhancement (vote)
Target Milestone: ---   Edit
Assignee: Steve Northover CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 37724
  Show dependency tree
 
Reported: 2003-06-26 19:09 EDT by Eugene Tarassov CLA
Modified: 2003-07-28 13:35 EDT (History)
3 users (show)

See Also:


Attachments
fixed: unnecessary delays in AWT event dispatch (71.52 KB, text/plain)
2003-07-05 12:40 EDT, Eugene Tarassov CLA
no flags Details
stand alone test case (2.14 KB, text/plain)
2003-07-09 15:00 EDT, Silenio Quarti CLA
no flags Details
code using only API (4.96 KB, text/plain)
2003-07-10 18:31 EDT, Silenio Quarti CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Eugene Tarassov CLA 2003-06-26 19:09:44 EDT
I am proposing to change SWT code to unsure reliable
interoperability between AWT/Swing and SWT.


The changes address following problems:


1. Merging SWT and AWT dispatch loops into one.
Having two dispatch threads makes Swsing based code integration almost 
impossible
because of all kinds of thread synchronization problems,
especially when Swing based code has to access Eclipse public APIs.

2. Original AWT integration plugin is unstable,
mostly because of problem #1. See SwingPlugin.java, new AWT integration plugin 
code,
similar to the original one - it allows to embed AWT Frame into
any SWT Composite, and can be used as basis for an application integration.


Detailed description of changes:

dircmp: Directory not found:
  c:\eclipse\2.1-org\source\plugins\com.windriver.rb.eclipse.awt


*** ET: see SwingPlugin.java at the and of this file.
It defines a class which allows to create AWT Frame embeded into SWT Composite.


dircmp: Files are not equal:
  < Wed Jun 18 00:09:23 2003, size      41450, summ          0
  
eclipse\workspace\org.eclipse.core.boot\src\org\eclipse\core\internal\boot\Inter
nalBootLoader.java
  > Mon Mar 10 17:09:50 2003, size      40712, summ          0
  c:\eclipse\2.1-
org\source\plugins\org.eclipse.core.boot\src\org\eclipse\core\internal\boot\Inte
rnalBootLoader.java
diff 
eclipse\workspace\org.eclipse.core.boot\src\org\eclipse\core\internal\boot\Inter
nalBootLoader.java c:\eclipse\2.1-
org\source\plugins\org.eclipse.core.boot\src\org\eclipse\core\internal\boot\Inte
rnalBootLoader.java | more

*** ET: code below passes control from Main thread to AWT dispatch thread 
during bootstrap.
AWT dispatch thread from now on will be the main and only one UI handlig thread.

835,873c835,852
< public static Object run(
<         final String applicationName/*R1.0 compatibility*/,
<         final URL pluginPathLocation/*R1.0 compatibility*/,
<         final String location,
<         final String[] args,
<         final Runnable handler) throws Exception {
<     final Object result[] = { null };
<     final Exception error[] = { null };
<     java.awt.EventQueue.invokeAndWait(new Runnable() {
<         public void run() {
<             try {
<                 applicationR10 = applicationName; // for R1.0 compatibility
<                 String[] applicationArgs = startup(pluginPathLocation, 
location, args, handler);
<                 
<                 String application = getCurrentPlatformConfiguration
().getApplicationIdentifier();
<                 IPlatformRunnable runnable = getRunnable(application);
<                 if (runnable == null)
<                       throw new IllegalArgumentException(Policy.bind
("application.notFound", application)); //$NON-NLS-1$
<                 try {
<                       result[0] = runnable.run(applicationArgs);
<                 }
<                 catch (Throwable e) {
<                       e.printStackTrace();
<                       throw new InvocationTargetException(e);
<                 }
<                 finally {
<                       shutdown();
<                 }
<             }
<             catch (Exception x) {
<                 error[0] = x;
<             }
<             catch (Throwable x) {
<                 error[0] = new Exception(x);
<             }
<         }
<     });
<     if (error[0] != null) throw error[0];
<     return result[0];
---
> public static Object run(String applicationName/*R1.0 compatibility*/, URL 
pluginPathLocation/*R1.0 compatibility*/, String location, String[] args, 
Runnable handler) throws Exception {
>       Object result = null;
>       applicationR10 = applicationName; // for R1.0 compatibility
>       String[] applicationArgs = startup(pluginPathLocation, location, args, 
handler);
>       
>       String application = getCurrentPlatformConfiguration
().getApplicationIdentifier();
>       IPlatformRunnable runnable = getRunnable(application);
>       if (runnable == null)
>               throw new IllegalArgumentException(Policy.bind
("application.notFound", application)); //$NON-NLS-1$
>       try {
>               result = runnable.run(applicationArgs);
>       } catch (Throwable e) {
>               e.printStackTrace();
>               throw new InvocationTargetException(e);
>       } finally {
>               shutdown();
>       }
>       return result;


dircmp: Files are not equal:
  < Fri Jun 13 09:24:40 2003, size      73433, summ          0
  eclipse\workspace\org.eclipse.swt\Eclipse SWT\win32
\org\eclipse\swt\widgets\Display.java
  > Thu Mar 27 21:52:48 2003, size      70497, summ          0
  c:\eclipse\2.1-org\source\plugins\org.eclipse.swt\Eclipse SWT\win32
\org\eclipse\swt\widgets\Display.java
diff "eclipse\workspace\org.eclipse.swt\Eclipse SWT\win32
\org\eclipse\swt\widgets\Display.java" "c:\eclipse\2.1-
org\source\plugins\org.eclipse.swt\Eclipse SWT\win32
\org\eclipse\swt\widgets\Display.java" | more

*** ET: AWTWatchDog thread is responsible to call wake() when Display is 
sleeping
and AWT event queue got an event.

231,270d230
<     
<         private class AWTWatchDog extends Thread {
<             
<             boolean display_sleeping = false;
<             
<             private java.awt.EventQueue queue = java.awt.Toolkit
<                 .getDefaultToolkit().getSystemEventQueue();
<             
<             AWTWatchDog() {
<                 setName("AWT-WatchDog");
<                 start();
<             }
< 
<             public void run() {
<                 for (;;) {
<                     try {
<                         synchronized (this) {
<                             if (!display_sleeping) {
<                                 wait();
<                                 continue;
<                             }
<                         }
<                         sun.awt.SunToolkit.flushPendingEvents();
<                         synchronized (queue) {
<                             if (queue.peekEvent() == null) {
<                                 queue.wait();
<                                 continue;
<                             }
<                         }
<                         wake();
<                     }
<                     catch (Throwable x) {
<                         x.printStackTrace();
<                     }
<                 }
<             }
<         }
<     
<         private final AWTWatchDog awt_watch_dog = new AWTWatchDog();
<

*** ET: main purpose of changes below is to insert call to runATW() into 
dispatch loop.
I also changed dispatch order a little, because I think it is more
efficient this way, but it is not really necessary.

1576,1577c1536
<         boolean res = false;
<       checkDevice ();
---
>       checkDevice ();
1579,1580c1538,1539
<       if (runPopups ()) res = true;
<       while (OS.PeekMessage (msg, 0, 0, 0, OS.PM_REMOVE)) {
---
>       runPopups ();
>       if (OS.PeekMessage (msg, 0, 0, 0, OS.PM_REMOVE)) {
1585d1543
<                                 res = true;
1586a1545,1546
>                       runDeferredEvents ();
>                       return true;
1589,1592c1549
<         if (runAWT ()) res = true;
<         if (eventQueue != null && runDeferredEvents ()) res = true;
<       if (runAsyncMessages ()) res = true;
<         return res;
---
>       return runAsyncMessages ();

*** ET: method runAWT() reads AWT queue and dispatches events.
It would be nice to call java.awt.EventQueue.dispatchEvent() here,
but unfortunately it is not public, so I had to put together
another version of dispatch code.

1913,1943d1869
< private boolean runAWT () {
<     if (!java.awt.EventQueue.isDispatchThread()) throw new Error("Must be 
dispatch thread");
<     sun.awt.SunToolkit.flushPendingEvents();
<     java.awt.EventQueue q = java.awt.Toolkit
<         .getDefaultToolkit().getSystemEventQueue();
<     boolean res = false;
<     while (q.peekEvent() != null) {
<         try {
<             res = true;
<             java.awt.AWTEvent e = q.getNextEvent();
<             Object src = e.getSource();
<             if (e instanceof java.awt.ActiveEvent) {
<                 ((java.awt.ActiveEvent)e).dispatch();
<             }
<             else if (src instanceof java.awt.Component) {
<                 ((java.awt.Component)src).dispatchEvent(e);
<             }
<             else if (src instanceof java.awt.MenuComponent) {
<                 ((java.awt.MenuComponent)src).dispatchEvent(e);
<             }
<             else {
<                 throw new Error("Unable to dispatch event: " + e);
<             }
<         }
<         catch (InterruptedException x) {
<             throw new Error(x);
<         }
<     }
<     return res;
< }
< 

*** ET: sleep() method is modified to notify AWTWatchDog (see above)

2203,2207d2128
<         boolean res = false;
<         synchronized (awt_watch_dog) {
<             awt_watch_dog.display_sleeping = true;
<             awt_watch_dog.notifyAll();
<         }
2209,2210c2130,2131
<             OS.MsgWaitForMultipleObjectsEx (0, 0, OS.INFINITE, 
OS.QS_ALLINPUT, OS.MWMO_INPUTAVAILABLE);
<             res = true;
---
>               OS.MsgWaitForMultipleObjectsEx (0, 0, OS.INFINITE, 
OS.QS_ALLINPUT, OS.MWMO_INPUTAVAILABLE);
>               return true;
2212,2219c2133
<         else {
<             res = OS.WaitMessage ();
<         }
<         synchronized (awt_watch_dog) {
<             awt_watch_dog.display_sleeping = false;
<             awt_watch_dog.notifyAll();
<         }
<         return res;
---
>       return OS.WaitMessage ();


*** ET: SwingPlugin.java

package com.windriver.rb.eclipse.awt;

import java.awt.*;

import org.eclipse.core.runtime.IPluginDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.internal.win32.RECT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.plugin.AbstractUIPlugin;

import sun.awt.windows.WEmbeddedFrame;

public class SwingPlugin extends AbstractUIPlugin {
	
    private Display display;
        
    private static class EFrame extends WEmbeddedFrame implements Listener {
    	
    	private Composite parent;
    	private String name;
    	
    	EFrame(Composite parent, String name) {
            super(parent.handle);
            this.parent = parent;
            this.name = name;
            parent.addListener(SWT.Resize, this);
            parent.addListener(SWT.Move, this);
            parent.addListener(SWT.Dispose, this);
            parent.getShell().addListener(SWT.Move, this);
            setFrameBounds();
            setCursor(Cursor.getDefaultCursor());
            addNotify();
            validate();
    	}
    	
    	public void handleEvent(Event e) {
            if (e.type == SWT.Dispose) {
            	parent.removeListener(SWT.Resize, this);
            	parent.removeListener(SWT.Move, this);
            	parent.removeListener(SWT.Dispose, this);
            	parent.getShell().removeListener(SWT.Move, this);
            }
            else {
            	setFrameBounds();
            }
    	}
            	
    	private void setFrameBounds() {
            org.eclipse.swt.graphics.Rectangle rc = parent.getClientArea();
            RECT rw = new RECT();
            OS.GetWindowRect(parent.handle, rw);
            setBounds(rw.left + rc.x, rw.top + rc.y, rc.width, rc.height);
            validate();
    	}
        
        public void paint(Graphics g) {
            super.paint(g);
            sun.awt.windows.WGlobalCursorManager.getCursorManager
().updateCursorImmediately();
        }
    }
    
    public SwingPlugin(IPluginDescriptor descriptor) {
    	super(descriptor);
    }
    
    public static Frame createFrame(Composite parent, String name) {
            return new EFrame(parent, name);
    }
    
    public static Composite getParentComposite(Component f) {
        if (f instanceof EFrame) return ((EFrame)f).parent;
        return null;
    }
}
Comment 1 Steve Northover CLA 2003-06-27 15:41:00 EDT
SSQ and SN to evaluate the code and the approach.
Comment 2 Eugene Tarassov CLA 2003-07-05 12:40:21 EDT
Created attachment 5361 [details]
fixed: unnecessary delays in AWT event dispatch
Comment 3 Eugene Tarassov CLA 2003-07-05 12:41:38 EDT
I've found and fixed couple defects which were causing unnecessary delays in 
AWT event dispatch. New version of Display.java is attached. I'm still working 
on a version for Motif.
I also would like to point out that this code provides only low level 
integration, i.e. event dispatch and windows parent/child coupling. It would be 
nice to include focus management integration, shortcuts support, etc. For now 
it has to be done on a client side. Providing such support would make an 
application integration job much easier.
Comment 4 Silenio Quarti CLA 2003-07-09 15:00:07 EDT
Created attachment 5410 [details]
stand alone test case

The following attachment is a stand alone test case for the code.
Comment 5 Silenio Quarti CLA 2003-07-09 15:34:48 EDT
It seems that the only advantage of this strategy is that application code 
does not have to call syncExec(), asyncExec(), invokeAndWait() or invokeLater
() in order to communicate between threads.  There are still two user 
interface threads in the operating system.  One is AWT's user interace thread 
and the other is the SWT's user interface thread (which happens also to be 
AWT's event dispatcher thread).

Runing the test case above after applying the changes to Display causes a 
deadlock when you resize the window to be as small as possible.  Debugging 
this under Eclipse shows the deadlock.  AWT's user interface thread is trying 
to calling getGraphicsConfiguration():

Thread [AWT-Windows] (Suspended)
	AWT_SWT_SingleLoop$EFrame(Window).getGraphicsConfiguration() line: 
1857 [local variables unavailable]
	WEmbeddedFramePeer(WComponentPeer).getGraphicsConfiguration() line: 301
	WEmbeddedFramePeer(WComponentPeer).getDeviceColorModel() line: 361
	Win32SurfaceData.createData(WComponentPeer, int) line: 219
	WEmbeddedFramePeer(WComponentPeer).replaceSurfaceData() line: 319
	WToolkit.eventLoop() line: not available [native method]
	WToolkit.run() line: 222
	Thread.run() line: 539

AWT's event dispatcher (also SWT's user interface thread) is trying to resize 
the AWT embedded frame:

Thread [AWT-EventQueue-0] (Suspended)
	WWindowPeer.reshapeFrame(int, int, int, int) line: not available 
[native method]
	WEmbeddedFramePeer(WFramePeer).reshape(int, int, int, int) line: 50
	WEmbeddedFramePeer(WComponentPeer).setBounds(int, int, int, int) line: 
130
	AWT_SWT_SingleLoop$EFrame(Component).reshape(int, int, int, int) line: 
1686
	AWT_SWT_SingleLoop$EFrame(Component).setBounds(int, int, int, int) 
line: 1645
	AWT_SWT_SingleLoop$EFrame.setFrameBounds() line: 52
	AWT_SWT_SingleLoop$EFrame.handleEvent(Event) line: 44
	EventTable.sendEvent(Event) line: 82
	Shell(Widget).sendEvent(Event) line: 848
	Shell(Widget).sendEvent(int, Event, boolean) line: 872
	Shell(Widget).sendEvent(int) line: 853
	Shell(Control).WM_SIZE(int, int) line: 4017
	Shell(Scrollable).WM_SIZE(int, int) line: 308
	Shell(Composite).WM_SIZE(int, int) line: 780
	Shell(Decorations).WM_SIZE(int, int) line: 1415
	Shell(Control).windowProc(int, int, int) line: 2893
	Shell(Decorations).windowProc(int, int, int) line: 1280
	Display.windowProc(int, int, int, int) line: 2640
	OS.DefWindowProcW(int, int, int, int) line: not available [native 
method]
	OS.DefWindowProc(int, int, int, int) line: 1332
	Shell.callWindowProc(int, int, int) line: 397
	Shell(Control).windowProc(int, int, int) line: 2906
	Shell(Decorations).windowProc(int, int, int) line: 1280
	Display.windowProc(int, int, int, int) line: 2640
	OS.DefWindowProcW(int, int, int, int) line: not available [native 
method]
	OS.DefWindowProc(int, int, int, int) line: 1332
	Shell.callWindowProc(int, int, int) line: 397
	Shell(Control).windowProc(int, int, int) line: 2906
	Shell(Decorations).windowProc(int, int, int) line: 1280
	Display.windowProc(int, int, int, int) line: 2640
	OS.DefWindowProcW(int, int, int, int) line: not available [native 
method]
	OS.DefWindowProc(int, int, int, int) line: 1332
	Shell.callWindowProc(int, int, int) line: 397
	Shell(Control).windowProc(int, int, int) line: 2906
	Shell(Decorations).windowProc(int, int, int) line: 1280
	Display.windowProc(int, int, int, int) line: 2640
	OS.DispatchMessageW(MSG) line: not available [native method]
	OS.DispatchMessage(MSG) line: 1337
	Display.readAndDispatch() line: 1787
	AWT_SWT_SingleLoop$1.run() line: 75
	InvocationEvent.dispatch() line: 174
	EventQueue.dispatchEvent(AWTEvent) line: 446
	EventDispatchThread.pumpOneEventForHierarchy(int, Component) line: 193
	EventDispatchThread.pumpEventsForHierarchy(int, Conditional, 
Component) line: 147
	EventDispatchThread.pumpEvents(int, Conditional) line: 141
	EventDispatchThread.pumpEvents(Conditional) line: 133
	EventDispatchThread.run() line: 101

It seems that this strategy can deadlock (easily?) in code that is beyond our 
control.  We think that automatically attempting to hide synchronozation 
issues will not work.

SN and SSQ
Comment 6 Eugene Tarassov CLA 2003-07-10 14:20:28 EDT
I investigated the deadlock problem found by Silenio.
Here is what I've found:

1. Deadlock caused by invalid synchronization code in AWT,
    two threads acquiring locks in different order:
        Thread [AWT-EventQueue-0]
                synchronized (Component.getTreeLock()) { ...
                    synchronized (WWindowPeer.this) { ...
        Thread [AWT-Windows]
                synchronized (WWindowPeer.this) { ...
                    synchronized (Component.getTreeLock()) { ...

2. Bug has nothing to do with SWT or Eclipse, it exists in pure Java.

3. Bug exists only in 1.4.0 beta and release candidate versions of JDK.
    1.3.1 does not have relevant code at all. 1.4.0 final has it fixed by
    substituting call to WComponentPeer.replaceSurfaceData() with 
    calling WComponentPeer.replaceSurfaceDataLater(), which is similar
    to using asyncExec().

I also would like to add: I argree with Silenio - the only advantage of this
strategy is that application code does not have to call syncExec(),
asyncExec(), invokeAndWait() or invokeLater() in order to communicate
between threads. However, IMHO, this is HUGE advantage!
It changes tight integration of Swing based application from
practically impossible to relatively simple.

BTW, "There are still two user interface threads ..." - it is not exactly true.
AWT-Windows thread never executes application code.
It's scope is limited to AWT native code.
After proposed changes application UI code is executed by single thread,
which eliminates all (well, almost) synchronization problems.
Comment 7 Steve Northover CLA 2003-07-10 14:57:48 EDT
Nope, I'm sorry, there are still 2 Windows user interface threads with their 
associated Windows message queues and other hidden Windows thread specific 
data that Windows creates behind the scenes when you create the first HWND in 
a thread.  Windows knows when a thread is a UI-thread and treats it 
differently.

SSQ and SN to continue the investigation of deadlock issues with a better VM.
Comment 8 Eugene Tarassov CLA 2003-07-10 16:15:43 EDT
Steve, you right, there are two Windows event queues and two threads reading
Windows events. But that is not what I worry about. I'm developing
application code on top of JDK and Eclipse. To get synchronization right
I only need to know which threads will run into MY code (application code)
through call-backs, public interfaces, etc., and which threads I have to use
to call public APIs. There are two such threads: "main" from Eclipse and
"AWT-EventQueue" from AWT. "AWT-Windows" thread never reaches my code, so
I don't care much about it as long as it stays that way.
The idea is to make sure there is exactly one such thread (it does not really
matter which one - I have modified version of AWT which uses "main" for
dispatch - works fine).
Having one thread in UI part of my application would make my job a LOT easier.
Comment 9 Steve Northover CLA 2003-07-10 16:35:11 EDT
I agree.  There is only one "user interface thread" as far as the programmer 
is concerned.

Here's a thought:

If the AWT-Windows thread (OS UI-thread for AWT) ever blocks and can no longer 
process messages for the AWT Event-Queue thread (which also happens to be the 
the OS UI-thread for SWT), then we can deadlock.
Comment 10 Silenio Quarti CLA 2003-07-10 18:31:15 EDT
Created attachment 5423 [details]
code using only API

Here is an implementation of the strategy the uses SWT API only. No hacks in
Display are necessary.
Comment 11 Eugene Tarassov CLA 2003-07-10 19:01:19 EDT
> Here is an implementation of the strategy the uses SWT API only...
Cool!
The only hack needed after that is invokeAndWait() somewhere in
Eclipse booter code.
Comment 12 Steve Northover CLA 2003-07-28 10:55:52 EDT
Eugene, I would like to close this PR as there doesn't seem that there is 
anything left for SWT to do.  Your thoughts?
Comment 13 Eugene Tarassov CLA 2003-07-28 12:37:56 EDT
Hi Steve,
You can close it. The solution works perfectly for Windows.
I still have to make it work on Unix, but I can open new PR for that if it will 
be necessary.
Comment 14 Steve Northover CLA 2003-07-28 13:35:24 EDT
Thanks.  Good luck.