Bug 660774 - e10s necko: refactor channelEventQueue to allow async resume/flush. r=jdm
authorJason Duell <jduell.mcbugs@gmail.com>
Sat, 11 Jun 2011 18:37:09 -0700
changeset 71211 4a33bc1f772d96c3dda760ad6d631e952b6b1cdc
parent 71210 6558faa0b314d6ba5d76d08e09704de13a11fec3
child 71212 8018eea2a540ea9e922c06e704b373c13bc8638c
push id209
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:42:16 +0000
treeherdermozilla-aurora@cc6e30cce8af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs660774
milestone7.0a1
Bug 660774 - e10s necko: refactor channelEventQueue to allow async resume/flush. r=jdm
netwerk/ipc/ChannelEventQueue.cpp
netwerk/ipc/ChannelEventQueue.h
netwerk/ipc/Makefile.in
netwerk/protocol/ftp/FTPChannelChild.cpp
netwerk/protocol/ftp/FTPChannelChild.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
netwerk/protocol/wyciwyg/WyciwygChannelChild.h
netwerk/test/unit/test_httpsuspend.js
new file mode 100644
--- /dev/null
+++ b/netwerk/ipc/ChannelEventQueue.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set sw=2 ts=8 et tw=80 :
+ */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at:
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Code.
+ *
+ * The Initial Developer of the Original Code is
+ *   The Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Josh Matthews <josh@joshmatthews.net> (initial developer)
+ *   Jason Duell <jduell.mcbugs@gmail.com>
+ *
+ * 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
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIChannel.h"
+#include "mozilla/net/ChannelEventQueue.h"
+
+namespace mozilla {
+namespace net {
+
+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<nsIChannel> kungFuDeathGrip(mOwner);
+
+  // Prevent flushed events from flushing the queue recursively
+  mFlushing = true;
+
+  PRUint32 i;
+  for (i = 0; i < mEventQueue.Length(); i++) {
+    mEventQueue[i]->Run();
+    if (mSuspended)
+      break;
+  }
+
+  // We will always want to remove at least one finished callback.
+  if (i < mEventQueue.Length())
+    i++;
+
+  // It is possible for new callbacks to be enqueued as we are
+  // flushing the queue, so the queue must not be cleared until
+  // all callbacks have run.
+  mEventQueue.RemoveElementsAt(0, i);
+
+  mFlushing = false;
+}
+
+
+}
+}
--- a/netwerk/ipc/ChannelEventQueue.h
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -1,10 +1,10 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: sw=2 ts=8 et :
+ * vim: set sw=2 ts=8 et tw=80 :
  */
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  * http://www.mozilla.org/MPL/
@@ -18,16 +18,17 @@
  *
  * The Initial Developer of the Original Code is
  *   The Mozilla Foundation
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Josh Matthews <josh@joshmatthews.net> (initial developer)
+ *   Jason Duell <jduell.mcbugs@gmail.com>
  *
  * 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
@@ -36,148 +37,165 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef mozilla_net_ChannelEventQueue_h
 #define mozilla_net_ChannelEventQueue_h
 
