Bug 1373708 - Part 1: Make local jar I/O off-main-thread. r=nwgh
authorLiang-Heng Chen <xeonchen@gmail.com>
Thu, 04 Jan 2018 22:47:00 -0500
changeset 398658 d31af7725e65299edc1b16f11fee0a3963f6f57f
parent 398657 ac990767e3e83207f325c9a1ec98e28dfdf9f9a1
child 398659 a30d2216e0b9c8fc902f5e1704eafabab828fcff
push id33228
push useraciure@mozilla.com
push dateThu, 11 Jan 2018 09:55:35 +0000
treeherdermozilla-central@e61c4485494e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnwgh
bugs1373708
milestone59.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 1373708 - Part 1: Make local jar I/O off-main-thread. r=nwgh MozReview-Commit-ID: J87ygHglR2
modules/libjar/nsJARChannel.cpp
modules/libjar/nsJARChannel.h
--- a/modules/libjar/nsJARChannel.cpp
+++ b/modules/libjar/nsJARChannel.cpp
@@ -21,16 +21,17 @@
 #include "nsIPrincipal.h"
 #include "nsIFileURL.h"
 
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Preferences.h"
 #include "nsITabChild.h"
 #include "private/pprio.h"
 #include "nsInputStreamPump.h"
+#include "nsThreadUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 // the entry for a directory will either be empty (in the case of the
 // top-level directory) or will end with a slash
@@ -193,27 +194,31 @@ nsJARInputThunk::IsNonBlocking(bool *non
 
 nsJARChannel::nsJARChannel()
     : mOpened(false)
     , mContentDisposition(0)
     , mContentLength(-1)
     , mLoadFlags(LOAD_NORMAL)
     , mStatus(NS_OK)
     , mIsPending(false)
+    , mEnableOMT(true)
+    , mPendingEvent()
     , mIsUnsafe(true)
     , mBlockRemoteFiles(false)
 {
+    LOG(("nsJARChannel::nsJARChannel [this=%p]\n", this));
     mBlockRemoteFiles = Preferences::GetBool("network.jar.block-remote-files", false);
 
     // hold an owning reference to the jar handler
     NS_ADDREF(gJarHandler);
 }
 
 nsJARChannel::~nsJARChannel()
 {
+    LOG(("nsJARChannel::~nsJARChannel [this=%p]\n", this));
     NS_ReleaseOnMainThreadSystemGroup("nsJARChannel::mLoadInfo",
                                       mLoadInfo.forget());
 
     // release owning reference to the jar handler
     nsJARProtocolHandler *handler = gJarHandler;
     NS_RELEASE(handler); // nullptr parameter
 }
 
@@ -225,17 +230,24 @@ NS_IMPL_ISUPPORTS_INHERITED(nsJARChannel
                             nsIRequestObserver,
                             nsIThreadRetargetableRequest,
                             nsIThreadRetargetableStreamListener,
                             nsIJARChannel)
 
 nsresult
 nsJARChannel::Init(nsIURI *uri)
 {
+    LOG(("nsJARChannel::Init [this=%p]\n", this));
     nsresult rv;
+
+    mWorker = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+    if (NS_FAILED(rv)) {
+        return rv;
+    }
+
     mJarURI = do_QueryInterface(uri, &rv);
     if (NS_FAILED(rv))
         return rv;
 
     mOriginalURI = mJarURI;
 
     // Prevent loading jar:javascript URIs (see bug 290982).
     nsCOMPtr<nsIURI> innerURI;
@@ -253,16 +265,17 @@ nsJARChannel::Init(nsIURI *uri)
 
     mJarURI->GetSpec(mSpec);
     return rv;
 }
 
 nsresult
 nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resultInput)
 {
+    LOG(("nsJARChannel::CreateJarInput [this=%p]\n", this));
     MOZ_ASSERT(resultInput);
     MOZ_ASSERT(mJarFile || mTempMem);
 
     // important to pass a clone of the file since the nsIFile impl is not
     // necessarily MT-safe
     nsCOMPtr<nsIFile> clonedFile;
     nsresult rv = NS_OK;
     if (mJarFile) {
@@ -377,34 +390,244 @@ nsJARChannel::LookupFile(bool aAllowAsyn
             }
         }
     }
 
     return rv;
 }
 
 nsresult
