Bug 1527712 - Move I/O in nsFileChannel::OpenContentStream to a background thread, r=kershaw,dragana
authorHonza Bambas <honzab.moz@firemni.cz>
Tue, 19 Mar 2019 15:25:47 +0000
changeset 465051 2657a29a6d76e50f493353ae82f04128acad76fe
parent 465050 ebf98069b0fbc60e1cfa79ce97f4266c66860bcd
child 465052 4a364b806b4a39823a7d2c107da53a5c2192e80e
push id112488
push userrmaries@mozilla.com
push dateTue, 19 Mar 2019 22:11:19 +0000
treeherdermozilla-inbound@d15d511d7fed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskershaw, dragana
bugs1527712
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1527712 - Move I/O in nsFileChannel::OpenContentStream to a background thread, r=kershaw,dragana Differential Revision: https://phabricator.services.mozilla.com/D23352
netwerk/base/nsBaseChannel.cpp
netwerk/base/nsBaseChannel.h
netwerk/protocol/file/nsFileChannel.cpp
netwerk/protocol/file/nsFileChannel.h
--- a/netwerk/base/nsBaseChannel.cpp
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -245,23 +245,55 @@ nsresult nsBaseChannel::BeginPumpingData
   // important that the pending flag is set when we call into the stream (the
   // call to AsyncRead results in the stream's AsyncWait method being called)
   // and especially when we call into the loadgroup.  Our caller takes care to
   // release mPump if we return an error.
 
   nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
   rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true,
                                  target);
