Bug 536289 - Generalize IPDL event queue some more. r=jduell a=blocking-fennec
authorJosh Matthews <josh@joshmatthews.net>
Sat, 16 Oct 2010 01:26:14 -0400
changeset 56089 fe519f659d606e728a29fcd20b98cb2a49837462
parent 56088 0e4c3af3ac59797c231eaca4962616fa39807bf9
child 56090 c56bcfa6defbb51a722681fe6647454c0a13078a
push idunknown
push userunknown
push dateunknown
reviewersjduell, blocking-fennec
bugs536289
milestone2.0b8pre
Bug 536289 - Generalize IPDL event queue some more. r=jduell a=blocking-fennec
netwerk/ipc/ChannelEventQueue.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
--- a/netwerk/ipc/ChannelEventQueue.h
+++ b/netwerk/ipc/ChannelEventQueue.h
@@ -53,87 +53,131 @@ class ChannelEvent
 };
 
 // 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.
 
+template<class T> class AutoEventEnqueuerBase;
+
+template<class T>
 class ChannelEventQueue
 {
  public:
-  ChannelEventQueue() : mQueuePhase(PHASE_UNQUEUED) {}
+  ChannelEventQueue(T* self) : mQueuePhase(PHASE_UNQUEUED)
+                             , mSelf(self) {}
   ~ChannelEventQueue() {}
   
  protected:
   void BeginEventQueueing();
   void EndEventQueueing();
   void EnqueueEvent(ChannelEvent* callback);
   bool ShouldEnqueue();
-
-  // Consumers must implement their own flushing routine, as there are too many
-  // implementation-specific details to generalize easily.
-  virtual void FlushEventQueue() = 0;
+  void FlushEventQueue();
 
   nsTArray<nsAutoPtr<ChannelEvent> > mEventQueue;
   enum {
     PHASE_UNQUEUED,
     PHASE_QUEUEING,
     PHASE_FINISHED_QUEUEING,
     PHASE_FLUSHING
   } mQueuePhase;
 
-  friend class AutoEventEnqueuer;
+  typedef AutoEventEnqueuerBase<T> AutoEventEnqueuer;
+
+ private:
+  T* mSelf;
+
+  friend class AutoEventEnqueuerBase<T>;
 };
 
-inline void
-ChannelEventQueue::BeginEventQueueing()
+template<class T> inline void
+ChannelEventQueue<T>::BeginEventQueueing()
 {
   if (mQueuePhase != PHASE_UNQUEUED)
     return;
   // Store incoming IPDL messages for later.
   mQueuePhase = PHASE_QUEUEING;
 }
 
-inline void
-ChannelEventQueue::EndEventQueueing()
+template<class T> inline void
+ChannelEventQueue<T>::EndEventQueueing()
 {
   if (mQueuePhase != PHASE_QUEUEING)
     return;
 
   mQueuePhase = PHASE_FINISHED_QUEUEING;
 }
 
-inline bool
-ChannelEventQueue::ShouldEnqueue()
+template<class T> inline bool
+ChannelEventQueue<T>::ShouldEnqueue()
 {
-  return mQueuePhase != PHASE_UNQUEUED;
+  return mQueuePhase != PHASE_UNQUEUED || mSelf->IsSuspended();
 }
 
-inline void
-ChannelEventQueue::EnqueueEvent(ChannelEvent* callback)
+template<class T> inline void
+ChannelEventQueue<T>::EnqueueEvent(ChannelEvent* callback)
 {
   mEventQueue.AppendElement(callback);
 }
 
+template<class T> void
+ChannelEventQueue<T>::FlushEventQueue()
+{
+  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;
+    }
+
+    // We will always want to remove at least one finished callback.
+    if (i < mEventQueue.Length())
+      i++;
+
+    mEventQueue.RemoveElementsAt(0, i);
+  }
+
+  if (mSelf->IsSuspended())
+    mQueuePhase = PHASE_QUEUEING;
+  else
+    mQueuePhase = PHASE_UNQUEUED;
+}
+
 // Ensures any incoming IPDL msgs are queued during its lifetime, and flushes
 // the queue when it goes out of scope.
-class AutoEventEnqueuer 
+template<class T>
+class AutoEventEnqueuerBase
 {
  public:
-  AutoEventEnqueuer(ChannelEventQueue* queue) : mEventQueue(queue) 
+  AutoEventEnqueuerBase(ChannelEventQueue<T>* queue) : mEventQueue(queue) 
   {
     mEventQueue->BeginEventQueueing();
   }
-  ~AutoEventEnqueuer() 
+  ~AutoEventEnqueuerBase() 
   { 
     mEventQueue->EndEventQueueing();
     mEventQueue->FlushEventQueue(); 
   }
  private:
-  ChannelEventQueue *mEventQueue;
+  ChannelEventQueue<T> *mEventQueue;
 };
 
 }
 }
 
 #endif
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -54,17 +54,18 @@
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild
 //-----------------------------------------------------------------------------
 
 HttpChannelChild::HttpChannelChild()