+CreateLocalJarInput(nsIZipReaderCache* aJarCache,
+                    nsIFile* aFile,
+                    const nsACString& aInnerJarEntry,
+                    nsIJARURI* aJarURI,
+                    const nsACString& aJarEntry,
+                    nsJARInputThunk** aResultInput)
+{
+    LOG(("nsJARChannel::CreateLocalJarInput [aJarCache=%p, %s, %s]\n",
+         aJarCache,
+         PromiseFlatCString(aInnerJarEntry).get(),
+         PromiseFlatCString(aJarEntry).get()));
+
+    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(aJarCache);
+    MOZ_ASSERT(aResultInput);
+
+    nsresult rv;
+
+    nsCOMPtr<nsIZipReader> reader;
+    if (aInnerJarEntry.IsEmpty()) {
+        rv = aJarCache->GetZip(aFile, getter_AddRefs(reader));
+    } else {
+        rv = aJarCache->GetInnerZip(aFile,
+                                    aInnerJarEntry,
+                                    getter_AddRefs(reader));
+    }
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    RefPtr<nsJARInputThunk> input = new nsJARInputThunk(reader,
+                                                        aJarURI,
+                                                        aJarEntry,
+                                                        aJarCache != nullptr);
+    rv = input->Init();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    input.forget(aResultInput);
+    return NS_OK;
+}
+
+nsresult
 nsJARChannel::OpenLocalFile()
 {
+    LOG(("nsJARChannel::OpenLocalFile [this=%p]\n", this));
+
+    MOZ_ASSERT(NS_IsMainThread());
+
+    MOZ_ASSERT(mWorker);
     MOZ_ASSERT(mIsPending);
+    MOZ_ASSERT(mJarFile);
 
     // Local files are always considered safe.
     mIsUnsafe = false;
 
-    RefPtr<nsJARInputThunk> input;
-    nsresult rv = CreateJarInput(gJarHandler->JarCache(),
-                                 getter_AddRefs(input));
-    if (NS_SUCCEEDED(rv)) {
-        // Create input stream pump and call AsyncRead as a block.
-        rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input.forget());
-        if (NS_SUCCEEDED(rv))
-            rv = mPump->AsyncRead(this, nullptr);
+    nsresult rv;
+
+    // Set mLoadGroup and mOpened before AsyncOpen return, and set back if
+    // if failed when callback.
+    if (mLoadGroup) {
+        mLoadGroup->AddRequest(this, nullptr);
+    }
+    mOpened = true;
+
+    if (!mEnableOMT) {
+
+        RefPtr<nsJARInputThunk> input;
+        rv = CreateJarInput(gJarHandler->JarCache(),
+                            getter_AddRefs(input));
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+            return OnOpenLocalFileComplete(rv);
+        }
+        return ContinueOpenLocalFile(input);
+    }
+
+
+    nsCOMPtr<nsIZipReaderCache> jarCache = gJarHandler->JarCache();
+    if (NS_WARN_IF(!jarCache)) {
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    nsCOMPtr<nsIFile> clonedFile;
+    rv = mJarFile->Clone(getter_AddRefs(clonedFile));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+
+    // clone mJarURI
+    nsCOMPtr<nsIURI> clonedURI;
+    rv = mJarURI->Clone(getter_AddRefs(clonedURI));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+    }
+    nsCOMPtr<nsIJARURI> clonedJarURI = do_QueryInterface(clonedURI, &rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
     }
 
