Bug 1347031 - Move the MediaCache off of opening its temporary file fd synchronously in the content process. r=jwwang draft
authorChris Pearce <cpearce@mozilla.com>
Fri, 17 Mar 2017 09:54:23 +1300
changeset 501345 56e0a1f1473db3c9722330254f7a4bf3a1f5caa3
parent 501295 1b9293be51637f841275541d8991314ca56561a5
child 549839 a1c6cb4bd5b62b64278ef944f057d8c369b6610a
push id49937
push userbmo:cpearce@mozilla.com
push dateMon, 20 Mar 2017 01:29:40 +0000
reviewersjwwang
bugs1347031, 1346987
milestone55.0a1
Bug 1347031 - Move the MediaCache off of opening its temporary file fd synchronously in the content process. r=jwwang In bug 1346987 we're attempting to remove uses of the NS_OpenAnonymousTemporaryFile() in the content process as it sends a synchronous IPC to the parent process on the main thread, which can cause UI jank. This patch makes the MediaCache use the async anonymous temporary file creation function added in bug 1346987. The file descriptor is held by the FileBlockCache. This object buffers data passed to it in memory, and defers writing of said data to another thread. I added the async wait for the file descriptor to be inside that async "defer to other thread" step. This means that while the content process is waiting for the file descriptor to come down from the parent process, we'll buffer media data being streamed in memory. Given that our MSE implementation will buffer up to 100MB of media data in memory anyway, it seems that more buffering in the src=url case while we wait for an async IPC to do a round trip to the main process is acceptable. MozReview-Commit-ID: 3OTBTWw5pr0
dom/media/FileBlockCache.cpp
dom/media/FileBlockCache.h
dom/media/MediaCache.cpp
--- a/dom/media/FileBlockCache.cpp
+++ b/dom/media/FileBlockCache.cpp
@@ -1,39 +1,84 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
+#include "FileBlockCache.h"
 #include "mozilla/SharedThreadPool.h"
-#include "FileBlockCache.h"
 #include "VideoUtils.h"
 #include "prio.h"
 #include <algorithm>
+#include "nsAnonymousTemporaryFile.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsXULAppAPI.h"
 
 namespace mozilla {
 
-nsresult FileBlockCache::Open(PRFileDesc* aFD)
+LazyLogModule gFileBlockCacheLog("FileBlockCache");
+#define FBC_LOG(type, msg) MOZ_LOG(gFileBlockCacheLog, type, msg)
+
+void
+FileBlockCache::SetCacheFile(PRFileDesc* aFD)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  NS_ENSURE_TRUE(aFD != nullptr, NS_ERROR_FAILURE);
-  {
-    MonitorAutoLock mon(mFileMonitor);
-    mFD = aFD;
+  MOZ_ASSERT(NS_IsMainThread());
+  FBC_LOG(LogLevel::Debug,
+          ("FileBlockCache::SetFD(aFD=%p) mIsOpen=%d", aFD, mIsOpen));
+
+  if (!aFD) {
+    // Failed to get a temporary file. Shutdown.
+    mInitPromise->Reject(NS_ERROR_FAILURE, __func__);
+    Close();
+    return;
   }
   {
-    MonitorAutoLock mon(mDataMonitor);
-    nsresult res = NS_NewNamedThread("FileBlockCache",
-                                     getter_AddRefs(mThread),
-                                     nullptr,
-                                     SharedThreadPool::kStackSize);
-    mIsOpen = NS_SUCCEEDED(res);
-    return res;
+    MonitorAutoLock lock(mFileMonitor);
+    mFD = aFD;
+  }
+  mInitPromise->Resolve(true, __func__);
+}
+
+nsresult
+FileBlockCache::Init()
+{
+  FBC_LOG(LogLevel::Debug, ("FileBlockCache::Init()"));
+
+  MOZ_ASSERT(NS_IsMainThread());
+
+  MonitorAutoLock mon(mDataMonitor);
+  nsresult rv = NS_NewNamedThread("FileBlockCache",
+                                  getter_AddRefs(mThread),
+                                  nullptr,
+                                  SharedThreadPool::kStackSize);
+  if (NS_FAILED(rv)) {
+    return rv;
   }
+  mAbstractThread = AbstractThread::CreateXPCOMThreadWrapper(mThread, false);
+  mIsOpen = true;
+
+  mInitPromise = new GenericPromise::Private(__func__);
+  if (XRE_IsParentProcess()) {
+    rv = NS_OpenAnonymousTemporaryFile(&mFD);
+    if (NS_SUCCEEDED(rv)) {
+      mInitPromise->Resolve(true, __func__);
+    }
+  } else {
+    // We must request a temporary file descriptor from the parent process.
+    RefPtr<FileBlockCache> self = this;
+    rv = dom::ContentChild::GetSingleton()->AsyncOpenAnonymousTemporaryFile(
+      [self](PRFileDesc* aFD) { self->SetCacheFile(aFD); });
+  }
+
+  if (NS_FAILED(rv)) {
+    Close();
+  }
+
+  return rv;
 }
 
 FileBlockCache::FileBlockCache()
   : mFileMonitor("MediaCache.Writer.IO.Monitor"),
     mFD(nullptr),
     mFDCurrentPos(0),
     mDataMonitor("MediaCache.Writer.Data.Monitor"),
     mIsWriteScheduled(false),
@@ -54,34 +99,38 @@ FileBlockCache::~FileBlockCache()
       if (prrc != PR_SUCCESS) {
         NS_WARNING("PR_Close() failed.");
       }
       mFD = nullptr;
     }
   }
 }
 
