Bug 1320744 - Part 2, implement nsIThreadRetargetableRequest in HttpChannelChild. draft
authorShih-Chiang Chien <schien@mozilla.com>
Thu, 29 Dec 2016 14:10:54 +0800
changeset 463437 01e1c15912b030375b3ea82ca726a723566a6ba6
parent 463436 d2ed3fc13b8fb7ffd81c942db9bb9969bab82bfc
child 542676 3b0584ff3468b66f5f09701905493a179985044b
push id42063
push userschien@mozilla.com
push dateThu, 19 Jan 2017 03:13:40 +0000
bugs1320744
milestone53.0a1
Bug 1320744 - Part 2, implement nsIThreadRetargetableRequest in HttpChannelChild. MozReview-Commit-ID: FyLXlkQde3h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelChild.h
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -15,16 +15,17 @@
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabGroup.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/HttpChannelChild.h"
 
+#include "nsCOMPtr.h"
 #include "nsISupportsPrimitives.h"
 #include "nsChannelClassifier.h"
 #include "nsGlobalWindow.h"
 #include "nsStringStream.h"
 #include "nsHttpHandler.h"
 #include "nsNetUtil.h"
 #include "nsSerializationHelper.h"
 #include "mozilla/Attributes.h"
@@ -37,18 +38,21 @@
 #include "SerializedLoadContext.h"
 #include "nsInputStreamPump.h"
 #include "InterceptedChannel.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsContentSecurityManager.h"
 #include "nsIDeprecationWarner.h"
 #include "nsICompressConvStats.h"
 #include "nsIDocument.h"
+#include "nsIDOMDocument.h"
 #include "nsIDOMWindowUtils.h"
+#include "nsIEventTarget.h"
 #include "nsStreamUtils.h"
+#include "nsThreadUtils.h"
 
 #ifdef OS_POSIX
 #include "chrome/common/file_descriptor_set_posix.h"
 #endif
 
 #ifdef MOZ_TASK_TRACER
 #include "GeckoTaskTracer.h"
 #endif
@@ -239,16 +243,17 @@ NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
   NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
   NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
   NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
   NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
   NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
   NS_INTERFACE_MAP_ENTRY(nsIDivertableChannel)
+  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
 
 //-----------------------------------------------------------------------------
 // HttpChannelChild::PHttpChannelChild
 //-----------------------------------------------------------------------------
 
 void
 HttpChannelChild::AddIPDLReference()
@@ -712,16 +717,53 @@ HttpChannelChild::OnTransportAndData(con
 
   // Hold queue lock throughout all three calls, else we might process a later
   // necko msg in between them.
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
 
   DoOnStatus(this, transportStatus);
   DoOnProgress(this, progress, progressMax);
 
+  if (mTargetThread && NS_GetCurrentThread() != mTargetThread) {
+    // Suspend channel event processing while doing OMT ODA
+    mEventQ->Suspend();
+
+    RefPtr<HttpChannelChild> self = this;
+    nsCOMPtr<nsIRunnable> resumeRunnable = NS_NewRunnableFunction([self]() {
+      MOZ_ASSERT(NS_IsMainThread());
+      // Resume channel event processing on original thread.
+      self->mEventQ->Resume();
+    });
+
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableFunction([self, data, offset, count, resumeRunnable]() {
+        DebugOnly<bool> isOnThread;
+        MOZ_ASSERT(NS_SUCCEEDED(self->mTargetThread->IsOnCurrentThread(&isOnThread)) && isOnThread);
+        nsCOMPtr<nsIInputStream> stringStream;
+        nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
+                                            count, NS_ASSIGNMENT_DEPEND);
+        if (NS_FAILED(rv)) {
+          self->Cancel(rv);
+          return;
+        }
+        self->DoOnDataAvailable(self, self->mListenerContext, stringStream, offset, count);
+        stringStream->Close();
+
+        nsCOMPtr<nsIEventTarget> originalTarget = self->GetEventTargetForNetwork();
+        originalTarget->Dispatch(resumeRunnable, NS_DISPATCH_NORMAL);
+      });
+
+    nsresult rv = mTargetThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+
+    if (NS_FAILED(rv)) {
+      resumeRunnable->Run();
+    }
+    return;
+  }
+
   // OnDataAvailable
   //
   // NOTE: the OnDataAvailable contract requires the client to read all the data
   // in the inputstream.  This code relies on that ('data' will go away after
   // this function).  Apparently the previous, non-e10s behavior was to actually
   // support only reading part of the data, allowing later calls to read the
   // rest.
   nsCOMPtr<nsIInputStream> stringStream;