+#include <nsTArray.h>
+#include <nsAutoPtr.h>
+
+class nsIChannel;
+
 namespace mozilla {
 namespace net {
 
 class ChannelEvent
 {
  public:
   ChannelEvent() { MOZ_COUNT_CTOR(ChannelEvent); }
   virtual ~ChannelEvent() { MOZ_COUNT_DTOR(ChannelEvent); }
   virtual void Run() = 0;
 };
 
 // Workaround for Necko re-entrancy dangers. We buffer IPDL messages in a
 // queue if still dispatching previous one(s) to listeners/observers.
 // Otherwise synchronous XMLHttpRequests and/or other code that spins the
 // event loop (ex: IPDL rpc) could cause listener->OnDataAvailable (for
-// instance) to be called before mListener->OnStartRequest has completed.
+// instance) to be dispatched and called before mListener->OnStartRequest has
+// completed.
 
-template<class T> class AutoEventEnqueuerBase;
+class AutoEventEnqueuerBase;
 
-template<class T>
 class ChannelEventQueue
 {
  public:
-  ChannelEventQueue(T* self) : mQueuePhase(PHASE_UNQUEUED)
-                             , mSelf(self) {}
+  ChannelEventQueue(nsIChannel *owner)
+    : mForced(false)
+    , mSuspended(false)
+    , mFlushing(false)
+    , mOwner(owner) {}
+
   ~ChannelEventQueue() {}
-  
- protected:
-  void BeginEventQueueing();
-  void EndEventQueueing();
-  void EnqueueEvent(ChannelEvent* callback);
-  bool ShouldEnqueue();
-  void FlushEventQueue();
+
+  // Checks to determine if an IPDL-generated channel event can be processed
+  // immediately, or needs to be queued using Enqueue().
+  inline bool ShouldEnqueue();
+
+  // Puts IPDL-generated channel event into queue, to be run later
+  // automatically when EndForcedQueueing and/or Resume is called.
+  inline void Enqueue(ChannelEvent* callback);
+
+  // After StartForcedQueueing is called, ShouldEnqueue() will return true and
+  // no events will be run/flushed until EndForcedQueueing is called.
+  // - Note: queueing may still be required after EndForcedQueueing() (if the
+  //   queue is suspended, etc):  always call ShouldEnqueue() to determine
+  //   whether queueing is needed.
+  inline void StartForcedQueueing();
+  inline void EndForcedQueueing();
+
+  // Suspend/resume event queue.  ShouldEnqueue() will return true and no events
+  // will be run/flushed until resume is called.  These should be called when
+  // the channel owning the event queue is suspended/resumed.
+  // - Note: these suspend/resume functions are NOT meant to be called
+  //   recursively: call them only at initial suspend, and actual resume).
+  // - Note: Resume flushes the queue and invokes any pending callbacks
+  //   immediately--caller must arrange any needed asynchronicity vis a vis
+  //   the channel's own Resume() method.
+  inline void Suspend();
+  inline void Resume();
+
+ private:
+  inline void MaybeFlushQueue();
+  void FlushQueue();
 
   nsTArray<nsAutoPtr<ChannelEvent> > mEventQueue;
-  enum {
-    PHASE_UNQUEUED,
-    PHASE_QUEUEING,
-    PHASE_FINISHED_QUEUEING,
-    PHASE_FLUSHING
-  } mQueuePhase;
+
+  bool mForced;
+  bool mSuspended;
+  bool mFlushing;
 
-  typedef AutoEventEnqueuerBase<T> AutoEventEnqueuer;
+  // Keep ptr to avoid refcount cycle: only grab ref during flushing.
+  nsIChannel *mOwner;
 
- private:
-  T* mSelf;
-
-  friend class AutoEventEnqueuerBase<T>;
+  friend class AutoEventEnqueuer;
 };
 
-template<class T> inline void
-ChannelEventQueue<T>::BeginEventQueueing()
+inline bool
+ChannelEventQueue::ShouldEnqueue()
 {
-  if (mQueuePhase != PHASE_UNQUEUED)
-    return;
-  // Store incoming IPDL messages for later.
-  mQueuePhase = PHASE_QUEUEING;
+  bool answer =  mForced || mSuspended || mFlushing;
+
+  NS_ABORT_IF_FALSE(answer == true || mEventQueue.IsEmpty(),
+                    "Should always enqueue if ChannelEventQueue not empty");
+
+  return answer;
 }
 
-template<class T> inline void
-ChannelEventQueue<T>::EndEventQueueing()
-{
-  if (mQueuePhase != PHASE_QUEUEING)
-    return;
-
-  mQueuePhase = PHASE_FINISHED_QUEUEING;
-}
-
-template<class T> inline bool
-ChannelEventQueue<T>::ShouldEnqueue()
-{
-  return mQueuePhase != PHASE_UNQUEUED || mSelf->IsSuspended();
-}
-
-template<class T> inline void
-ChannelEventQueue<T>::EnqueueEvent(ChannelEvent* callback)
+inline void
+ChannelEventQueue::Enqueue(ChannelEvent* callback)
 {
   mEventQueue.AppendElement(callback);
 }
 
-template<class T> void
-ChannelEventQueue<T>::FlushEventQueue()
+inline void
+ChannelEventQueue::StartForcedQueueing()
+{
+  mForced = true;
+}
+
+inline void
+ChannelEventQueue::EndForcedQueueing()
 {
-  NS_ABORT_IF_FALSE(mQueuePhase != PHASE_UNQUEUED,
-                    "Queue flushing should not occur if PHASE_UNQUEUED");
-  
-  // Queue already being flushed
-  if (mQueuePhase != PHASE_FINISHED_QUEUEING || mSelf->IsSuspended())
-    return;
-  
-  nsRefPtr<T> kungFuDeathGrip(mSelf);
-  if (mEventQueue.Length() > 0) {
-    // It is possible for new callbacks to be enqueued as we are
-    // flushing the queue, so the queue must not be cleared until
-    // all callbacks have run.
-    mQueuePhase = PHASE_FLUSHING;
-    
-    PRUint32 i;
-    for (i = 0; i < mEventQueue.Length(); i++) {
-      mEventQueue[i]->Run();
-      if (mSelf->IsSuspended())
-        break;
-    }
+  mForced = false;
+  MaybeFlushQueue();
+}
 
-    // We will always want to remove at least one finished callback.
-    if (i < mEventQueue.Length())
-      i++;
+inline void
+ChannelEventQueue::Suspend()
+{
+  NS_ABORT_IF_FALSE(!mSuspended,
+                    "ChannelEventQueue::Suspend called recursively");
 
-    mEventQueue.RemoveElementsAt(0, i);
-  }
-
-  if (mSelf->IsSuspended())
-    mQueuePhase = PHASE_QUEUEING;
-  else
-    mQueuePhase = PHASE_UNQUEUED;
+  mSuspended = true;
 }
 
-// Ensures any incoming IPDL msgs are queued during its lifetime, and flushes
-// the queue when it goes out of scope.
-template<class T>
-class AutoEventEnqueuerBase
+inline void
+ChannelEventQueue::Resume()
+{
+  NS_ABORT_IF_FALSE(mSuspended,
+                    "ChannelEventQueue::Resume called when not suspended!");
+
+  mSuspended = false;
+  MaybeFlushQueue();
+}
+
+inline void
+ChannelEventQueue::MaybeFlushQueue()
+{
+  // Don't flush if forced queuing on, we're already being flushed, or
+  // suspended, or there's nothing to flush
+  if (!mForced && !mFlushing && !mSuspended && !mEventQueue.IsEmpty())
+    FlushQueue();
+}
+
+// Ensures that ShouldEnqueue() will be true during its lifetime (letting
+// caller know incoming IPDL msgs should be queued). Flushes the queue when it
+// goes out of scope.
+class AutoEventEnqueuer
 {
  public:
-  AutoEventEnqueuerBase(ChannelEventQueue<T>* queue) : mEventQueue(queue) 
-  {
-    mEventQueue->BeginEventQueueing();
+  AutoEventEnqueuer(ChannelEventQueue &queue) : mEventQueue(queue) {
+    mEventQueue.StartForcedQueueing();
   }
-  ~AutoEventEnqueuerBase() 
-  { 
-    mEventQueue->EndEventQueueing();
-    mEventQueue->FlushEventQueue(); 
+  ~AutoEventEnqueuer() {
+    mEventQueue.EndForcedQueueing();
   }
  private:
-  ChannelEventQueue<T> *mEventQueue;
+  ChannelEventQueue &mEventQueue;
 };
 
 }
 }
 
 #endif
--- a/netwerk/ipc/Makefile.in
+++ b/netwerk/ipc/Makefile.in
@@ -56,16 +56,17 @@ EXPORTS_mozilla/net = \
   NeckoCommon.h       \
   NeckoMessageUtils.h \
   ChannelEventQueue.h \
   $(NULL)
 
 CPPSRCS =               \
   NeckoChild.cpp        \
   NeckoParent.cpp       \
+  ChannelEventQueue.cpp \
   $(NULL)
 
 LOCAL_INCLUDES +=                  \
   -I$(srcdir)/../protocol/http \
   -I$(srcdir)/../base/src          \
   $(NULL)
 
 include $(topsrcdir)/config/config.mk
--- a/netwerk/protocol/ftp/FTPChannelChild.cpp
+++ b/netwerk/protocol/ftp/FTPChannelChild.cpp
@@ -51,18 +51,18 @@
 
 #undef LOG
 #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
 
 namespace mozilla {
 namespace net {
 
 FTPChannelChild::FTPChannelChild(nsIURI* uri)
-: ChannelEventQueue<FTPChannelChild>(this)
-, mIPCOpen(false)
+: mIPCOpen(false)
+, mEventQ(this)
 , mCanceled(false)
 , mSuspendCount(0)
 , mIsPending(PR_FALSE)
 , mWasOpened(PR_FALSE)
 , mLastModifiedTime(0)
 , mStartPos(0)
 {
   LOG(("Creating FTPChannelChild @%x\n", this));
@@ -246,19 +246,19 @@ class FTPStartRequestEvent : public Chan
 
 bool
 FTPChannelChild::RecvOnStartRequest(const PRInt32& aContentLength,
                                     const nsCString& aContentType,
                                     const PRTime& aLastModified,
                                     const nsCString& aEntityID,
                                     const IPC::URI& aURI)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FTPStartRequestEvent(this, aContentLength, aContentType,
-                                          aLastModified, aEntityID, aURI));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FTPStartRequestEvent(this, aContentLength, aContentType,
+                                             aLastModified, aEntityID, aURI));
   } else {
     DoOnStartRequest(aContentLength, aContentType, aLastModified,
                      aEntityID, aURI);
   }
   return true;
 }
 
 void
@@ -275,17 +275,17 @@ FTPChannelChild::DoOnStartRequest(const 
   mLastModifiedTime = aLastModified;
   mEntityID = aEntityID;
 
   nsCString spec;
   nsCOMPtr<nsIURI> uri(aURI);
   uri->GetSpec(spec);
   nsBaseChannel::URI()->SetSpec(spec);
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   nsresult rv = mListener->OnStartRequest(this, mListenerContext);
   if (NS_FAILED(rv))
     Cancel(rv);
 }
 
 class FTPDataAvailableEvent : public ChannelEvent
 {
  public:
@@ -299,18 +299,18 @@ class FTPDataAvailableEvent : public Cha
   PRUint32 mOffset, mCount;
 };
 
 bool
 FTPChannelChild::RecvOnDataAvailable(const nsCString& data,
                                      const PRUint32& offset,
                                      const PRUint32& count)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FTPDataAvailableEvent(this, data, offset, count));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FTPDataAvailableEvent(this, data, offset, count));
   } else {
     DoOnDataAvailable(data, offset, count);
   }
   return true;
 }
 
 void
 FTPChannelChild::DoOnDataAvailable(const nsCString& data,
@@ -332,17 +332,17 @@ FTPChannelChild::DoOnDataAvailable(const
                                       data.get(),
                                       count,
                                       NS_ASSIGNMENT_DEPEND);
   if (NS_FAILED(rv)) {
     Cancel(rv);
     return;
   }
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   rv = mListener->OnDataAvailable(this, mListenerContext,
                                   stringStream, offset, count);
   if (NS_FAILED(rv))
     Cancel(rv);
   stringStream->Close();
 }
 
 class FTPStopRequestEvent : public ChannelEvent
