Process XPCOM events from nested native event loops. b=389931 r+sr=roc a=blocking1.9
authormats.palmgren@bredband.net
Tue, 26 Feb 2008 05:36:40 -0800
changeset 12251 75eca74b159c91beb4dd88277a33c7d768eb148f
parent 12250 6b0da17b7eace2c8478022698a8e5db9433f8a5a
child 12252 826ebd925a60db6095154052870ae127b200ffd3
push idunknown
push userunknown
push dateunknown
reviewersblocking1.9
bugs389931
milestone1.9b4pre
Process XPCOM events from nested native event loops. b=389931 r+sr=roc a=blocking1.9
widget/src/xpwidgets/nsBaseAppShell.cpp
widget/src/xpwidgets/nsBaseAppShell.h
--- a/widget/src/xpwidgets/nsBaseAppShell.cpp
+++ b/widget/src/xpwidgets/nsBaseAppShell.cpp
@@ -15,16 +15,17 @@
  * The Original Code is Widget code.
  *
  * The Initial Developer of the Original Code is Google Inc.
  * Portions created by the Initial Developer are Copyright (C) 2006
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Darin Fisher <darin@meer.net> (original author)
+ *   Mats Palmgren <mats.palmgren@bredband.net>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -45,24 +46,25 @@
 // next thread event for at most this many ticks:
 #define THREAD_EVENT_STARVATION_LIMIT PR_MillisecondsToInterval(20)
 
 NS_IMPL_THREADSAFE_ISUPPORTS3(nsBaseAppShell, nsIAppShell, nsIThreadObserver,
                               nsIObserver)
 
 nsBaseAppShell::nsBaseAppShell()
   : mSuspendNativeCount(0)
+  , mBlockedWait(nsnull)
   , mFavorPerf(0)
-  , mNativeEventPending(PR_FALSE)
+  , mNativeEventPending(0)
   , mStarvationDelay(0)
   , mSwitchTime(0)
   , mLastNativeEventTime(0)
+  , mEventloopNestingState(eEventloopNone)
   , mRunWasCalled(PR_FALSE)
   , mExiting(PR_FALSE)