@@ -1822,17 +1864,25 @@ HttpChannelChild::OnRedirectVerifyCallba
 //-----------------------------------------------------------------------------
 // HttpChannelChild::nsIRequest
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpChannelChild::Cancel(nsresult status)
 {
   LOG(("HttpChannelChild::Cancel [this=%p]\n", this));
-  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIEventTarget> originalTarget = GetEventTargetForNetwork();
+  bool isOnThread;
+  originalTarget->IsOnCurrentThread(&isOnThread);
+  if (!isOnThread) {
+    return originalTarget->Dispatch(
+      NewRunnableMethod<nsresult>(this, &HttpChannelChild::Cancel, status),
+      NS_DISPATCH_NORMAL);
+  }
 
   if (!mCanceled) {
     // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
     // is responsible for cleaning up.
     mCanceled = true;
     mStatus = status;
     if (RemoteChannelExists())
       SendCancel(status);
@@ -2023,58 +2073,77 @@ HttpChannelChild::AsyncOpen2(nsIStreamLi
   return AsyncOpen(listener, nullptr);
 }
 
 // Assigns an nsIEventTarget to our IPDL actor so that IPC messages are sent to
 // the correct DocGroup/TabGroup.
 void
 HttpChannelChild::SetEventTarget()
 {
+  nsCOMPtr<nsIEventTarget> target = GetEventTargetForNetwork();
+  gNeckoChild->SetEventTargetForActor(this, target);
+  mEventQ->RetargetDeliveryTo(target);
+}
+
+already_AddRefed<nsIEventTarget>
+HttpChannelChild::GetEventTargetForNetwork()
+{
+  if (mNetworkEventTarget) {
+    nsCOMPtr<nsIEventTarget> target = mNetworkEventTarget;
+    return target.forget();
+  }
+
+  // First GetEventTargetForNetwork can only happened on main thread
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIEventTarget> target = do_GetMainThread();
+  mNetworkEventTarget = target;
+
   nsCOMPtr<nsILoadInfo> loadInfo;
   GetLoadInfo(getter_AddRefs(loadInfo));
   if (!loadInfo) {
-    return;
+    return target.forget();
   }
 
   nsCOMPtr<nsIDOMDocument> domDoc;
   loadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
   RefPtr<Dispatcher> dispatcher;
   if (doc) {
     dispatcher = doc->GetDocGroup();
   } else {
     // There's no document yet, but this might be a top-level load where we can
     // find a TabGroup.
     uint64_t outerWindowId;
     if (NS_FAILED(loadInfo->GetOuterWindowID(&outerWindowId))) {
       // No window. This might be an add-on XHR, a service worker request, or
       // something else.
-      return;
+      return target.forget();
     }
     RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetOuterWindowWithId(outerWindowId);
     if (!window) {
-      return;
+      return target.forget();
     }
 
 #ifdef DEBUG
     // We have a TabGroup. This must be a top-level load.
     bool isMainDocumentChannel;
     GetIsMainDocumentChannel(&isMainDocumentChannel);
     MOZ_ASSERT(isMainDocumentChannel);
 #endif
 
     dispatcher = window->TabGroup();
   }
 
   if (dispatcher) {
-    nsCOMPtr<nsIEventTarget> target =
-      dispatcher->EventTargetFor(TaskCategory::Network);
-    gNeckoChild->SetEventTargetForActor(this, target);
-    mEventQ->RetargetDeliveryTo(target);
+    target = dispatcher->EventTargetFor(TaskCategory::Network);
+    mNetworkEventTarget = target;
   }
+
+  return target.forget();
 }
 
 nsresult
 HttpChannelChild::ContinueAsyncOpen()
 {
   nsCString appCacheClientId;
   if (mInheritApplicationCache) {
     // Pick up an application cache from the notification
@@ -2800,16 +2869,55 @@ HttpChannelChild::UnknownDecoderInvolved
 NS_IMETHODIMP
 HttpChannelChild::GetDivertingToParent(bool* aDiverting)
 {
   NS_ENSURE_ARG_POINTER(aDiverting);
   *aDiverting = mDivertingToParent;
   return NS_OK;
 }
 
+//-----------------------------------------------------------------------------
+// HttpChannelChild::nsIThreadRetargetableRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpChannelChild::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
+{
+  LOG(("HttpChannelChild::retargetDeliveryToCallbed"
+       "[this=%p, aNewTarget=%p]", this, aNewTarget));
+
+  MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
+  MOZ_ASSERT(!mTargetThread);
+  MOZ_ASSERT(aNewTarget);
+
+  NS_ENSURE_ARG(aNewTarget);
+  if (aNewTarget == NS_GetCurrentThread()) {
+    NS_WARNING("Retargeting delivery to same thread");
+    return NS_OK;
+  }
+
+  // Ensure that |mListener| and any subsequent listeners can be retargeted
+  // to another thread.
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+      do_QueryInterface(mListener, &rv);
+  if (!retargetableListener || NS_FAILED(rv)) {
+    NS_WARNING("Listener is not retargetable");
+    return NS_OK;
+  }
+
+  rv = retargetableListener->CheckListenerChain();
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Subsequent listeners are not retargetable");
+    return NS_OK;
+  }
+
+  mTargetThread = aNewTarget;
+  return NS_OK;
+}
 
 void
 HttpChannelChild::ResetInterception()
 {
   NS_ENSURE_TRUE_VOID(gNeckoChild != nullptr);
 
   if (mInterceptListener) {
     mInterceptListener->Cleanup();
--- a/netwerk/protocol/http/HttpChannelChild.h
+++ b/netwerk/protocol/http/HttpChannelChild.h
@@ -24,18 +24,20 @@
 #include "nsIUploadChannel2.h"
 #include "nsIResumableChannel.h"
 #include "nsIProxiedChannel.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIAssociatedContentSecurity.h"
 #include "nsIChildChannel.h"
 #include "nsIHttpChannelChild.h"
 #include "nsIDivertableChannel.h"
+#include "nsIThreadRetargetableRequest.h"
 #include "mozilla/net/DNS.h"
 
+class nsIEventTarget;
 class nsInputStreamPump;
 
 namespace mozilla {
 namespace net {
 
 class InterceptedChannelContent;
 class InterceptStreamListener;
 
@@ -45,29 +47,31 @@ class HttpChannelChild final : public PH
                              , public nsICacheInfoChannel
                              , public nsIProxiedChannel
                              , public nsIApplicationCacheChannel
                              , public nsIAsyncVerifyRedirectCallback
                              , public nsIAssociatedContentSecurity
                              , public nsIChildChannel
                              , public nsIHttpChannelChild
                              , public nsIDivertableChannel
+                             , public nsIThreadRetargetableRequest
 {
   virtual ~HttpChannelChild();
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICACHEINFOCHANNEL
   NS_DECL_NSIPROXIEDCHANNEL
   NS_DECL_NSIAPPLICATIONCACHECONTAINER
   NS_DECL_NSIAPPLICATIONCACHECHANNEL
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
   NS_DECL_NSIASSOCIATEDCONTENTSECURITY
   NS_DECL_NSICHILDCHANNEL
   NS_DECL_NSIHTTPCHANNELCHILD
   NS_DECL_NSIDIVERTABLECHANNEL
+  NS_DECL_NSITHREADRETARGETABLEREQUEST
 
   HttpChannelChild();
 
   // Methods HttpBaseChannel didn't implement for us or that we override.
   //
   // nsIRequest
   NS_IMETHOD Cancel(nsresult status) override;
   NS_IMETHOD Suspend() override;
@@ -184,16 +188,17 @@ private:
     nsAutoPtr<nsHttpResponseHead> mHead;
   };
 
   // Sets the event target for future IPC messages. Messages will either be
   // directed to the TabGroup or DocGroup, depending on the LoadInfo associated
   // with the channel. Should be called when a new channel is being set up,
   // before the constructor message is sent to the parent.
   void SetEventTarget();
+  already_AddRefed<nsIEventTarget> GetEventTargetForNetwork();
 
   nsresult ContinueAsyncOpen();
 
   void DoOnStartRequest(nsIRequest* aRequest, nsISupports* aContext);
   void DoOnStatus(nsIRequest* aRequest, nsresult status);
   void DoOnProgress(nsIRequest* aRequest, int64_t progress, int64_t progressMax);
   void DoOnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, nsIInputStream* aStream,
                          uint64_t offset, uint32_t count);
@@ -272,16 +277,20 @@ private:
   // Set if the corresponding parent channel should force an interception to occur
   // before the network transaction is initiated.
   bool mShouldParentIntercept;
 
   // Set if the corresponding parent channel should suspend after a response
   // is synthesized.
   bool mSuspendParentAfterSynthesizeResponse;
 
+  // Target thread for delivering ODA.
+  nsCOMPtr<nsIEventTarget> mTargetThread;
+  nsCOMPtr<nsIEventTarget> mNetworkEventTarget;
+
   // Needed to call AsyncOpen in FinishInterceptedRedirect
   nsCOMPtr<nsIStreamListener> mInterceptedRedirectListener;
   nsCOMPtr<nsISupports> mInterceptedRedirectContext;
   // Needed to call CleanupRedirectingChannel in FinishInterceptedRedirect
   RefPtr<HttpChannelChild> mInterceptingChannel;
   // Used to call OverrideWithSynthesizedResponse in FinishInterceptedRedirect
   RefPtr<OverrideRunnable> mOverrideRunnable;