@@ -354,18 +354,18 @@ class FTPStopRequestEvent : public Chann
  private:
   FTPChannelChild* mChild;
   nsresult mStatusCode;
 };
 
 bool
 FTPChannelChild::RecvOnStopRequest(const nsresult& statusCode)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FTPStopRequestEvent(this, statusCode));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FTPStopRequestEvent(this, statusCode));
   } else {
     DoOnStopRequest(statusCode);
   }
   return true;
 }
 
 void
 FTPChannelChild::DoOnStopRequest(const nsresult& statusCode)
@@ -374,17 +374,17 @@ FTPChannelChild::DoOnStopRequest(const n
            this, statusCode));
 
   if (!mCanceled)
     mStatus = statusCode;
 
   { // Ensure that all queued ipdl events are dispatched before
     // we initiate protocol deletion below.
     mIsPending = PR_FALSE;
-    AutoEventEnqueuer ensureSerialDispatch(this);
+    AutoEventEnqueuer ensureSerialDispatch(mEventQ);
     (void)mListener->OnStopRequest(this, mListenerContext, statusCode);
     mListener = nsnull;
     mListenerContext = nsnull;
 
     if (mLoadGroup)
       mLoadGroup->RemoveRequest(this, nsnull, statusCode);
   }
 
@@ -402,18 +402,18 @@ class FTPCancelEarlyEvent : public Chann
  private:
   FTPChannelChild* mChild;
   nsresult mStatus;
 };
 
 bool
 FTPChannelChild::RecvCancelEarly(const nsresult& statusCode)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FTPCancelEarlyEvent(this, statusCode));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FTPCancelEarlyEvent(this, statusCode));
   } else {
     DoCancelEarly(statusCode);
   }
   return true;
 }
 
 void
 FTPChannelChild::DoCancelEarly(const nsresult& statusCode)