-  : mIsFromCache(PR_FALSE)
+  : ChannelEventQueue<HttpChannelChild>(this)
+  , mIsFromCache(PR_FALSE)
   , mCacheEntryAvailable(PR_FALSE)
   , mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
   , mSendResumeAt(false)
   , mSuspendCount(0)
   , mIPCOpen(false)
   , mKeptAlive(false)
 {
   LOG(("Creating HttpChannelChild @%x\n", this));
@@ -136,53 +137,16 @@ HttpChannelChild::AddIPDLReference()
 void
 HttpChannelChild::ReleaseIPDLReference()
 {
   NS_ABORT_IF_FALSE(mIPCOpen, "Attempt to release nonexistent IPDL reference");
   mIPCOpen = false;
   Release();
 }
 
-void
-HttpChannelChild::FlushEventQueue()
-{
-  NS_ABORT_IF_FALSE(mQueuePhase != PHASE_UNQUEUED,
-                    "Queue flushing should not occur if PHASE_UNQUEUED");
-  
-  // Queue already being flushed, or the channel's suspended.
-  if (mQueuePhase != PHASE_FINISHED_QUEUEING || mSuspendCount)
-    return;
-  
-  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;
-    
-    nsRefPtr<HttpChannelChild> kungFuDeathGrip(this);
-    PRUint32 i;
-    for (i = 0; i < mEventQueue.Length(); i++) {
-      mEventQueue[i]->Run();
-      // If the callback ended up suspending us, abort all further flushing.
-      if (mSuspendCount)
-        break;
-    }
-    // We will always want to remove at least one finished callback.
-    if (i < mEventQueue.Length())
-      i++;
-
-    mEventQueue.RemoveElementsAt(0, i);
-  }
-
-  if (mSuspendCount)
-    mQueuePhase = PHASE_QUEUEING;
-  else
-    mQueuePhase = PHASE_UNQUEUED;
-}
-
 class StartRequestEvent : public ChannelEvent
 {
  public:
   StartRequestEvent(HttpChannelChild* child,
                     const nsHttpResponseHead& responseHead,
                     const PRBool& useResponseHead,
                     const RequestHeaderTuples& requestHeaders,
                     const PRBool& isFromCache,
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -60,27 +60,25 @@
 #include "nsIProxiedChannel.h"
 #include "nsITraceableChannel.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIAssociatedContentSecurity.h"
 
 namespace mozilla {
 namespace net {
 
-class ChildChannelEvent;
-
 class HttpChannelChild : public PHttpChannelChild
                        , public HttpBaseChannel
                        , public nsICacheInfoChannel
                        , public nsIProxiedChannel
                        , public nsITraceableChannel
                        , public nsIApplicationCacheChannel
                        , public nsIAsyncVerifyRedirectCallback
                        , public nsIAssociatedContentSecurity
-                       , public ChannelEventQueue
+                       , public ChannelEventQueue<HttpChannelChild>
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICACHEINFOCHANNEL
   NS_DECL_NSIPROXIEDCHANNEL
   NS_DECL_NSITRACEABLECHANNEL
   NS_DECL_NSIAPPLICATIONCACHECONTAINER
   NS_DECL_NSIAPPLICATIONCACHECHANNEL
@@ -115,16 +113,18 @@ public:
                                  nsISupports *aContext);
 
   // IPDL holds a reference while the PHttpChannel protocol is live (starting at
   // AsyncOpen, and ending at either OnStopRequest or any IPDL error, either of
   // which call NeckoChild::DeallocPHttpChannel()).
   void AddIPDLReference();
   void ReleaseIPDLReference();
 
+  bool IsSuspended();
+
 protected:
   bool RecvOnStartRequest(const nsHttpResponseHead& responseHead,
                           const PRBool& useResponseHead,
                           const RequestHeaderTuples& requestHeaders,
                           const PRBool& isFromCache,
                           const PRBool& cacheEntryAvailable,
                           const PRUint32& cacheExpirationTime,
                           const nsCString& cachedCharset,
@@ -159,19 +159,16 @@ private:
   // If ResumeAt is called before AsyncOpen, we need to send extra data upstream
   bool mSendResumeAt;
   // Current suspension depth for this channel object
   PRUint32 mSuspendCount;
 
   bool mIPCOpen;
   bool mKeptAlive;
 
-  void FlushEventQueue();
-  bool ShouldEnqueue();
-
   void OnStartRequest(const nsHttpResponseHead& responseHead,
                           const PRBool& useResponseHead,
                           const RequestHeaderTuples& requestHeaders,
                           const PRBool& isFromCache,
                           const PRBool& cacheEntryAvailable,
                           const PRUint32& cacheExpirationTime,
                           const nsCString& cachedCharset,
                           const nsCString& securityInfoSerialization);
@@ -183,34 +180,33 @@ private:
   void OnStatus(const nsresult& status, const nsString& statusArg);
   void OnCancel(const nsresult& status);
   void Redirect1Begin(PHttpChannelChild* newChannel, const URI& newURI,
                       const PRUint32& redirectFlags,
                       const nsHttpResponseHead& responseHead);
   void Redirect3Complete();
   void DeleteSelf();
 
-  friend class AutoEventEnqueuer;
   friend class StartRequestEvent;
   friend class StopRequestEvent;
   friend class DataAvailableEvent;
   friend class ProgressEvent;
   friend class StatusEvent;
   friend class CancelEvent;
   friend class Redirect1Event;
   friend class Redirect3Event;
   friend class DeleteSelfEvent;
 };
 
 //-----------------------------------------------------------------------------
 // inline functions
 //-----------------------------------------------------------------------------
 
 inline bool
-HttpChannelChild::ShouldEnqueue()
+HttpChannelChild::IsSuspended()
 {
-  return ChannelEventQueue::ShouldEnqueue() || mSuspendCount;
+  return mSuspendCount != 0;
 }
 
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_HttpChannelChild_h