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
--- 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();