@@ -448,18 +448,18 @@ class FTPDeleteSelfEvent : public Channe
   void Run() { mChild->DoDeleteSelf(); }
  private:
   FTPChannelChild* mChild;
 };
 
 bool
 FTPChannelChild::RecvDeleteSelf()
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FTPDeleteSelfEvent(this));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FTPDeleteSelfEvent(this));
   } else {
     DoDeleteSelf();
   }
   return true;
 }
 
 void
 FTPChannelChild::DoDeleteSelf()
@@ -480,31 +480,31 @@ FTPChannelChild::Cancel(nsresult status)
     SendCancel(status);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FTPChannelChild::Suspend()
 {
   NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
-  mSuspendCount++;
-  SendSuspend();
+  if (!mSuspendCount++) {
+    SendSuspend();
+    mEventQ.Suspend();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 FTPChannelChild::Resume()
 {
   NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
-  SendResume();
-  --mSuspendCount;
-  if (!mSuspendCount) {
-    if (mQueuePhase == PHASE_UNQUEUED)
-      mQueuePhase = PHASE_FINISHED_QUEUEING;
-    FlushEventQueue();
+
+  if (!--mSuspendCount) {
+    SendResume();
+    mEventQ.Resume();    // TODO: make this async: see HttpChannelChild::Resume
   }
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // FTPChannelChild::nsIChildChannel
 //-----------------------------------------------------------------------------
 
--- a/netwerk/protocol/ftp/FTPChannelChild.h
+++ b/netwerk/protocol/ftp/FTPChannelChild.h
@@ -63,17 +63,16 @@ namespace net {
 
 class FTPChannelChild : public PFTPChannelChild
                       , public nsBaseChannel
                       , public nsIFTPChannel
                       , public nsIUploadChannel
                       , public nsIResumableChannel
                       , public nsIProxiedChannel
                       , public nsIChildChannel
-                      , public ChannelEventQueue<FTPChannelChild>
 {
 public:
   typedef ::nsIStreamListener nsIStreamListener;
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIFTPCHANNEL
   NS_DECL_NSIUPLOADCHANNEL
   NS_DECL_NSIRESUMABLECHANNEL
@@ -132,16 +131,17 @@ protected:
   friend class FTPStopRequestEvent;
   friend class FTPCancelEarlyEvent;
   friend class FTPDeleteSelfEvent;
 
 private:
   nsCOMPtr<nsIInputStream> mUploadStream;
 
   bool mIPCOpen;
+  ChannelEventQueue mEventQ;
   bool mCanceled;
   PRUint32 mSuspendCount;
   PRPackedBool mIsPending;
   PRPackedBool mWasOpened;
   
   PRTime mLastModifiedTime;
   PRUint64 mStartPos;
   nsCString mEntityID;
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -55,23 +55,23 @@ namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild
 //-----------------------------------------------------------------------------
 
 HttpChannelChild::HttpChannelChild()
   : HttpAsyncAborter<HttpChannelChild>(this)
-  , ChannelEventQueue<HttpChannelChild>(this)
   , mIsFromCache(PR_FALSE)
   , mCacheEntryAvailable(PR_FALSE)
   , mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
   , mSendResumeAt(false)
   , mIPCOpen(false)
   , mKeptAlive(false)
+  , mEventQ(this)
 {
   LOG(("Creating HttpChannelChild @%x\n", this));
 }
 
 HttpChannelChild::~HttpChannelChild()
 {
   LOG(("Destroying HttpChannelChild @%x\n", this));
 }
@@ -214,23 +214,23 @@ HttpChannelChild::RecvOnStartRequest(con
                                      const PRBool& isFromCache,
                                      const PRBool& cacheEntryAvailable,
                                      const PRUint32& cacheExpirationTime,
                                      const nsCString& cachedCharset,
                                      const nsCString& securityInfoSerialization,
                                      const PRNetAddr& selfAddr,
                                      const PRNetAddr& peerAddr)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new StartRequestEvent(this, responseHead, useResponseHead,
-                                       requestHeaders,
-                                       isFromCache, cacheEntryAvailable,
-                                       cacheExpirationTime, cachedCharset,
-                                       securityInfoSerialization, selfAddr,
-                                       peerAddr));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new StartRequestEvent(this, responseHead, useResponseHead,
+                                          requestHeaders, isFromCache,
+                                          cacheEntryAvailable,
+                                          cacheExpirationTime, cachedCharset,
+                                          securityInfoSerialization, selfAddr,
+                                          peerAddr));
   } else {
     OnStartRequest(responseHead, useResponseHead, requestHeaders, isFromCache,
                    cacheEntryAvailable, cacheExpirationTime, cachedCharset,
                    securityInfoSerialization, selfAddr, peerAddr);
   }
   return true;
 }
 
@@ -250,23 +250,23 @@ HttpChannelChild::OnStartRequest(const n
 
   if (useResponseHead && !mCanceled)
     mResponseHead = new nsHttpResponseHead(responseHead);
 
   if (!securityInfoSerialization.IsEmpty()) {
     NS_DeserializeObject(securityInfoSerialization, 
                          getter_AddRefs(mSecurityInfo));
   }
- 
+
   mIsFromCache = isFromCache;
   mCacheEntryAvailable = cacheEntryAvailable;
   mCacheExpirationTime = cacheExpirationTime;
   mCachedCharset = cachedCharset;
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   // replace our request headers with what actually got sent in the parent
   mRequestHead.ClearHeaders();
   for (PRUint32 i = 0; i < requestHeaders.Length(); i++) {
     mRequestHead.Headers().SetHeader(nsHttp::ResolveAtom(requestHeaders[i].mHeader),
                                      requestHeaders[i].mValue);
   }
 
@@ -324,19 +324,20 @@ class TransportAndDataEvent : public Cha
 bool
 HttpChannelChild::RecvOnTransportAndData(const nsresult& status,
                                          const PRUint64& progress,
                                          const PRUint64& progressMax,
                                          const nsCString& data,
                                          const PRUint32& offset,
                                          const PRUint32& count)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new TransportAndDataEvent(this, status, progress, progressMax,
-                                           data, offset, count));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new TransportAndDataEvent(this, status, progress,
+                                              progressMax, data, offset, 
+                                              count));
   } else {
     OnTransportAndData(status, progress, progressMax, data, offset, count);
   }
   return true;
 }
 
 void
 HttpChannelChild::OnTransportAndData(const nsresult& status,
@@ -352,17 +353,17 @@ HttpChannelChild::OnTransportAndData(con
     return;
 
   // cache the progress sink so we don't have to query for it each time.
   if (!mProgressSink)
     GetCallback(mProgressSink);
 
   // Hold queue lock throughout all three calls, else we might process a later
   // necko msg in between them.
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   // block status/progress after Cancel or OnStopRequest has been called,
   // or if channel has LOAD_BACKGROUND set.
   // - JDUELL: may not need mStatus/mIsPending checks, given this is always called
   //   during OnDataAvailable, and we've already checked mCanceled.  Code
   //   dupe'd from nsHttpChannel
   if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
       !(mLoadFlags & LOAD_BACKGROUND))
@@ -420,18 +421,18 @@ class StopRequestEvent : public ChannelE
  private:
   HttpChannelChild* mChild;
   nsresult mStatusCode;
 };
 
 bool 
 HttpChannelChild::RecvOnStopRequest(const nsresult& statusCode)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new StopRequestEvent(this, statusCode));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new StopRequestEvent(this, statusCode));
   } else {
     OnStopRequest(statusCode);
   }
   return true;
 }
 
 void 
 HttpChannelChild::OnStopRequest(const nsresult& statusCode)