-  , mProcessingNextNativeEvent(PR_FALSE)
 {
 }
 
 nsresult
 nsBaseAppShell::Init()
 {
   // Configure ourselves as an observer for the current thread:
 
@@ -84,37 +86,31 @@ nsBaseAppShell::NativeEventCallback()
 {
   PRInt32 hasPending = PR_AtomicSet(&mNativeEventPending, 0);
   if (hasPending == 0)
     return;
 
   // If DoProcessNextNativeEvent is on the stack, then we assume that we can
   // just unwind and let nsThread::ProcessNextEvent process the next event.
   // However, if we are called from a nested native event loop (maybe via some
-  // plug-in or library function), then we need to eventually process the event
-  // ourselves.  To make that happen, we schedule another call to ourselves and
-  // unset the flag set by DoProcessNextNativeEvent.  This increases the
-  // workload on the native event queue slightly.
-  if (mProcessingNextNativeEvent) {
-#if 0
-    // XXX(darin): This causes a hefty Ts and Txul regression.  We need some
-    // sort of solution to handle native event loops spun from native events,
-    // but we should try to find something better.
-    mProcessingNextNativeEvent = PR_FALSE;
-    if (NS_HasPendingEvents(NS_GetCurrentThread()))
-      OnDispatchedEvent(nsnull);
-#endif
+  // plug-in or library function), then go ahead and process Gecko events now.
+  if (mEventloopNestingState == eEventloopXPCOM) {
+    mEventloopNestingState = eEventloopOther;
+    // XXX there is a tiny risk we will never get a new NativeEventCallback,
+    // XXX see discussion in bug 389931.
     return;
   }
 
   // nsBaseAppShell::Run is not being used to pump events, so this may be
   // our only opportunity to process pending gecko events.
 
+  EventloopNestingState prevVal = mEventloopNestingState;
   nsIThread *thread = NS_GetCurrentThread();
   NS_ProcessPendingEvents(thread, THREAD_EVENT_STARVATION_LIMIT);
+  mEventloopNestingState = prevVal;
 
   // Continue processing pending events later (we don't want to starve the
   // embedders event loop).
   if (NS_HasPendingEvents(thread))
     OnDispatchedEvent(nsnull);
 }
 
 PRBool
@@ -124,23 +120,24 @@ nsBaseAppShell::DoProcessNextNativeEvent
   // in which case we do not want it to process any thread events since we'll
   // do that when this function returns.
   //
   // If the next native event is not our NativeEventCallback, then we may end
   // up recursing into this function.
   //
   // However, if the next native event is not our NativeEventCallback, but it
   // results in another native event loop, then our NativeEventCallback could
-  // fire and it will see mProcessNextNativeEvent as true.
+  // fire and it will see mEventloopNestingState as eEventloopOther.
   //
-  PRBool prevVal = mProcessingNextNativeEvent;
+  EventloopNestingState prevVal = mEventloopNestingState;
+  mEventloopNestingState = eEventloopXPCOM;
 
-  mProcessingNextNativeEvent = PR_TRUE;
-  PRBool result = ProcessNextNativeEvent(mayWait); 
-  mProcessingNextNativeEvent = prevVal;
+  PRBool result = ProcessNextNativeEvent(mayWait);
+
+  mEventloopNestingState = prevVal;
   return result;
 }
 
 //-------------------------------------------------------------------------
 // nsIAppShell methods:
 
 NS_IMETHODIMP
 nsBaseAppShell::Run(void)
@@ -198,61 +195,71 @@ nsBaseAppShell::ResumeNative(void)
 
 // Called from any thread
 NS_IMETHODIMP
 nsBaseAppShell::OnDispatchedEvent(nsIThreadInternal *thr)
 {
   PRInt32 lastVal = PR_AtomicSet(&mNativeEventPending, 1);
   if (lastVal == 1)
     return NS_OK;
-    
+
   ScheduleNativeEventCallback();
   return NS_OK;
 }
 
 // Called from the main thread
 NS_IMETHODIMP
 nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, PRBool mayWait,
                                    PRUint32 recursionDepth)
 {
   PRIntervalTime start = PR_IntervalNow();
   PRIntervalTime limit = THREAD_EVENT_STARVATION_LIMIT;
 
+  // Unblock outer nested wait loop (below).
+  if (mBlockedWait)
+    *mBlockedWait = PR_FALSE;
+
+  PRBool *oldBlockedWait = mBlockedWait;
+  mBlockedWait = &mayWait;
+
+  // When mayWait is true, we need to make sure that there is an event in the
+  // thread's event queue before we return.  Otherwise, the thread will block
+  // on its event queue waiting for an event.
+  PRBool needEvent = mayWait;
+
   if (mFavorPerf <= 0 && start > mSwitchTime + mStarvationDelay) {
     // Favor pending native events
     PRIntervalTime now = start;
     PRBool keepGoing;
     do {
       mLastNativeEventTime = now;
       keepGoing = DoProcessNextNativeEvent(PR_FALSE);
     } while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
   } else {
     // Avoid starving native events completely when in performance mode
     if (start - mLastNativeEventTime > limit) {
       mLastNativeEventTime = start;
       DoProcessNextNativeEvent(PR_FALSE);
     }
   }
 
-  // When mayWait is true, we need to make sure that there is an event in the
-  // thread's event queue before we return.  Otherwise, the thread will block
-  // on its event queue waiting for an event.
-  PRBool needEvent = mayWait;
-
   while (!NS_HasPendingEvents(thr)) {
     // If we have been asked to exit from Run, then we should not wait for
-    // events to process.  
-    if (mExiting && mayWait)
+    // events to process.  Note that an inner nested event loop causes
+    // 'mayWait' to become false too, through 'mBlockedWait'.
+    if (mExiting)
       mayWait = PR_FALSE;
 
     mLastNativeEventTime = PR_IntervalNow();
     if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
       break;
   }
 
+  mBlockedWait = oldBlockedWait;
+
   // Make sure that the thread event queue does not block on its monitor, as
   // it normally would do if it did not have any pending events.  To avoid
   // that, we simply insert a dummy event into its queue during shutdown.
   if (needEvent && !NS_HasPendingEvents(thr)) {  
     if (!mDummyEvent)
       mDummyEvent = new nsRunnable();
     thr->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL);
   }
--- a/widget/src/xpwidgets/nsBaseAppShell.h
+++ b/widget/src/xpwidgets/nsBaseAppShell.h
@@ -95,19 +95,31 @@ protected:
   virtual PRBool ProcessNextNativeEvent(PRBool mayWait) = 0;
 
   PRInt32 mSuspendNativeCount;
 
 private:
   PRBool DoProcessNextNativeEvent(PRBool mayWait);
 
   nsCOMPtr<nsIRunnable> mDummyEvent;
+  /**
+   * mBlockedWait points back to a slot that controls the wait loop in
+   * an outer OnProcessNextEvent invocation.  Nested calls always set
+   * it to PR_FALSE to unblock an outer loop, since all events may
+   * have been consumed by the inner event loop(s).
+   */
+  PRBool *mBlockedWait;
   PRInt32 mFavorPerf;
   PRInt32 mNativeEventPending;
   PRIntervalTime mStarvationDelay;
   PRIntervalTime mSwitchTime;
   PRIntervalTime mLastNativeEventTime;
+  enum EventloopNestingState {
+    eEventloopNone,  // top level thread execution
+    eEventloopXPCOM, // innermost native event loop is ProcessNextNativeEvent
+    eEventloopOther  // innermost native event loop is a native library/plugin etc
+  };
+  EventloopNestingState mEventloopNestingState;
   PRPackedBool mRunWasCalled;
   PRPackedBool mExiting;
-  PRPackedBool mProcessingNextNativeEvent;
 };
 
 #endif // nsBaseAppShell_h__