-
 void FileBlockCache::Close()
 {
+  FBC_LOG(LogLevel::Debug, ("FileBlockCache::Close"));
+
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  MonitorAutoLock mon(mDataMonitor);
-
-  mIsOpen = false;
 
-  if (mThread) {
-    // We must shut down the thread in another runnable. This is called
-    // while we're shutting down the media cache, and nsIThread::Shutdown()
-    // can cause events to run before it completes, which could end up
-    // opening more streams, while the media cache is shutting down and
-    // releasing memory etc! Also note we close mFD in the destructor so
-    // as to not disturb any IO that's currently running.
-    nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
-    SystemGroup::Dispatch("ShutdownThreadEvent", TaskCategory::Other, event.forget());
+  MonitorAutoLock mon(mDataMonitor);
+  mIsOpen = false;
+  if (!mThread) {
+    return;
   }
+  mAbstractThread = nullptr;
+  // We must shut down the thread in another runnable. This is called
+  // while we're shutting down the media cache, and nsIThread::Shutdown()
+  // can cause events to run before it completes, which could end up
+  // opening more streams, while the media cache is shutting down and
+  // releasing memory etc! Also note we close mFD in the destructor so
+  // as to not disturb any IO that's currently running.
+  nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(mThread);
+  SystemGroup::Dispatch(
+    "ShutdownThreadEvent", TaskCategory::Other, event.forget());
+  mThread = nullptr;
 }
 
 template<typename Container, typename Value>
 bool
 ContainerContains(const Container& aContainer, const Value& value)
 {
   return std::find(aContainer.begin(), aContainer.end(), value)
          != aContainer.end();
@@ -113,60 +162,79 @@ nsresult FileBlockCache::WriteBlock(uint
   EnsureWriteScheduled();
 
   return NS_OK;
 }
 
 void FileBlockCache::EnsureWriteScheduled()
 {
   mDataMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mIsOpen);
 
-  if (!mIsWriteScheduled) {
-    mThread->Dispatch(this, NS_DISPATCH_NORMAL);
-    mIsWriteScheduled = true;
+  if (mIsWriteScheduled) {
+    return;
   }
+  mIsWriteScheduled = true;
+
+  RefPtr<FileBlockCache> self = this;
+  mInitPromise->Then(mAbstractThread,
+                     __func__,
+                     [self](bool aValue) { self->Run(); },
+                     [self](nsresult rv) {}
+                     // Failure handled by EnsureInitialized.
+                     );
 }
 
 nsresult FileBlockCache::Seek(int64_t aOffset)
 {
   mFileMonitor.AssertCurrentThreadOwns();
 
   if (mFDCurrentPos != aOffset) {
+    MOZ_ASSERT(mFD);
     int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
     if (result != aOffset) {
       NS_WARNING("Failed to seek media cache file");
       return NS_ERROR_FAILURE;
     }
     mFDCurrentPos = result;
   }
   return NS_OK;
 }
 
 nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
                                       uint8_t* aDest,
                                       int32_t aBytesToRead,
                                       int32_t& aBytesRead)
 {
+  FBC_LOG(LogLevel::Debug,
+          ("FileBlockCache::ReadFromFile(offset=%" PRIu64 ", len=%u)",
+           aOffset,
+           aBytesToRead));
   mFileMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mFD);
 
   nsresult res = Seek(aOffset);
   if (NS_FAILED(res)) return res;
 
   aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
   if (aBytesRead <= 0)
     return NS_ERROR_FAILURE;
   mFDCurrentPos += aBytesRead;
 
   return NS_OK;
 }
 
 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
                                           const uint8_t* aBlockData)
 {
+  FBC_LOG(LogLevel::Debug,
+          ("FileBlockCache::WriteBlockToFile(index=%u)", aBlockIndex));
+
   mFileMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mFD);
 
   nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
   if (NS_FAILED(rv)) return rv;
 
   int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
   if (amount < BLOCK_SIZE) {
     NS_WARNING("Failed to write media cache block!");
     return NS_ERROR_FAILURE;
@@ -174,35 +242,44 @@ nsresult FileBlockCache::WriteBlockToFil
   mFDCurrentPos += BLOCK_SIZE;
 
   return NS_OK;
 }
 
 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
                                          int32_t aDestBlockIndex)
 {
+  FBC_LOG(LogLevel::Debug,
+          ("FileBlockCache::MoveBlockInFile(src=%u, dest=%u)",
+           aSourceBlockIndex,
+           aDestBlockIndex));
+
   mFileMonitor.AssertCurrentThreadOwns();
 
   uint8_t buf[BLOCK_SIZE];
   int32_t bytesRead = 0;
   if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
                              buf,
                              BLOCK_SIZE,
                              bytesRead))) {
     return NS_ERROR_FAILURE;
   }
   return WriteBlockToFile(aDestBlockIndex, buf);
 }
 
 nsresult FileBlockCache::Run()
 {
+  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   MonitorAutoLock mon(mDataMonitor);
-  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
   NS_ASSERTION(!mChangeIndexList.empty(), "Only dispatch when there's work to do");
   NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
+  MOZ_ASSERT(mFD);
+
+  FBC_LOG(LogLevel::Debug,
+          ("FileBlockCache::Run mFD=%p mIsOpen=%d", mFD, mIsOpen));
 
   while (!mChangeIndexList.empty()) {
     if (!mIsOpen) {
       // We've been closed, abort, discarding unwritten changes.
       mIsWriteScheduled = false;
       return NS_ERROR_FAILURE;
     }
 
@@ -247,17 +324,17 @@ nsresult FileBlockCache::Run()
 
 nsresult FileBlockCache::Read(int64_t aOffset,
                               uint8_t* aData,
                               int32_t aLength,
                               int32_t* aBytes)
 {
   MonitorAutoLock mon(mDataMonitor);
 
-  if (!mFD || (aOffset / BLOCK_SIZE) > INT32_MAX)
+  if (!mIsOpen || (aOffset / BLOCK_SIZE) > INT32_MAX)
     return NS_ERROR_FAILURE;
 
   int32_t bytesToRead = aLength;
   int64_t offset = aOffset;
   uint8_t* dst = aData;
   while (bytesToRead > 0) {
     int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
     int32_t start = offset % BLOCK_SIZE;
--- a/dom/media/FileBlockCache.h
+++ b/dom/media/FileBlockCache.h
@@ -4,17 +4,19 @@
  * 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 FILE_BLOCK_CACHE_H_
 #define FILE_BLOCK_CACHE_H_
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/AbstractThread.h"
 #include "nsTArray.h"
 #include "MediaCache.h"
 #include "nsDeque.h"
 #include "nsThreadUtils.h"
 #include <deque>
 
 struct PRFileDesc;
 
@@ -57,18 +59,17 @@ public:
   };
 
   FileBlockCache();
 
 protected:
   ~FileBlockCache();
 
 public:
-  // Assumes ownership of aFD.
-  nsresult Open(PRFileDesc* aFD);
+  nsresult Init();
 
   // Closes writer, shuts down thread.
   void Close();
 
   // Can be called on any thread. This defers to a non-main thread.
   nsresult WriteBlock(uint32_t aBlockIndex, const uint8_t* aData);
 
   // Performs block writes and block moves on its own thread.
@@ -125,16 +126,18 @@ public:
     }
   };
 
 private:
   int64_t BlockIndexToOffset(int32_t aBlockIndex) {
     return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE;
   }
 
+  void SetCacheFile(PRFileDesc* aFD);
+
   // Monitor which controls access to mFD and mFDCurrentPos. Don't hold
   // mDataMonitor while holding mFileMonitor! mFileMonitor must be owned
   // while accessing any of the following data fields or methods.
   Monitor mFileMonitor;
   // Moves a block already committed to file.
   nsresult MoveBlockInFile(int32_t aSourceBlockIndex,
                            int32_t aDestBlockIndex);
   // Seeks file pointer.
@@ -155,26 +158,35 @@ private:
   // and mFDCurrentPos. Don't hold mDataMonitor while holding mFileMonitor!
   // mDataMonitor must be owned while accessing any of the following data
   // fields or methods.
   Monitor mDataMonitor;
   // Ensures we either are running the event to preform IO, or an event
   // has been dispatched to preform the IO.
   // mDataMonitor must be owned while calling this.
   void EnsureWriteScheduled();
+  // Promise that tracks the request for an anonymous temporary file for the
+  // cache to store data into. The file descriptor must be requested from the
+  // parent process when the cache is initialized. While this promise is
+  // outstanding, the FileBlockCache buffers blocks in memory, and reads
+  // against the cache are serviced from the in-memory buffers.
+  RefPtr<GenericPromise::Private> mInitPromise;
+
   // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nullptr,
   // then the block has no pending changes to be written, but if
   // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block
   // cached in memory waiting to be written, or this block is the target of a
   // block move.
   nsTArray< RefPtr<BlockChange> > mBlockChanges;
   // Thread upon which block writes and block moves are performed. This is
   // created upon open, and shutdown (asynchronously) upon close (on the
   // main thread).
   nsCOMPtr<nsIThread> mThread;
+  // Wrapper for mThread.
+  RefPtr<AbstractThread> mAbstractThread;
   // Queue of pending block indexes that need to be written or moved.
   std::deque<int32_t> mChangeIndexList;
   // True if we've dispatched an event to commit all pending block changes
   // to file on mThread.
   bool mIsWriteScheduled;
   // True if the writer is ready to write data to file.
   bool mIsOpen;
 };
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -9,17 +9,16 @@
 #include "MediaCache.h"
 #include "prio.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "MediaResource.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "FileBlockCache.h"
-#include "nsAnonymousTemporaryFile.h"
 #include "nsIObserverService.h"
 #include "nsISeekableStream.h"
 #include "nsIPrincipal.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Services.h"
 #include <algorithm>
 
 namespace mozilla {
@@ -569,22 +568,18 @@ MediaCacheStream::BlockList::NotifyBlock
 }
 
 nsresult
 MediaCache::Init()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mFileCache, "Cache file already open?");
 
-  PRFileDesc* fileDesc = nullptr;
-  nsresult rv = NS_OpenAnonymousTemporaryFile(&fileDesc);
-  NS_ENSURE_SUCCESS(rv,rv);
-
   mFileCache = new FileBlockCache();
-  rv = mFileCache->Open(fileDesc);
+  nsresult rv = mFileCache->Init();
   NS_ENSURE_SUCCESS(rv,rv);
 
   MediaCacheFlusher::Init();
 
   return NS_OK;
 }
 
 void