Hi,
I committed this patch to fix some problems with how GtkMainThread was
implemented. Basically there were race conditions associated with
starting and stopping the native GTK main loop. This resulted in
exceptions like this, when running the Intel test suite:
(.:9593): Gtk-CRITICAL **: gtk_main_quit: assertion `main_loops != NULL'
failed
java.lang.InternalError: Gtk: gtk_main_quit: assertion `main_loops !=
NULL' failed
at gnu.java.awt.peer.gtk.GtkToolkit.gtkQuit(Native Method)
at
gnu.java.awt.peer.gtk.GtkMainThread.endMainThread(GtkMainThread.java:75)
at
gnu.java.awt.peer.gtk.GtkMainThread.destroyWindow(GtkMainThread.java:94)
at gnu.java.awt.peer.gtk.GtkWindowPeer.dispose(GtkWindowPeer.java:83)
...
This patch enforces some conditions: 1) that the GTK main loop is
actually running by the time startMainThread returns; 2) that the GTK
main loop is actually stopped by the time endMainThread returns; 3) that
the GTK main loop is not terminated while it is starting up; and 4) that
the GTK main loop is not started while it is shutting down.
I confirmed that the AWT exit conditions work, and that Intel's AWT and
Swing test suites now run properly. I also checked the GNU Classpath
AWT and Swing examples and gcjwebplugin and confirmed that they work as
before.
Tom
2006-10-17 Thomas Fitzsimmons <[EMAIL PROTECTED]>
* gnu/java/awt/peer/gtk/GtkMainThread.java: Introduce running flag
to track native GTK event loop status.
* native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c: Set and
clear running flag when native GTK event loop starts and stops.
Index: gnu/java/awt/peer/gtk/GtkMainThread.java
===================================================================
RCS file: /sources/classpath/classpath/gnu/java/awt/peer/gtk/GtkMainThread.java,v
retrieving revision 1.15
diff -u -r1.15 GtkMainThread.java
--- gnu/java/awt/peer/gtk/GtkMainThread.java 8 Aug 2006 22:23:36 -0000 1.15
+++ gnu/java/awt/peer/gtk/GtkMainThread.java 17 Oct 2006 23:17:38 -0000
@@ -38,6 +38,53 @@
package gnu.java.awt.peer.gtk;
+import java.awt.AWTEvent;
+
+/**
+ * This class implements the AWT exit conditions listed here:
+ *
+ * http://java.sun.com/j2se/1.5.0/docs/api/java/awt/doc-files/AWTThreadIssues.html
+ *
+ * The Java thread representing the native GTK main loop, that is,
+ * GtkMainThread.mainThread, terminates when GtkToolkit.gtkMain()
+ * returns. That happens in response to the last window peer being
+ * disposed (see GtkWindowPeer.dispose).
+ *
+ * When GtkMainThread.destroyWindow is called for the last window, it
+ * in turn calls GtkMainThread.endMainThread, which calls gtk_quit.
+ * gtk_quit signals gtk_main to return, which causes GtkMainThread.run
+ * to return.
+ *
+ * There should only be one native GTK main loop running at any given
+ * time. In order to safely start and stop the GTK main loop, we use
+ * a running flag and corresponding runningLock. startMainThread will
+ * not return until the native GTK main loop has started, as confirmed
+ * by the native set_running_flag callback setting the running flag to
+ * true. Without this protection, gtk_quit could be called before the
+ * main loop has actually started, which causes GTK assertion
+ * failures. Likewise endMainThread will not return until the native
+ * GTK main loop has ended.
+ *
+ * post_running_flag_callback is called during gtk_main initialization
+ * and no window can be created before startMainThread returns. This
+ * ensures that calling post_running_flag_callback is the first action
+ * taken by the native GTK main loop.
+ *
+ * GtkMainThread.mainThread is started when the window count goes from
+ * zero to one.
+ *
+ * The three exit conditions in the above document are satisfied by
+ * GtkMainThread alone: 1) no displayable AWT or Swing components: if
+ * no window peers exist then no AWT or Swing component is
+ * displayable; 2) no native events in the native event queue: when
+ * the last window is disposed of, the native GTK main loop is
+ * terminated, which means that there is no native event queue let
+ * alone native events therein; 3) no AWT events in the AWT event
+ * queue: endMainThread posts a dummy event to the AWT event queue,
+ * which if the other exit conditions are satisfied, becomes the last
+ * event posted to the AWT event queue before the AWT event dispatch
+ * thread terminates.
+ */
public class GtkMainThread extends Thread
{
/** Count of the number of open windows */
@@ -46,6 +93,12 @@
/** Lock for the above */
private static Object nWindowsLock = new Object();
+ /** Indicates whether or not the GTK main loop is running. */
+ private static boolean running = false;
+
+ /** Lock for the above. */
+ private static Object runningLock = new Object();
+
/** The main thread instance (singleton) */
public static GtkMainThread mainThread;
@@ -60,26 +113,82 @@
GtkToolkit.gtkMain ();
}
+ private static void setRunning(boolean running)
+ {
+ synchronized (runningLock)
+ {
+ GtkMainThread.running = running;
+ runningLock.notifyAll();
+ }
+ }
+
private static void startMainThread()
{
- if( mainThread == null )
+ synchronized (runningLock)
{
- mainThread = new GtkMainThread();
- mainThread.start();
+ if (!running)
+ {
+ mainThread = new GtkMainThread();
+ mainThread.start();
+
+ while (!running)
+ {
+ try
+ {
+ runningLock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println ("GtkMainThread.startMainThread:"
+ + " interrupted while waiting "
+ + " for GTK main loop to start");
+ }
+ }
+ }
}
}
private static void endMainThread()
{
- if( mainThread != null )
- GtkToolkit.gtkQuit();
+ synchronized (runningLock)
+ {
+ if (running)
+ {
+ GtkToolkit.gtkQuit();
+
+ while (running)
+ {
+ try
+ {
+ runningLock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println ("GtkMainThread.endMainThread:"
+ + " interrupted while waiting "
+ + " for GTK main loop to stop");
+ }
+ }
+ // Post a dummy event to wake up the AWT event dispatch
+ // thread. EventQueue.getNextEvent first checks the
+ // shutdown condition, then waits indefinitely for the
+ // next event to be posted. There is a possibility that
+ // the native GTK main loop will end between the
+ // isShutdown check and the wait call. Posting a dummy
+ // event here causes the AWT event dispatch thread to wake
+ // up, giving it a chance to check the shutdown condition
+ // again and terminate cleanly.
+ GtkGenericPeer.q()
+ .postEvent(new WakeupEventDispatchThreadEvent (mainThread));
+ }
+ }
}
public static void createWindow()
{
- synchronized( nWindowsLock )
+ synchronized (nWindowsLock)
{
- if( numberOfWindows == 0 )
+ if (numberOfWindows == 0)
startMainThread();
numberOfWindows++;
}
@@ -87,11 +196,20 @@
public static void destroyWindow()
{
- synchronized( nWindowsLock )
+ synchronized (nWindowsLock)
{
numberOfWindows--;
- if( numberOfWindows == 0 )
+ if (numberOfWindows == 0)
endMainThread();
}
}
-}
\ No newline at end of file
+}
+
+class WakeupEventDispatchThreadEvent
+ extends AWTEvent
+{
+ WakeupEventDispatchThreadEvent(Object o)
+ {
+ super (o, 2000);
+ }
+}
Index: native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c
===================================================================
RCS file: /sources/classpath/classpath/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c,v
retrieving revision 1.29
diff -u -r1.29 gnu_java_awt_peer_gtk_GtkToolkit.c
--- native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c 8 Aug 2006 22:23:36 -0000 1.29
+++ native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c 17 Oct 2006 23:17:38 -0000
@@ -77,8 +77,10 @@
struct state_table *cp_gtk_native_global_ref_table;
static jclass gtkgenericpeer;
+static jclass gtktoolkit;
static JavaVM *java_vm;
static jmethodID printCurrentThreadID;
+static jmethodID setRunningID;
union env_union
{
@@ -99,7 +101,9 @@
double cp_gtk_dpi_conversion_factor;
static void init_glib_threads(JNIEnv *, jint);
-
+static gboolean post_set_running_flag (gpointer);
+static gboolean set_running_flag (gpointer);
+static gboolean clear_running_flag (gpointer);
static void init_dpi_conversion_factor (void);
static void dpi_changed_cb (GtkSettings *settings,
GParamSpec *pspec);
@@ -199,6 +203,10 @@
cp_gtk_global_window_group = gtk_window_group_new ();
init_dpi_conversion_factor ();
+
+ gtktoolkit = (*env)->FindClass(env, "gnu/java/awt/peer/gtk/GtkMainThread");
+ setRunningID = (*env)->GetStaticMethodID (env, gtktoolkit,
+ "setRunning", "(Z)V");
}
@@ -324,6 +332,9 @@
{
gdk_threads_enter ();
+ gtk_init_add (post_set_running_flag, NULL);
+ gtk_quit_add (gtk_main_level (), clear_running_flag, NULL);
+
gtk_main ();
gdk_threads_leave ();
@@ -502,3 +513,28 @@
return (jint) (0xff000000 | (red << 16) | (green << 8) | blue);
}
+
+static gboolean
+post_set_running_flag (gpointer data __attribute__((unused)))
+{
+ g_idle_add (set_running_flag, NULL);
+ return FALSE;
+}
+
+static gboolean
+set_running_flag (gpointer data __attribute__((unused)))
+{
+ (*cp_gtk_gdk_env ())->CallStaticVoidMethod (cp_gtk_gdk_env (),
+ gtktoolkit,
+ setRunningID, TRUE);
+ return FALSE;
+}
+
+static gboolean
+clear_running_flag (gpointer data __attribute__((unused)))
+{
+ (*cp_gtk_gdk_env ())->CallStaticVoidMethod (cp_gtk_gdk_env (),
+ gtktoolkit,
+ setRunningID, FALSE);
+ return FALSE;
+}