This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
[gui][patch] improvements to single threaded event dispatch
- From: graydon hoare <graydon at redhat dot com>
- To: java-patches at gcc dot gnu dot org
- Date: Mon, 17 Jan 2005 22:51:04 -0500
- Subject: [gui][patch] improvements to single threaded event dispatch
hi,
this patch, which I've committed to java-gui-branch, improves the event
dispatch performance of the recent "single threading" patch I put into
the AWT peers. there are two major changes here:
- if the java side is full for "sufficiently long", it checks the
native side in a non-blocking manner to see if there are pending
native events. this prevents java-generated events (painting, timers)
from starving the peer event queue.
- if the peer side is full for "sufficiently long", it transfers
control back up to the java side for a moment, to give it a chance to
drain the java queue.
the latter behavior almost existed in the initial patch, but it used an
ad-hoc "event batch count" to decide when to transfer control back to
java. now both behaviors are controlled by time: if 100ms worth of
processing has elapsed on either side (this is the human perceptual
latency threshold), the transition occurs.
note that this is *not* the same as saying that the two halves of the
event loop are polling or "busy waiting". the thread still blocks in the
native side when there is no event traffic. it simply means that time
measurements govern the transitions between behavior modes *during* the
time when there is activity. you could think of it somewhat like a
timeslice in a multi-threaded program in which the threads might do
blocking i/o.
(indeed, the need for this patch might be a sensible argument that
having two threads was a better state of affairs; but I still think I
prefer this organization, since we have more explicit control over
scheduling).
-graydon
2005-01-17 Graydon Hoare <graydon@redhat.com>
* gnu/awt/xlib/XEventLoop.java: Add non-blocking event mode.
* gnu/awt/xlib/XToolkit.java: Likewise.
* gnu/gcj/xlib/XAnyEvent.java: Likewise.
* gnu/gcj/xlib/natXAnyEvent.cc: Likewise.
* gnu/java/awt/ClasspathToolkit.java: Likewise.
* gnu/java/awt/peer/gtk/GtkToolkit.java: Likewise.
* java/awt/EventQueue.java (getNextEvent):
Adjust event loop to switch to native mode after 100ms.
* javax/swing/Timer.java (drainEvents): Reuse Runnable.
* jni/gtk-peer/gnu_java_awt_peer_gtk_GtkGenericPeer.c
(Java_gnu_java_awt_peer_gtk_GtkGenericPeer_dispose):
Wake up event thread.
* jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c
(Java_gnu_java_awt_peer_gtk_GtkToolkit_iterateNativeQueue):
Adjust event loop to switch to java mode after 100ms.
--- gnu/awt/xlib/XEventLoop.java 4 Jan 2005 18:45:19 -0000 1.3.14.2
+++ gnu/awt/xlib/XEventLoop.java 18 Jan 2005 03:28:58 -0000
@@ -42,23 +42,23 @@
anyEvent.interrupt();
}
- void postNextEvent()
+ void postNextEvent(boolean block)
{
- AWTEvent evt = getNextEvent();
+ AWTEvent evt = getNextEvent(block);
if (evt != null)
queue.postEvent(evt);
}
/** get next event. Will block until events become available. */
- public AWTEvent getNextEvent()
+ public AWTEvent getNextEvent(boolean block)
{
// ASSERT:
if (isIdle())
throw new Error("should not be idle");
AWTEvent event = null;
- if (loadNextEvent())
+ if (loadNextEvent(block))
{
event = createEvent();
event = lightweightRedirector.redirect(event);
@@ -66,7 +66,7 @@
return event;
}
- boolean loadNextEvent()
+ boolean loadNextEvent(boolean block)
{
boolean gotEvent = false;
try
@@ -95,7 +95,7 @@
of events. */
//display.flush(); // implicit?
- gotEvent = anyEvent.loadNext();
+ gotEvent = anyEvent.loadNext(block);
}
catch (RuntimeException re)
{
--- gnu/awt/xlib/XToolkit.java 4 Jan 2005 18:45:20 -0000 1.7.2.3
+++ gnu/awt/xlib/XToolkit.java 18 Jan 2005 03:28:59 -0000
@@ -457,10 +457,10 @@
eventLoop.interrupt();
}
- public void iterateNativeQueue(java.awt.EventQueue locked)
+ public void iterateNativeQueue(java.awt.EventQueue locked, boolean block)
{
interrupted = false;
while (!interrupted)
- eventLoop.postNextEvent();
+ eventLoop.postNextEvent(block);
};
}
--- gnu/gcj/xlib/XAnyEvent.java 4 Jan 2005 18:45:20 -0000 1.3.10.1
+++ gnu/gcj/xlib/XAnyEvent.java 18 Jan 2005 03:28:59 -0000
@@ -70,7 +70,7 @@
/**
* Load next event into the event structure.
*/
- public native boolean loadNext();
+ public native boolean loadNext(boolean block);
public native void interrupt();
public native int getType();
--- gnu/gcj/xlib/natXAnyEvent.cc 4 Jan 2005 18:45:20 -0000 1.1.92.1
+++ gnu/gcj/xlib/natXAnyEvent.cc 18 Jan 2005 03:28:59 -0000
@@ -51,7 +51,7 @@
structure = 0;
}
-jboolean gnu::gcj::xlib::XAnyEvent::loadNext()
+jboolean gnu::gcj::xlib::XAnyEvent::loadNext(jboolean block)
{
::Display* dpy = (::Display*) display->display;
::XEvent* evt = (::XEvent*) structure;
@@ -62,6 +62,9 @@
return true;
}
+ if (!block)
+ return false;
+
int *pipe = reinterpret_cast<int *>(pipefds);
int xfd = XConnectionNumber(dpy);
int pipefd = pipe[0];
--- gnu/java/awt/ClasspathToolkit.java 4 Jan 2005 20:50:38 -0000 1.1.18.7
+++ gnu/java/awt/ClasspathToolkit.java 18 Jan 2005 03:28:59 -0000
@@ -367,5 +367,5 @@
public abstract boolean nativeQueueEmpty();
public abstract void wakeNativeQueue();
- public abstract void iterateNativeQueue(EventQueue locked);
+ public abstract void iterateNativeQueue(EventQueue locked, boolean block);
}
--- gnu/java/awt/peer/gtk/GtkToolkit.java 4 Jan 2005 20:50:39 -0000 1.8.2.17
+++ gnu/java/awt/peer/gtk/GtkToolkit.java 18 Jan 2005 03:28:59 -0000
@@ -656,6 +656,6 @@
public native boolean nativeQueueEmpty();
public native void wakeNativeQueue();
- public native void iterateNativeQueue(EventQueue locked);
+ public native void iterateNativeQueue(EventQueue locked, boolean block);
} // class GtkToolkit
--- java/awt/EventQueue.java 4 Jan 2005 18:45:22 -0000 1.17.4.3
+++ java/awt/EventQueue.java 18 Jan 2005 03:28:59 -0000
@@ -76,6 +76,9 @@
private EventDispatchThread dispatchThread = new EventDispatchThread(this);
private boolean shutdown = false;
+ private long lastNativeQueueAccess = 0;
+ private long humanLatencyThreshold = 100;
+
synchronized void setShutdown (boolean b)
{
shutdown = b;
@@ -122,6 +125,16 @@
{
if (next != null)
return next.getNextEvent();
+
+ ClasspathToolkit tk = ((ClasspathToolkit) Toolkit.getDefaultToolkit());
+ long curr = System.currentTimeMillis();
+
+ if (! tk.nativeQueueEmpty() &&
+ (curr - lastNativeQueueAccess > humanLatencyThreshold))
+ {
+ tk.iterateNativeQueue(this, false);
+ lastNativeQueueAccess = curr;
+ }
while (next_in == next_out)
{
@@ -143,7 +156,8 @@
if (isShutdown())
throw new InterruptedException();
- ((ClasspathToolkit) Toolkit.getDefaultToolkit()).iterateNativeQueue(this);
+ tk.iterateNativeQueue(this, true);
+ lastNativeQueueAccess = System.currentTimeMillis();
}
else
{
--- javax/swing/Timer.java 22 Oct 2004 17:43:52 -0000 1.3.18.12
+++ javax/swing/Timer.java 18 Jan 2005 03:28:59 -0000
@@ -72,6 +72,14 @@
/** DOCUMENT ME! */
private Waker waker;
+ private Runnable drainer = new Runnable()
+ {
+ public void run()
+ {
+ drainEvents();
+ }
+ };
+
/**
* DOCUMENT ME!
*/
@@ -81,14 +89,7 @@
{
queue++;
if (queue == 1)
- SwingUtilities.invokeLater(new Runnable()
- {
- public void run()
- {
- drainEvents();
- }
- });
-
+ SwingUtilities.invokeLater(drainer);
}
}
--- jni/gtk-peer/gnu_java_awt_peer_gtk_GtkGenericPeer.c 7 Nov 2004 00:04:27 -0000 1.1.16.3
+++ jni/gtk-peer/gnu_java_awt_peer_gtk_GtkGenericPeer.c 18 Jan 2005 03:28:59 -0000
@@ -56,6 +56,13 @@
gtk_widget_destroy (GTK_WIDGET (ptr));
gdk_threads_leave ();
+
+ /*
+ * Wake up the main thread, to make sure it re-checks the window
+ * destruction condition.
+ */
+
+ g_main_context_wakeup (NULL);
}
JNIEXPORT void JNICALL
--- jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c 4 Jan 2005 18:45:23 -0000 1.2.16.2
+++ jni/gtk-peer/gnu_java_awt_peer_gtk_GtkToolkit.c 18 Jan 2005 03:28:59 -0000
@@ -40,6 +40,8 @@
#include "gnu_java_awt_peer_gtk_GtkToolkit.h"
#include "gthread-jni.h"
+#include <sys/time.h>
+
#ifdef JVM_SUN
struct state_table *native_state_table;
struct state_table *native_global_ref_table;
@@ -298,12 +300,27 @@
dpi_conversion_factor = PANGO_SCALE * 72.0 / (int_dpi / PANGO_SCALE);
}
+static int
+within_human_latency_tolerance(struct timeval *init)
+{
+ struct timeval curr;
+ unsigned long milliseconds_elapsed;
+
+ gettimeofday(&curr, NULL);
+
+ milliseconds_elapsed = (((curr.tv_sec * 1000) + (curr.tv_usec / 1000))
+ - ((init->tv_sec * 1000) + (init->tv_usec / 1000)));
+
+ return milliseconds_elapsed < 100;
+}
+
JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_GtkToolkit_iterateNativeQueue
(JNIEnv *env,
jobject self __attribute__((unused)),
- jobject lockedQueue)
+ jobject lockedQueue,
+ jboolean block)
{
/* We're holding an EventQueue lock, and we're about to acquire the GDK
* lock before dropping the EventQueue lock. This can deadlock if someone
@@ -313,21 +330,30 @@
* acquiring the GDK lock and calling back into
* EventQueue.getNextEvent().
*/
+
+ struct timeval init;
+ gettimeofday(&init, NULL);
+
gdk_threads_enter ();
(*env)->MonitorExit (env, lockedQueue);
- /* It is quite important that this be a do .. while loop. The first pass
- * should do an iteration w/o a test so that it sleeps when there really
- * aren't any events; and the loop should continue for as many events as
- * there are to avoid pointless thrashing up and down through JNI (it
- * runs very slowly when this is not a loop).
- */
- do
+ if (block)
{
- gtk_main_iteration();
+
+ /* If we're blocking-when-empty, we want a do .. while loop. */
+ do
+ gtk_main_iteration ();
+ while (within_human_latency_tolerance (&init)
+ && gtk_events_pending ());
}
- while (gtk_events_pending());
-
+ else
+ {
+ /* If we're not blocking-when-empty, we want a while loop. */
+ while (within_human_latency_tolerance (&init)
+ && gtk_events_pending ())
+ gtk_main_iteration ();
+ }
+
(*env)->MonitorEnter (env, lockedQueue);
gdk_threads_leave ();
}