@@ -442,17 +443,17 @@ HttpChannelChild::OnStopRequest(const ns
   mIsPending = PR_FALSE;
 
   if (!mCanceled && NS_SUCCEEDED(mStatus))
     mStatus = statusCode;
 
   { // We must flush the queue before we Send__delete__
     // (although we really shouldn't receive any msgs after OnStop),
     // so make sure this goes out of scope before then.
-    AutoEventEnqueuer ensureSerialDispatch(this);
+    AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
     mListener->OnStopRequest(this, mListenerContext, mStatus);
 
     mListener = 0;
     mListenerContext = 0;
     mCacheEntryAvailable = PR_FALSE;
     if (mLoadGroup)
       mLoadGroup->RemoveRequest(this, nsnull, mStatus);
@@ -485,18 +486,18 @@ class ProgressEvent : public ChannelEven
   HttpChannelChild* mChild;
   PRUint64 mProgress, mProgressMax;
 };
 
 bool
 HttpChannelChild::RecvOnProgress(const PRUint64& progress,
                                  const PRUint64& progressMax)
 {
-  if (ShouldEnqueue())  {
-    EnqueueEvent(new ProgressEvent(this, progress, progressMax));
+  if (mEventQ.ShouldEnqueue())  {
+    mEventQ.Enqueue(new ProgressEvent(this, progress, progressMax));
   } else {
     OnProgress(progress, progressMax);
   }
   return true;
 }
 
 void
 HttpChannelChild::OnProgress(const PRUint64& progress,
@@ -507,17 +508,17 @@ HttpChannelChild::OnProgress(const PRUin
 
   if (mCanceled)
     return;
 
   // cache the progress sink so we don't have to query for it each time.
   if (!mProgressSink)
     GetCallback(mProgressSink);
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   // block socket status event after Cancel or OnStopRequest has been called,
   // or if channel has LOAD_BACKGROUND set
   if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && 
       !(mLoadFlags & LOAD_BACKGROUND)) 
   {
     if (progress > 0) {
       NS_ASSERTION(progress <= progressMax, "unexpected progress values");
@@ -538,18 +539,18 @@ class StatusEvent : public ChannelEvent
  private:
   HttpChannelChild* mChild;
   nsresult mStatus;
 };
 
 bool
 HttpChannelChild::RecvOnStatus(const nsresult& status)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new StatusEvent(this, status));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new StatusEvent(this, status));
   } else {
     OnStatus(status);
   }
   return true;
 }
 
 void
 HttpChannelChild::OnStatus(const nsresult& status)
