JDK-4271416 : Should try to coalesce events on the PostEventQueue
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.3.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 1999-09-14
  • Updated: 2000-05-09
  • Resolved: 2000-05-09
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.4.0 betaFixed
Related Reports
Relates :  
Description
Paint and mouse events are coalesced in the event queue currently.  
However, the intermediate step of posting them to the PostEventQueue in 
/sun/awt/SunToolkit.java produces a noticeable performance hit.  

This was done very artificially: as a test case, Robi simply changed 
every place where the event was posted to the PostEventQueue to post 
directly to the EventQueue.  Painting appeared to happen much faster 
when this was done.  

We should investigate where the slowdown is, and what we can do about it.  
For example, adding code to the PostEventQueue to do coalescing of paint 
events might be one approach.  Or, it might turn out that the bottleneck 
lies somewhere else.  

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: merlin FIXED IN: merlin-beta INTEGRATED IN: merlin-beta
14-06-2004

EVALUATION Commit to fix in Merlin (if not sooner). eric.hawkes@eng 1999-10-19 SunToolkit originally had security hole (4152133 & 4152184) allowing public EventQueue.postEvent to be called on Toolkit thread. To fix it PostEventQueue thread was introduced. This decision seriously hindered performance of event delivering, in particular painting. If Events are posted directly to the EventQueue, painting appear to happen much faster. So it is better to add secure post method to EventQueue itself. The postEventOtherThread was implemented to this purpose. ###@###.### 2000-04-13 Requires CCC approval (new public method). We have begun this process. eric.hawkes@eng 2000-04-13 Name: ssR10077 Date: 05/05/2000 Finally decided to modify PostEventQueue to not be a thread. Now it is polled by EventQueue as needed using SunToolkit.flushPendingEvents() ======================================================================
11-06-2004