-    return rv;
+    nsAutoCString jarEntry(mJarEntry);
+    nsAutoCString innerJarEntry(mInnerJarEntry);
+
+    RefPtr<nsJARChannel> self = this;
+    return mWorker->Dispatch(
+            NS_NewRunnableFunction("nsJARChannel::OpenLocalFile",
+                                   [self,
+                                   jarCache,
+                                   clonedFile,
+                                   clonedJarURI,
+                                   jarEntry,
+                                   innerJarEntry] () mutable {
+
+        RefPtr<nsJARInputThunk> input;
+        nsresult rv = CreateLocalJarInput(jarCache,
+                                          clonedFile,
+                                          innerJarEntry,
+                                          clonedJarURI,
+                                          jarEntry,
+                                          getter_AddRefs(input));
+
+        NS_ReleaseOnMainThreadSystemGroup("nsJARChannel::clonedJarURI",
+                                          clonedJarURI.forget());
+
+        nsCOMPtr<nsIRunnable> target;
+        if (NS_SUCCEEDED(rv)) {
+            target = NewRunnableMethod<RefPtr<nsJARInputThunk>>(
+                "nsJARChannel::ContinueOpenLocalFile",
+                self,
+                &nsJARChannel::ContinueOpenLocalFile,
+                input);
+        } else {
+            target = NewRunnableMethod<nsresult>(
+                "nsJARChannel::OnOpenLocalFileComplete",
+                self,
+                &nsJARChannel::OnOpenLocalFileComplete,
+                rv);
+        }
+
+        // nsJARChannel must be release on main thread, and sometimes
+        // this still hold nsJARChannel after dispatched.
+        self = nullptr;
+
+        NS_DispatchToMainThread(target);
+    }));
+}
+
+nsresult
+nsJARChannel::ContinueOpenLocalFile(nsJARInputThunk* aInput)
+{
+    LOG(("nsJARChannel::ContinueOpenLocalFile [this=%p %p]\n", this, aInput));
+
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mIsPending);
+
+    // Make GetContentLength meaningful
+    mContentLength = aInput->GetContentLength();
+
+    nsresult rv;
+    RefPtr<nsJARInputThunk> input = aInput;
+    // Create input stream pump and call AsyncRead as a block.
+    rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input.forget());
+    if (NS_SUCCEEDED(rv)) {
+        rv = mPump->AsyncRead(this, nullptr);
+    }
+
+    if (NS_SUCCEEDED(rv)) {
+        rv = CheckPendingEvents();
+    }
+
+    return OnOpenLocalFileComplete(rv);
+}
+
+nsresult
+nsJARChannel::OnOpenLocalFileComplete(nsresult aResult)
+{
+    LOG(("nsJARChannel::OnOpenLocalFileComplete [this=%p %08x]\n",
+         this,
+         static_cast<uint32_t>(aResult)));
+
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mIsPending);
+
+    if (NS_FAILED(aResult)) {
+        if (mIsPending && mEnableOMT) {
+            NotifyError(aResult);
+        }
+
+        if (mLoadGroup) {
+            mLoadGroup->RemoveRequest(this, nullptr, aResult);
+        }
+
+        mOpened = false;
+        mIsPending = false;
+        mListenerContext = nullptr;
+        mListener = nullptr;
+        mCallbacks = nullptr;
+        mProgressSink = nullptr;
+
+        return aResult;
+    }
+
+    return NS_OK;
+}
+
+nsresult nsJARChannel::CheckPendingEvents()
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mIsPending);
+    MOZ_ASSERT(mPump);
+
+    nsresult rv;
+
+    auto suspendCount = mPendingEvent.suspendCount;
+    while (suspendCount--) {
+        if (NS_WARN_IF(NS_FAILED(rv = mPump->Suspend()))) {
+            return rv;
+        }
+    }
+
+    if (mPendingEvent.isCanceled) {
+        if (NS_WARN_IF(NS_FAILED(rv = mPump->Cancel(mStatus)))) {
+            return rv;
+        }
+        mPendingEvent.isCanceled = false;
+    }
+
+    return NS_OK;
 }
 
 void
 nsJARChannel::NotifyError(nsresult aError)
 {
     MOZ_ASSERT(NS_FAILED(aError));
 
     mStatus = aError;
@@ -448,40 +671,51 @@ nsJARChannel::GetStatus(nsresult *status
         *status = mStatus;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Cancel(nsresult status)
 {
     mStatus = status;
-    if (mPump)
+    if (mPump) {
         return mPump->Cancel(status);
+    }
 
-    NS_ASSERTION(!mIsPending, "need to implement cancel when downloading");
+    if (mIsPending) {
+        mPendingEvent.isCanceled = true;
+    }
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Suspend()
 {
-    if (mPump)
+    ++mPendingEvent.suspendCount;
+
+    if (mPump) {
         return mPump->Suspend();
+    }
 
-    NS_ASSERTION(!mIsPending, "need to implement suspend when downloading");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Resume()
 {
-    if (mPump)
+    if (NS_WARN_IF(mPendingEvent.suspendCount == 0)) {
+        return NS_ERROR_UNEXPECTED;
+    }
+    --mPendingEvent.suspendCount;
+
+    if (mPump) {
         return mPump->Resume();
+    }
 
-    NS_ASSERTION(!mIsPending, "need to implement resume when downloading");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
 {
     *aLoadFlags = mLoadFlags;
     return NS_OK;
@@ -750,25 +984,27 @@ nsJARChannel::Open(nsIInputStream **stre
     // local files are always considered safe
     mIsUnsafe = false;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::Open2(nsIInputStream** aStream)
 {
+    LOG(("nsJARChannel::Open2 [this=%p]\n", this));
     nsCOMPtr<nsIStreamListener> listener;
     nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
     NS_ENSURE_SUCCESS(rv, rv);
     return Open(aStream);
 }
 
 NS_IMETHODIMP
 nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
 {
+    LOG(("nsJARChannel::AsyncOpen [this=%p]\n", this));
     MOZ_ASSERT(!mLoadInfo ||
                mLoadInfo->GetSecurityMode() == 0 ||
                mLoadInfo->GetInitialSecurityCheckDone() ||
                (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
                 nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
                "security flags in loadInfo but asyncOpen2() not called");
 
     LOG(("nsJARChannel::AsyncOpen [this=%p]\n", this));
@@ -832,41 +1068,46 @@ nsJARChannel::AsyncOpen(nsIStreamListene
             return rv;
         }
         if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
             rv = channel->AsyncOpen2(downloader);
         }
         else {
             rv = channel->AsyncOpen(downloader, nullptr);
         }
+
     }
     else {
         rv = OpenLocalFile();
+        if (NS_SUCCEEDED(rv)) {
+            return NS_OK;
+        }
     }
 
     if (NS_FAILED(rv)) {
         mIsPending = false;
         mListenerContext = nullptr;
         mListener = nullptr;
         mCallbacks = nullptr;
         mProgressSink = nullptr;
         return rv;
     }
 
     if (mLoadGroup)
         mLoadGroup->AddRequest(this, nullptr);
 
     mOpened = true;
-
+    LOG(("nsJARChannel::AsyncOpen [this=%p] 8\n", this));
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsJARChannel::AsyncOpen2(nsIStreamListener *aListener)
 {
+    LOG(("nsJARChannel::AsyncOpen2 [this=%p]\n", this));
   nsCOMPtr<nsIStreamListener> listener = aListener;
   nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
   if (NS_FAILED(rv)) {
       mIsPending = false;
       mListenerContext = nullptr;
       mListener = nullptr;
       mCallbacks = nullptr;
       mProgressSink = nullptr;
--- a/modules/libjar/nsJARChannel.h
+++ b/modules/libjar/nsJARChannel.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsJARChannel_h__
 #define nsJARChannel_h__
 
 #include "mozilla/net/MemoryDownloader.h"
 #include "nsIJARChannel.h"
 #include "nsIJARURI.h"
+#include "nsIEventTarget.h"
 #include "nsIInputStreamPump.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIStreamListener.h"
 #include "nsIZipReader.h"
 #include "nsILoadGroup.h"
 #include "nsILoadInfo.h"
 #include "nsIThreadRetargetableRequest.h"
@@ -54,16 +55,19 @@ public:
     void SetFile(nsIFile *file);
 
 private:
     virtual ~nsJARChannel();
 
     nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **);
     nsresult LookupFile(bool aAllowAsync);
     nsresult OpenLocalFile();
+    nsresult ContinueOpenLocalFile(nsJARInputThunk* aInput);
+    nsresult OnOpenLocalFileComplete(nsresult aResult);
+    nsresult CheckPendingEvents();
     void NotifyError(nsresult aError);
     void FireOnProgress(uint64_t aProgress);
     virtual void OnDownloadComplete(mozilla::net::MemoryDownloader* aDownloader,
                                     nsIRequest* aRequest,
                                     nsISupports* aCtxt,
                                     nsresult aStatus,
                                     mozilla::net::MemoryDownloader::Data aData)
         override;
@@ -86,28 +90,39 @@ private:
     nsCString                       mContentCharset;
     nsCString                       mContentDispositionHeader;
     /* mContentDisposition is uninitialized if mContentDispositionHeader is
      * empty */
     uint32_t                        mContentDisposition;
     int64_t                         mContentLength;
     uint32_t                        mLoadFlags;
     nsresult                        mStatus;
-    bool                            mIsPending;
+    bool                            mIsPending; // the AsyncOpen is in progress.
+
+    bool                            mEnableOMT;
+    // |Cancel()|, |Suspend()|, and |Resume()| might be called during AsyncOpen.
+    struct {
+        bool isCanceled;
+        uint32_t suspendCount;
+    }                               mPendingEvent;
+
     bool                            mIsUnsafe;
 
     mozilla::net::MemoryDownloader::Data mTempMem;
     nsCOMPtr<nsIInputStreamPump>    mPump;
     // mRequest is only non-null during OnStartRequest, so we'll have a pointer
     // to the request if we get called back via RetargetDeliveryTo.
     nsCOMPtr<nsIRequest>            mRequest;
     nsCOMPtr<nsIFile>               mJarFile;
     nsCOMPtr<nsIFile>               mJarFileOverride;
     nsCOMPtr<nsIZipReader>          mPreCachedJarReader;
     nsCOMPtr<nsIURI>                mJarBaseURI;
     nsCString                       mJarEntry;
     nsCString                       mInnerJarEntry;
 
+    // use StreamTransportService as background thread
+    nsCOMPtr<nsIEventTarget>        mWorker;
+
     // True if this channel should not download any remote files.
     bool                            mBlockRemoteFiles;
 };
 
 #endif // nsJARChannel_h__