@@ -558,17 +559,17 @@ HttpChannelChild::OnStatus(const nsresul
 
   if (mCanceled)
     return;
 
   // cache the progress sink so we don't have to query for it each time.
   if (!mProgressSink)
     GetCallback(mProgressSink);
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   // block socket status event after Cancel or OnStopRequest has been called,
   // or if channel has LOAD_BACKGROUND set
   if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && 
       !(mLoadFlags & LOAD_BACKGROUND)) 
   {
     nsCAutoString host;
     mURI->GetHost(host);
@@ -588,18 +589,18 @@ class FailedAsyncOpenEvent : public Chan
  private:
   HttpChannelChild* mChild;
   nsresult mStatus;
 };
 
 bool
 HttpChannelChild::RecvFailedAsyncOpen(const nsresult& status)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new FailedAsyncOpenEvent(this, status));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new FailedAsyncOpenEvent(this, status));
   } else {
     FailedAsyncOpen(status);
   }
   return true;
 }
 
 // We need to have an implementation of this function just so that we can keep
 // all references to mCallOnResume of type HttpChannelChild:  it's not OK in C++
@@ -635,18 +636,18 @@ class DeleteSelfEvent : public ChannelEv
   void Run() { mChild->DeleteSelf(); }
  private:
   HttpChannelChild* mChild;
 };
 
 bool
 HttpChannelChild::RecvDeleteSelf()
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new DeleteSelfEvent(this));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new DeleteSelfEvent(this));
   } else {
     DeleteSelf();
   }
   return true;
 }
 
 void
 HttpChannelChild::DeleteSelf()
@@ -682,19 +683,19 @@ class Redirect1Event : public ChannelEve
 };
 
 bool
 HttpChannelChild::RecvRedirect1Begin(const PRUint32& newChannelId,
                                      const URI& newUri,
                                      const PRUint32& redirectFlags,
                                      const nsHttpResponseHead& responseHead)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new Redirect1Event(this, newChannelId, newUri, redirectFlags,
-                                    responseHead)); 
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new Redirect1Event(this, newChannelId, newUri,
+                                       redirectFlags, responseHead));
   } else {
     Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead);
   }
   return true;
 }
 
 void
 HttpChannelChild::Redirect1Begin(const PRUint32& newChannelId,
@@ -755,18 +756,18 @@ class Redirect3Event : public ChannelEve
   void Run() { mChild->Redirect3Complete(); }
  private:
   HttpChannelChild* mChild;
 };
 
 bool
 HttpChannelChild::RecvRedirect3Complete()
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new Redirect3Event(this));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new Redirect3Event(this));
   } else {
     Redirect3Complete();
   }
   return true;
 }
 
 void
 HttpChannelChild::Redirect3Complete()
