Bug 1354455 - avoid CompleteResume while FlushQueue is ran on other thread. r=mayhemer draft
authorShih-Chiang Chien <schien@mozilla.com>
Mon, 08 May 2017 19:16:06 +0800
changeset 576653 d04268a04ab24c38bf6fa436526a04f77cd64805
parent 574519 b21b974d60d3075ae24f6fb1bae75d0f122f28fc
child 628270 42c938e74d9addcc4d455aecac1a43a75e00e1c6
push id58441
push userbmo:schien@mozilla.com
push dateFri, 12 May 2017 02:38:46 +0000
reviewersmayhemer
bugs1354455
milestone55.0a1
Bug 1354455 - avoid CompleteResume while FlushQueue is ran on other thread. r=mayhemer If HttpChannelChild::Cancel is triggered off main thread while flushing channel event queue, CompleteResume might be executed while the flush is about to be finished. In this case, queue resumption will not be able to trigger the second queue flush because the previous one is not finished yet. Therefore, the HttpChannelChild::Cancel will be sitted in the queue without executing. MozReview-Commit-ID: GxnkiDUmEnw
netwerk/ipc/ChannelEventQueue.cpp
--- a/netwerk/ipc/ChannelEventQueue.cpp
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -13,17 +13,17 @@
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace net {
 
 ChannelEvent*
 ChannelEventQueue::TakeEvent()
 {
-  MutexAutoLock lock(mMutex);
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mFlushing);
 
   if (mSuspended || mEventQueue.IsEmpty()) {
     return nullptr;
   }
 
   UniquePtr<ChannelEvent> event(Move(mEventQueue[0]));
   mEventQueue.RemoveElementAt(0);
@@ -35,67 +35,68 @@ void
 ChannelEventQueue::FlushQueue()
 {
   // Events flushed could include destruction of channel (and our own
   // destructor) unless we make sure its refcount doesn't drop to 0 while this
   // method is running.
   nsCOMPtr<nsISupports> kungFuDeathGrip(mOwner);
   mozilla::Unused << kungFuDeathGrip; // Not used in this function
 
-  bool needResumeOnOtherThread = false;
+  // Prevent flushed events from flushing the queue recursively
   {
-    // Don't allow event enqueued during flush to make sure all events
-    // are run.
-    ReentrantMonitorAutoEnter monitor(mRunningMonitor);
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(!mFlushing);
+    mFlushing = true;
+  }
 
-    // Prevent flushed events from flushing the queue recursively
+  bool needResumeOnOtherThread = false;
+
+  while (true) {
+    UniquePtr<ChannelEvent> event;
     {
       MutexAutoLock lock(mMutex);
-      MOZ_ASSERT(!mFlushing);
-      mFlushing = true;
-    }
-
-    while (true) {
-      UniquePtr<ChannelEvent> event(TakeEvent());
+      event.reset(TakeEvent());
       if (!event) {
+        MOZ_ASSERT(mFlushing);
+        mFlushing = false;
+        MOZ_ASSERT(mEventQueue.IsEmpty() || (mSuspended || !!mForcedCount));
         break;
       }
+    }
 
-      nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
-      MOZ_ASSERT(target);
+    nsCOMPtr<nsIEventTarget> target = event->GetEventTarget();
+    MOZ_ASSERT(target);
 
-      bool isCurrentThread = false;
-      nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        // Simply run this event on current thread if we are not sure about it
-        // in release channel, or assert in Aurora/Nightly channel.
-        MOZ_DIAGNOSTIC_ASSERT(false);
-        isCurrentThread = true;
-      }
-
-      if (!isCurrentThread) {
-        // Next event needs to run on another thread. Put it back to
-        // the front of the queue can try resume on that thread.
-        Suspend();
-        PrependEvent(event);
-
-        needResumeOnOtherThread = true;
-        break;
-      }
-
-      event->Run();
+    bool isCurrentThread = false;
+    nsresult rv = target->IsOnCurrentThread(&isCurrentThread);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      // Simply run this event on current thread if we are not sure about it
+      // in release channel, or assert in Aurora/Nightly channel.
+      MOZ_DIAGNOSTIC_ASSERT(false);
+      isCurrentThread = true;
     }
 
-    {
-      MutexAutoLock lock(mMutex);
-      MOZ_ASSERT(mFlushing);
-      mFlushing = false;
-      MOZ_ASSERT(mEventQueue.IsEmpty() || (needResumeOnOtherThread || mSuspended || !!mForcedCount));
+    if (!isCurrentThread) {
+      // Next event needs to run on another thread. Put it back to
+      // the front of the queue can try resume on that thread.
+      Suspend();
+      PrependEvent(event);
+
+      needResumeOnOtherThread = true;
+      {
+        MutexAutoLock lock(mMutex);
+        MOZ_ASSERT(mFlushing);
+        mFlushing = false;
+        MOZ_ASSERT(!mEventQueue.IsEmpty());
+      }
+      break;
     }
-  }
+
+    event->Run();
+  } // end of while(true)
 
   // The flush procedure is aborted because next event cannot be run on current
   // thread. We need to resume the event processing right after flush procedure
   // is finished.
   // Note: we cannot call Resume() while "mFlushing == true" because
   // CompleteResume will not trigger FlushQueue while there is an ongoing flush.
   if (needResumeOnOtherThread) {
     Resume();