SUGGESTED FIX /* * Continually post pending AWTEvents to the Java EventQueue. */ ! public void run() { ! while (keepGoing && !isInterrupted()) { ! try { ! EventQueueItem item; ! synchronized(this) { ! while (keepGoing && (queueHead == null)) { ! notifyAll(); ! wait(); } ! if (!keepGoing) ! break; ! item = queueHead; } - eventQueue.postEvent(item.event); - synchronized(this) { - queueHead = queueHead.next; - if (queueHead == null) - queueTail = null; } - } catch (InterruptedException e) { - keepGoing = false; // Exit gracefully when interrupted - synchronized(this) { - notifyAll(); - } - } - } - } /* * Enqueue an AWTEvent to be posted to the Java EventQueue. */ ! synchronized void postEvent(AWTEvent event) { EventQueueItem item = new EventQueueItem(event); if (queueHead == null) { queueHead = queueTail = item; - notifyAll(); } else { queueTail.next = item; queueTail = item; } } ! ! /* ! * Wait for all pending events to be processed before returning. ! * If other events are posted before the queue empties, those ! * will also be processed before this method returns. ! */ ! void flush() { ! if (Thread.currentThread() == this) { ! return; // Won't wait for itself } - synchronized(this) { - if (queueHead != null) { - try { - wait(); - } catch (InterruptedException e) { } - } - } - } - - /* - * Notifies this PostEventQueue to stop running. - */ - synchronized void quitRunning() { - keepGoing = false; - notifyAll(); - } } // class PostEventQueue class EventQueueItem { --- 463,512 ---- * We do this because EventQueue.postEvent() may be overridden by client * code, and we mustn't ever call client code from the toolkit thread. */ ! class PostEventQueue { private EventQueueItem queueHead = null; private EventQueueItem queueTail = null; private final EventQueue eventQueue; PostEventQueue(EventQueue eq) { eventQueue = eq; } /* * Continually post pending AWTEvents to the Java EventQueue. */ ! public void flush() { ! if (queueHead != null) { ! EventQueueItem tempQueue; ! synchronized (this) { ! tempQueue = queueHead; ! queueHead = queueTail = null; } ! do { ! eventQueue.postEvent(tempQueue.event); ! tempQueue = tempQueue.next; ! } while (tempQueue != null); } } /* * Enqueue an AWTEvent to be posted to the Java EventQueue. */ ! void postEvent(AWTEvent event) { EventQueueItem item = new EventQueueItem(event); + synchronized (this) { if (queueHead == null) { queueHead = queueTail = item; } else { queueTail.next = item; queueTail = item; } } ! synchronized (eventQueue) { ! eventQueue.notifyAll(); } } } // class PostEventQueue class EventQueueItem { ======================================================================
11-06-2004

SUGGESTED FIX ------- EventQueue.java ------- *** /tmp/sccs.g4aWcG Tue Apr 11 17:13:09 2000 --- EventQueue.java Tue Apr 11 17:02:08 2000 *************** *** 64,69 **** --- 64,74 ---- private Queue[] queues = new Queue[NUM_PRIORITIES]; /* + * Intermediate Queue replacing PostEventQueue + */ + private Queue postQueue = new Queue(); + + /* * The next EventQueue on the stack, or null if this EventQueue is * on the top of the stack. If nextQueue is non-null, requests to post * an event are forwarded to nextQueue. *************** *** 102,115 **** * subclass of it. */ public void postEvent(AWTEvent theEvent) { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - if (toolkit instanceof SunToolkit) { - ((SunToolkit)toolkit).flushPendingEvents(); - } postEventPrivate(theEvent); } /** * Post a 1.1-style event to the EventQueue. If there is an * existing event on the queue with the same ID and event source, * the source Component's coalesceEvents method will be called. --- 107,167 ---- * subclass of it. */ public void postEvent(AWTEvent theEvent) { postEventPrivate(theEvent); } /** + * Post Event to Intermediate Queue. This method is secure, + * with no calls to user code. Events will be posted back on + * EventDispatchThread using {@link EventQueue#postEvent} + * + * @param theEvent an instance of java.awt.AWTEvent, or a + * subclass of it. + */ + public final void postEventOtherThread(AWTEvent theEvent) { + EventQueueItem newItem = new EventQueueItem(theEvent); + synchronized(postQueue) { + if(postQueue.head == null) { + postQueue.head = newItem; + postQueue.tail = newItem; + } else { + postQueue.tail.next = newItem; + postQueue.tail = newItem; + } + } + if (noEvents()) { + synchronized(this) { + notifyAll(); + } + } + } + + /** + * Post Events from Intermediate Queue using public postEvent + * Should be called on EventDispatchTread + */ + private void postEventsPending() { + if(dbg.on) { + dbg.assert(isDispatchThread(), "Should be called on EventDispatchTread"); + } + Queue newQueue; + synchronized(postQueue) { + newQueue = postQueue.copyReset(); + } + while(newQueue.head != null) { + postEvent(newQueue.head.event); + newQueue.head = newQueue.head.next; + } + newQueue.tail = null; + } + + /** + * returns is any event event in intermediate Queue + */ + final boolean isEventPending() { + return (postQueue.head != null); + } + /** * Post a 1.1-style event to the EventQueue. If there is an * existing event on the queue with the same ID and event source, * the source Component's coalesceEvents method will be called. *************** *** 233,238 **** --- 285,293 ---- */ public synchronized AWTEvent getNextEvent() throws InterruptedException { do { + if (isEventPending()) { + postEventsPending(); + } for (int i = NUM_PRIORITIES - 1; i >= 0; i--) { if (queues[i].head != null) { EventQueueItem eqi = queues[i].head; *************** *** 457,467 **** * This method is normally called by the source's removeNotify method. */ final void removeSourceEvents(Object source) { - Toolkit toolkit = Toolkit.getDefaultToolkit(); - if (toolkit instanceof SunToolkit) { - ((SunToolkit)toolkit).flushPendingEvents(); - } - synchronized (this) { for (int i = 0; i < NUM_PRIORITIES; i++) { EventQueueItem entry = queues[i].head; --- 512,517 ---- *************** *** 543,554 **** /** * The Queue object holds pointers to the beginning and end of one internal * queue. An EventQueue object is composed of multiple internal Queues, one ! * for each priority supported by the EventQueue. All Events on a particular * internal Queue have identical priority. */ class Queue { EventQueueItem head; EventQueueItem tail; } class EventQueueItem { --- 593,616 ---- /** * The Queue object holds pointers to the beginning and end of one internal * queue. An EventQueue object is composed of multiple internal Queues, one ! * for each priority supported by the EventQueue. And one used as buffer ! * between Toolkit and EventQueue. All Events on a particular * internal Queue have identical priority. */ class Queue { EventQueueItem head; EventQueueItem tail; + public Queue() { + } + public Queue(Queue theQueue) { + head = theQueue.head; + tail = theQueue.tail; + } + Queue copyReset() { + Queue newQueue = new Queue(this); + head = tail = null; + return newQueue; + } } class EventQueueItem { ------- SunToolkit.java ------- *** /tmp/d3MaGh2 Tue Feb 15 15:17:00 2000 --- ./sun/awt/SunToolkit.java Tue Feb 15 15:04:41 2000 *************** *** 36,45 **** private static boolean hotjavaUrlCache = false; // REMIND: UGH!!!!! - /* The key to put()/get() the PostEventQueue into/from the AppContext. - */ - private static final String POST_EVENT_QUEUE_KEY = "PostEventQueue"; - public SunToolkit() { EventQueue eventQueue; String eqName = Toolkit.getProperty("AWT.EventQueueClass", --- 36,41 ---- *************** *** 52,60 **** } AppContext appContext = AppContext.getAppContext(); appContext.put(AppContext.EVENT_QUEUE_KEY, eventQueue); - - PostEventQueue postEventQueue = new PostEventQueue(eventQueue); - appContext.put(POST_EVENT_QUEUE_KEY, postEventQueue); } /* --- 48,53 ---- *************** *** 76,84 **** AppContext appContext = new AppContext(threadGroup); appContext.put(AppContext.EVENT_QUEUE_KEY, eventQueue); - PostEventQueue postEventQueue = new PostEventQueue(eventQueue); - appContext.put(POST_EVENT_QUEUE_KEY, postEventQueue); - return appContext; } --- 69,74 ---- *************** *** 143,170 **** } /* ! * Post an AWTEvent to the Java EventQueue, using the PostEventQueue ! * to avoid possibly calling client code (EventQueueSubclass.postEvent()) ! * on the toolkit (AWT-Windows/AWT-Motif) thread. */ public static void postEvent(AppContext appContext, AWTEvent event) { ! PostEventQueue postEventQueue = ! (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); ! postEventQueue.postEvent(event); } /* - * Flush any pending events which haven't been posted to the AWT - * EventQueue yet. - */ - public void flushPendingEvents() { - AppContext appContext = AppContext.getAppContext(); - PostEventQueue postEventQueue = - (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); - postEventQueue.flush(); - } - - /* * Execute a chunk of code on the Java event handler thread for the * given target. Does not wait for the execution to occur before * returning to the caller. --- 133,147 ---- } /* ! * Post an AWTEvent to the Java EventQueue using safe method ! * EventQueue.postEventOtherThread() */ public static void postEvent(AppContext appContext, AWTEvent event) { ! EventQueue eventQueue = (EventQueue)appContext.get(appContext.EVENT_QUEUE_KEY); ! eventQueue.postEventOtherThread(event); } /* * Execute a chunk of code on the Java event handler thread for the * given target. Does not wait for the execution to occur before * returning to the caller. *************** *** 460,567 **** - /* - * PostEventQueue is a Thread that runs in the same AppContext as the - * Java EventQueue. It is a queue of AWTEvents to be posted to the - * Java EventQueue. The toolkit Thread (AWT-Windows/AWT-Motif) posts - * events to this queue, which then calls EventQueue.postEvent(). - * - * We do this because EventQueue.postEvent() may be overridden by client - * code, and we mustn't ever call client code from the toolkit thread. - */ - class PostEventQueue extends Thread { - static private int threadNum = 0; - private EventQueueItem queueHead = null; - private EventQueueItem queueTail = null; - private boolean keepGoing = true; - private final EventQueue eventQueue; - - PostEventQueue(EventQueue eq) { - super("SunToolkit.PostEventQueue-"+threadNum); - synchronized (PostEventQueue.class) { threadNum++; } - eventQueue = eq; - start(); - } - - /* - * Continually post pending AWTEvents to the Java EventQueue. - */ - public void run() { - while (keepGoing && !isInterrupted()) { - try { - EventQueueItem item; - synchronized(this) { - while (keepGoing && (queueHead == null)) { - notifyAll(); - wait(); - } - if (!keepGoing) - break; - item = queueHead; - } - eventQueue.postEvent(item.event); - synchronized(this) { - queueHead = queueHead.next; - if (queueHead == null) - queueTail = null; - } - } catch (InterruptedException e) { - keepGoing = false; // Exit gracefully when interrupted - synchronized(this) { - notifyAll(); - } - } - } - } - - /* - * Enqueue an AWTEvent to be posted to the Java EventQueue. - */ - synchronized void postEvent(AWTEvent event) { - EventQueueItem item = new EventQueueItem(event); - - if (queueHead == null) { - queueHead = queueTail = item; - notifyAll(); - } else { - queueTail.next = item; - queueTail = item; - } - } - - /* - * Wait for all pending events to be processed before returning. - * If other events are posted before the queue empties, those - * will also be processed before this method returns. - */ - void flush() { - if (Thread.currentThread() == this) { - return; // Won't wait for itself - } - synchronized(this) { - if (queueHead != null) { - try { - wait(); - } catch (InterruptedException e) { - } - } - } - } - - /* - * Notifies this PostEventQueue to stop running. - */ - synchronized void quitRunning() { - keepGoing = false; - notifyAll(); - } - } // class PostEventQueue - - class EventQueueItem { - AWTEvent event; - EventQueueItem next; - - EventQueueItem(AWTEvent evt) { - event = evt; - } - } // class EventQueueItem --- 437,439 ---- ###@###.### 2000-04-13 Name: ssR10077 Date: 05/05/2000 ------- EventQueue.java ------- *** /tmp/sccs.xgayYo Thu Apr 27 12:15:43 2000 --- EventQueue.java Thu Apr 27 12:03:46 2000 *************** *** 102,111 **** * subclass of it. */ public void postEvent(AWTEvent theEvent) { ! Toolkit toolkit = Toolkit.getDefaultToolkit(); ! if (toolkit instanceof SunToolkit) { ! ((SunToolkit)toolkit).flushPendingEvents(); ! } postEventPrivate(theEvent); } --- 102,108 ---- * subclass of it. */ public void postEvent(AWTEvent theEvent) { ! SunToolkit.flushPendingEvents(); postEventPrivate(theEvent); } *************** *** 232,237 **** --- 229,235 ---- * if another thread has interrupted this thread. */ public synchronized AWTEvent getNextEvent() throws InterruptedException { do { + SunToolkit.flushPendingEvents(); for (int i = NUM_PRIORITIES - 1; i >= 0; i--) { if (queues[i].head != null) { *************** *** 457,467 **** * This method is normally called by the source's removeNotify method. */ final void removeSourceEvents(Object source) { ! Toolkit toolkit = Toolkit.getDefaultToolkit(); ! if (toolkit instanceof SunToolkit) { ! ((SunToolkit)toolkit).flushPendingEvents(); ! } ! synchronized (this) { for (int i = 0; i < NUM_PRIORITIES; i++) { EventQueueItem entry = queues[i].head; --- 455,461 ---- * This method is normally called by the source's removeNotify method. */ final void removeSourceEvents(Object source) { ! SunToolkit.flushPendingEvents(); synchronized (this) { for (int i = 0; i < NUM_PRIORITIES; i++) { EventQueueItem entry = queues[i].head; ------- SunToolkit.java ------- *** /tmp/sccs.50ai0o Thu Apr 27 12:15:46 2000 --- SunToolkit.java Thu Apr 27 12:01:35 2000 *************** *** 157,168 **** * Flush any pending events which haven't been posted to the AWT * EventQueue yet. */ ! public void flushPendingEvents() { AppContext appContext = AppContext.getAppContext(); PostEventQueue postEventQueue = (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); postEventQueue.flush(); } /* * Execute a chunk of code on the Java event handler thread for the --- 157,170 ---- * Flush any pending events which haven't been posted to the AWT * EventQueue yet. */ ! public static void flushPendingEvents() { AppContext appContext = AppContext.getAppContext(); PostEventQueue postEventQueue = (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); + if(postEventQueue != null) { postEventQueue.flush(); } + } /* * Execute a chunk of code on the Java event handler thread for the *************** *** 461,552 **** * We do this because EventQueue.postEvent() may be overridden by client * code, and we mustn't ever call client code from the toolkit thread. */ ! class PostEventQueue extends Thread { ! static private int threadNum = 0; private EventQueueItem queueHead = null; private EventQueueItem queueTail = null; - private boolean keepGoing = true; private final EventQueue eventQueue; PostEventQueue(EventQueue eq) { - super("SunToolkit.PostEventQueue-"+threadNum); - synchronized (PostEventQueue.class) { threadNum++; } eventQueue = eq; - start(); }
11-06-2004