@@ -900,49 +901,46 @@ HttpChannelChild::Cancel(nsresult status
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpChannelChild::Suspend()
 {
   NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
-  SendSuspend();
-  mSuspendCount++;
+  if (!mSuspendCount++) {
+    SendSuspend();
+    mEventQ.Suspend();
+  }
   return NS_OK;
 }
 
 void
 HttpChannelChild::CompleteResume()
 {
   if (mCallOnResume) {
     (this->*mCallOnResume)();
     mCallOnResume = 0;
   }
 
-  FlushEventQueue();
+  // Don't resume event queue until now, else queued events could get
+  // flushed/called before mCallOnResume, which needs to run first.
+  mEventQ.Resume();
 }
 
 NS_IMETHODIMP
 HttpChannelChild::Resume()
 {
   NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
 
   nsresult rv = NS_OK;
 
-  SendResume();
-  mSuspendCount--;
-  if (!mSuspendCount) {
-    // If we were suspended outside of an event handler (bug 595972) we'll
-    // consider ourselves unqueued. This is a legal state of affairs but
-    // FlushEventQueue() can't easily ensure this fact, so we'll do some
-    // fudging to set the invariants correctly.
-    if (mQueuePhase == PHASE_UNQUEUED)
-      mQueuePhase = PHASE_FINISHED_QUEUEING;
+  if (!--mSuspendCount) {
+    SendResume();
     rv = AsyncCall(&HttpChannelChild::CompleteResume);
   }
   return rv;
 }
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIChannel
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -71,17 +71,16 @@ class HttpChannelChild : public PHttpCha
                        , public HttpAsyncAborter<HttpChannelChild>
                        , public nsICacheInfoChannel
                        , public nsIProxiedChannel
                        , public nsIApplicationCacheChannel
                        , public nsIAsyncVerifyRedirectCallback
                        , public nsIAssociatedContentSecurity
                        , public nsIChildChannel
                        , public nsIHttpChannelChild
-                       , public ChannelEventQueue<HttpChannelChild>
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICACHEINFOCHANNEL
   NS_DECL_NSIPROXIEDCHANNEL
   NS_DECL_NSIAPPLICATIONCACHECONTAINER
   NS_DECL_NSIAPPLICATIONCACHECHANNEL
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
@@ -168,16 +167,17 @@ private:
   PRUint32     mCacheExpirationTime;
   nsCString    mCachedCharset;
 
   // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
   bool mSendResumeAt;
 
   bool mIPCOpen;
   bool mKeptAlive;
+  ChannelEventQueue mEventQ;
 
   void OnStartRequest(const nsHttpResponseHead& responseHead,
                       const PRBool& useResponseHead,
                       const RequestHeaderTuples& requestHeaders,
                       const PRBool& isFromCache,
                       const PRBool& cacheEntryAvailable,
                       const PRUint32& cacheExpirationTime,
                       const nsCString& cachedCharset,
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.cpp
@@ -52,25 +52,25 @@ namespace net {
 
 NS_IMPL_ISUPPORTS3(WyciwygChannelChild,
                    nsIRequest,
                    nsIChannel,
                    nsIWyciwygChannel);
 
 
 WyciwygChannelChild::WyciwygChannelChild()
-  : ChannelEventQueue<WyciwygChannelChild>(this)
-  , mStatus(NS_OK)
+  : mStatus(NS_OK)
   , mIsPending(PR_FALSE)
   , mCanceled(false)
   , mLoadFlags(LOAD_NORMAL)
   , mContentLength(-1)
   , mCharsetSource(kCharsetUninitialized)
   , mState(WCC_NEW)
   , mIPCOpen(false)
+  , mEventQ(this)
 {
   LOG(("Creating WyciwygChannelChild @%x\n", this));
 }
 
 WyciwygChannelChild::~WyciwygChannelChild()
 {
   LOG(("Destroying WyciwygChannelChild @%x\n", this));
 }
@@ -133,19 +133,20 @@ private:
 
 bool
 WyciwygChannelChild::RecvOnStartRequest(const nsresult& statusCode,
                                         const PRInt32& contentLength,
                                         const PRInt32& source,
                                         const nsCString& charset,
                                         const nsCString& securityInfo)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new WyciwygStartRequestEvent(this, statusCode, contentLength,
-                                              source, charset, securityInfo));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new WyciwygStartRequestEvent(this, statusCode,
+                                                 contentLength, source,
+                                                 charset, securityInfo));
   } else {
     OnStartRequest(statusCode, contentLength, source, charset, securityInfo);
   }
   return true;
 }
 
 void
 WyciwygChannelChild::OnStartRequest(const nsresult& statusCode,
@@ -162,17 +163,17 @@ WyciwygChannelChild::OnStartRequest(cons
   mContentLength = contentLength;
   mCharsetSource = source;
   mCharset = charset;
 
   if (!securityInfo.IsEmpty()) {
     NS_DeserializeObject(securityInfo, getter_AddRefs(mSecurityInfo));
   }
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   nsresult rv = mListener->OnStartRequest(this, mListenerContext);
   if (NS_FAILED(rv))
     Cancel(rv);
 }
 
 class WyciwygDataAvailableEvent : public ChannelEvent
 {
@@ -187,18 +188,18 @@ private:
   nsCString mData;
   PRUint32 mOffset;
 };
 
 bool
 WyciwygChannelChild::RecvOnDataAvailable(const nsCString& data,
                                          const PRUint32& offset)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new WyciwygDataAvailableEvent(this, data, offset));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new WyciwygDataAvailableEvent(this, data, offset));
   } else {
     OnDataAvailable(data, offset);
   }
   return true;
 }
 
 void
 WyciwygChannelChild::OnDataAvailable(const nsCString& data,
@@ -221,17 +222,17 @@ WyciwygChannelChild::OnDataAvailable(con
                                       data.get(),
                                       data.Length(),
                                       NS_ASSIGNMENT_DEPEND);
   if (NS_FAILED(rv)) {
     Cancel(rv);
     return;
   }
 
-  AutoEventEnqueuer ensureSerialDispatch(this);
+  AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   
   rv = mListener->OnDataAvailable(this, mListenerContext,
                                   stringStream, offset, data.Length());
   if (NS_FAILED(rv))
     Cancel(rv);
 
   if (mProgressSink && NS_SUCCEEDED(rv) && !(mLoadFlags & LOAD_BACKGROUND))
     mProgressSink->OnProgress(this, nsnull, PRUint64(offset + data.Length()),
@@ -248,34 +249,34 @@ public:
 private:
   WyciwygChannelChild* mChild;
   nsresult mStatusCode;
 };
 
 bool
 WyciwygChannelChild::RecvOnStopRequest(const nsresult& statusCode)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new WyciwygStopRequestEvent(this, statusCode));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new WyciwygStopRequestEvent(this, statusCode));
   } else {
     OnStopRequest(statusCode);
   }
   return true;
 }
 
 void
 WyciwygChannelChild::OnStopRequest(const nsresult& statusCode)
 {
   LOG(("WyciwygChannelChild::RecvOnStopRequest [this=%x status=%u]\n",
            this, statusCode));
 
   { // We need to ensure that all IPDL message dispatching occurs
     // before we delete the protocol below
-    AutoEventEnqueuer ensureSerialDispatch(this);
-    
+    AutoEventEnqueuer ensureSerialDispatch(mEventQ);
+
     mState = WCC_ONSTOP;
 
     mIsPending = PR_FALSE;
 
     if (!mCanceled)
       mStatus = statusCode;
 
     mListener->OnStopRequest(this, mListenerContext, statusCode);
@@ -305,18 +306,18 @@ class WyciwygCancelEvent : public Channe
  private:
   WyciwygChannelChild* mChild;
   nsresult mStatus;
 };
 
 bool
 WyciwygChannelChild::RecvCancelEarly(const nsresult& statusCode)
 {
-  if (ShouldEnqueue()) {
-    EnqueueEvent(new WyciwygCancelEvent(this, statusCode));
+  if (mEventQ.ShouldEnqueue()) {
+    mEventQ.Enqueue(new WyciwygCancelEvent(this, statusCode));
   } else {
     CancelEarly(statusCode);
   }
   return true;
 }
 
 void WyciwygChannelChild::CancelEarly(const nsresult& statusCode)
 {
--- a/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
+++ b/netwerk/protocol/wyciwyg/WyciwygChannelChild.h
@@ -62,17 +62,16 @@ enum WyciwygChannelChildState {
   WCC_ONWRITE,
   WCC_ONCLOSED
 };
 
 
 // Header file contents
 class WyciwygChannelChild : public PWyciwygChannelChild
                           , public nsIWyciwygChannel
-                          , public ChannelEventQueue<WyciwygChannelChild>
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUEST
   NS_DECL_NSICHANNEL
   NS_DECL_NSIWYCIWYGCHANNEL
 
   WyciwygChannelChild();
@@ -123,16 +122,17 @@ private:
   nsCOMPtr<nsIStreamListener>       mListener;
   nsCOMPtr<nsISupports>             mListenerContext;
   nsCOMPtr<nsISupports>             mSecurityInfo;
 
   // FIXME: replace with IPDL states (bug 536319)
   enum WyciwygChannelChildState mState;
 
   bool mIPCOpen;
+  ChannelEventQueue mEventQ;
 
   friend class WyciwygStartRequestEvent;
   friend class WyciwygDataAvailableEvent;
   friend class WyciwygStopRequestEvent;
   friend class WyciwygCancelEvent;
 };
 
 inline bool
--- a/netwerk/test/unit/test_httpsuspend.js
+++ b/netwerk/test/unit/test_httpsuspend.js
@@ -20,25 +20,29 @@ var listener = {
 
   onStartRequest: function(request, ctx) {
     this._lastEvent = Date.now();
     request.QueryInterface(Ci.nsIRequest);
 
     // Insert a delay between this and the next callback to ensure message buffering
     // works correctly
     request.suspend();
+    request.suspend();
     do_timeout(RESUME_DELAY, function() request.resume());
+    do_timeout(RESUME_DELAY + 1000, function() request.resume());
   },
 
   onDataAvailable: function(request, context, stream, offset, count) {
     do_check_true(Date.now() - this._lastEvent >= MIN_TIME_DIFFERENCE);
     read_stream(stream, count);
 
     // Ensure that suspending and resuming inside a callback works correctly
     request.suspend();
+    request.suspend();
+    request.resume();
     request.resume();
 
     this._gotData = true;
   },
 
   onStopRequest: function(request, ctx, status) {
     do_check_true(this._gotData);
     httpserv.stop(do_test_finished);