-  if (NS_SUCCEEDED(rv)) {
-    mPumpingData = true;
-    mRequest = mPump;
-    rv = mPump->AsyncRead(this, nullptr);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mPumpingData = true;
+  mRequest = mPump;
+  rv = mPump->AsyncRead(this, nullptr);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<BlockingPromise> promise;
+  rv = ListenerBlockingPromise(getter_AddRefs(promise));
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  return rv;
+  if (promise) {
+    mPump->Suspend();
+
+    RefPtr<nsBaseChannel> self(this);
+    nsCOMPtr<nsISerialEventTarget> serialTarget(do_QueryInterface(target));
+    MOZ_ASSERT(serialTarget);
+
+    promise->Then(serialTarget, __func__,
+                  [self, this](nsresult rv) {
+                    MOZ_ASSERT(mPump);
+                    MOZ_ASSERT(NS_SUCCEEDED(rv));
+                    mPump->Resume();
+                  },
+                  [self, this](nsresult rv) {
+                    MOZ_ASSERT(mPump);
+                    MOZ_ASSERT(NS_FAILED(rv));
+                    Cancel(rv);
+                    mPump->Resume();
+                  });
+  }
+
+  return NS_OK;
 }
 
 void nsBaseChannel::HandleAsyncRedirect(nsIChannel *newChannel) {
   NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
 
   nsresult rv = mStatus;
   if (NS_SUCCEEDED(mStatus)) {
     rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true);
--- a/netwerk/base/nsBaseChannel.h
+++ b/netwerk/base/nsBaseChannel.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsBaseChannel_h__
 #define nsBaseChannel_h__
 
 #include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/MozPromise.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsHashPropertyBag.h"
 #include "nsInputStreamPump.h"
 
 #include "nsIChannel.h"
 #include "nsIURI.h"
@@ -68,16 +69,18 @@ class nsBaseChannel
   nsresult Init() { return NS_OK; }
 
  protected:
   // -----------------------------------------------
   // Methods to be implemented by the derived class:
 
   virtual ~nsBaseChannel();
 
+  using BlockingPromise = mozilla::MozPromise<nsresult, nsresult, true>;
+
  private:
   // Implemented by subclass to supply data stream.  The parameter, async, is
   // true when called from nsIChannel::AsyncOpen and false otherwise.  When
   // async is true, the resulting stream will be used with a nsIInputStreamPump
   // instance.  This means that if it is a non-blocking stream that supports
   // nsIAsyncInputStream that it will be read entirely on the main application
   // thread, and its AsyncWait method will be called whenever ReadSegments
   // returns NS_BASE_STREAM_WOULD_BLOCK.  Otherwise, if the stream is blocking,
@@ -101,16 +104,30 @@ class nsBaseChannel
   // and at some point call OnStartRequest followed by OnStopRequest.
   // Additionally, it may provide a request object which may be used to
   // suspend, resume, and cancel the underlying request.
   virtual nsresult BeginAsyncRead(nsIStreamListener *listener,
                                   nsIRequest **request) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  // This method may return a promise that will keep the input stream pump
+  // suspended until the promise is resolved or rejected.  On resolution the
+  // pump is resumed.  On rejection the channel is canceled with the resulting
+  // error and then the pump is also resumed to propagate the error to the
+  // channel listener.  Use it to do any asynchronous/background tasks you need
+  // to finish prior calling OnStartRequest of the listener.  This method is
+  // called right after OpenContentStream() with async == true, after the input
+  // stream pump has already been called asyncRead().
+  virtual nsresult ListenerBlockingPromise(BlockingPromise **aPromise) {
+    NS_ENSURE_ARG(aPromise);
+    *aPromise = nullptr;
+    return NS_OK;
+  }
+
   // The basechannel calls this method from its OnTransportStatus method to
   // determine whether to call nsIProgressEventSink::OnStatus in addition to
   // nsIProgressEventSink::OnProgress.  This method may be overriden by the
   // subclass to enable nsIProgressEventSink::OnStatus events.  If this method
   // returns true, then the statusArg out param specifies the "statusArg" value
   // to pass to the OnStatus method.  By default, OnStatus messages are
   // suppressed.  The status parameter passed to this method is the status value
   // from the OnTransportStatus method.
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -24,16 +24,17 @@
 
 #include "nsIFileURL.h"
 #include "nsIURIMutator.h"
 #include "nsIFile.h"
 #include "nsIMIMEService.h"
 #include "prio.h"
 #include <algorithm>
 
+#include "mozilla/TaskQueue.h"
 #include "mozilla/Unused.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 //-----------------------------------------------------------------------------
 
 class nsFileCopyEvent : public Runnable {
@@ -386,37 +387,90 @@ nsresult nsFileChannel::OpenContentStrea
   } else {
     nsAutoCString contentType;
     rv = MakeFileInputStream(file, stream, contentType, async);
     if (NS_FAILED(rv)) return rv;
 
     EnableSynthesizedProgressEvents(true);
 
     // fixup content length and type
-    if (mContentLength < 0) {
-      int64_t size;
-      rv = file->GetFileSize(&size);
+
+    // when we are called from asyncOpen, the content length fixup will be
+    // performed on a background thread and block the listener invocation via
+    // ListenerBlockingPromise method
+    if (!async && mContentLength < 0) {
+      rv = FixupContentLength(false);
       if (NS_FAILED(rv)) {
-        if (async && (NS_ERROR_FILE_NOT_FOUND == rv ||
-                      NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
-          size = 0;
-        } else {
-          return rv;
-        }
+        return rv;
       }
-      mContentLength = size;
     }
-    if (!contentType.IsEmpty()) SetContentType(contentType);
+
+    if (!contentType.IsEmpty()) {
+      SetContentType(contentType);
+    }
   }
 
   *result = nullptr;
   stream.swap(*result);
   return NS_OK;
 }
 
+nsresult nsFileChannel::ListenerBlockingPromise(BlockingPromise **aPromise) {
+  NS_ENSURE_ARG(aPromise);
+  *aPromise = nullptr;
+
+  if (mContentLength >= 0) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIEventTarget> sts(
+      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+  if (!sts) {
+    return FixupContentLength(true);
+  }
+
+  RefPtr<TaskQueue> taskQueue = new TaskQueue(sts.forget());
+  RefPtr<nsFileChannel> self = this;
+  RefPtr<BlockingPromise> promise =
+      mozilla::InvokeAsync(taskQueue, __func__, [self{std::move(self)}]() {
+        nsresult rv = self->FixupContentLength(true);
+        if (NS_FAILED(rv)) {
+          return BlockingPromise::CreateAndReject(rv, __func__);
+        }
+        return BlockingPromise::CreateAndResolve(NS_OK, __func__);
+      });
+
+  promise.forget(aPromise);
+  return NS_OK;
+}
+
+nsresult nsFileChannel::FixupContentLength(bool async) {
+  MOZ_ASSERT(mContentLength < 0);
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = GetFile(getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  int64_t size;
+  rv = file->GetFileSize(&size);
+  if (NS_FAILED(rv)) {
+    if (async && (NS_ERROR_FILE_NOT_FOUND == rv ||
+                  NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
+      size = 0;
+    } else {
+      return rv;
+    }
+  }
+  mContentLength = size;
+
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsFileChannel::nsISupports
 
 NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
                             nsIFileChannel)
 
 //-----------------------------------------------------------------------------
 // nsFileChannel::nsIFileChannel
--- a/netwerk/protocol/file/nsFileChannel.h
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -32,15 +32,21 @@ class nsFileChannel : public nsBaseChann
   // untouched. The caller should not use it in that case.
   MOZ_MUST_USE nsresult MakeFileInputStream(nsIFile *file,
                                             nsCOMPtr<nsIInputStream> &stream,
                                             nsCString &contentType, bool async);
 
   virtual MOZ_MUST_USE nsresult OpenContentStream(
       bool async, nsIInputStream **result, nsIChannel **channel) override;
 
+  // Implementing the pump blocking promise to fixup content length on a
+  // background thread prior to calling on mListener
+  virtual nsresult ListenerBlockingPromise(BlockingPromise **promise) override;
+
  private:
+  nsresult FixupContentLength(bool async);
+
   nsCOMPtr<nsIInputStream> mUploadStream;
   int64_t mUploadLength;
   nsCOMPtr<nsIURI> mFileURI;
 };
 
 #endif  // !nsFileChannel_h__