Bug 736342 - Defer media cache writes and block moves to a non-main thread. r=roc
authorChris Pearce <cpearce@mozilla.com>
Wed, 28 Mar 2012 13:04:26 +1300
changeset 90603 b604d4e1916d87373f06b4209d7f0cb672c4fa17
parent 90602 b564e3f1ffe0563dda0f32880f6ad99ce4e956a0
child 90604 55a95cfe5ab902ac4e03043a0c927a61681bd44c
push id22366
push usermak77@bonardo.net
push dateThu, 29 Mar 2012 15:38:30 +0000
treeherdermozilla-central@ff3521bc6559 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs736342
milestone14.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 736342 - Defer media cache writes and block moves to a non-main thread. r=roc
content/media/nsMediaCache.cpp
--- a/content/media/nsMediaCache.cpp
+++ b/content/media/nsMediaCache.cpp
@@ -46,16 +46,17 @@
 #include "nsNetUtil.h"
 #include "prio.h"
 #include "nsThreadUtils.h"
 #include "MediaResource.h"
 #include "nsMathUtils.h"
 #include "prlog.h"
 #include "nsIPrivateBrowsingService.h"
 #include "mozilla/Preferences.h"
+#include "FileBlockCache.h"
 
 using namespace mozilla;
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gMediaCacheLog;
 #define LOG(type, msg) PR_LOG(gMediaCacheLog, type, msg)
 #else
 #define LOG(type, msg)
@@ -131,30 +132,29 @@ public:
   friend class nsMediaCacheStream::BlockList;
   typedef nsMediaCacheStream::BlockList BlockList;
   enum {
     BLOCK_SIZE = nsMediaCacheStream::BLOCK_SIZE
   };
 
   nsMediaCache() : mNextResourceID(1),
     mReentrantMonitor("nsMediaCache.mReentrantMonitor"),
-    mFD(nsnull), mFDCurrentPos(0), mUpdateQueued(false)
+    mUpdateQueued(false)
 #ifdef DEBUG
     , mInUpdate(false)
 #endif
   {
     MOZ_COUNT_CTOR(nsMediaCache);
   }
   ~nsMediaCache() {
     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     Truncate();
     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
-    if (mFD) {
-      PR_Close(mFD);
-    }
+    mFileCache->Close();
+    mFileCache = nsnull;
     MOZ_COUNT_DTOR(nsMediaCache);
   }
 
   // Main thread only. Creates the backing cache file. If this fails,
   // then the cache is still in a semi-valid state; mFD will be null,
   // so all I/O on the cache file will fail.
   nsresult Init();
   // Shut down the global cache if it's no longer needed. We shut down
@@ -170,18 +170,16 @@ public:
 
   // Cache-file access methods. These are the lowest-level cache methods.
   // mReentrantMonitor must be held; these can be called on any thread.
   // This can return partial reads.
   nsresult ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
                          PRInt32* aBytes);
   // This will fail if all aLength bytes are not read
   nsresult ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength);
-  // This will fail if all aLength bytes are not written
-  nsresult WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength);
 
   PRInt64 AllocateResourceID()
   {
     mReentrantMonitor.AssertCurrentThreadIn();
     return mNextResourceID++;
   }
 
   // mReentrantMonitor must be held, called on main thread.
@@ -355,21 +353,18 @@ protected:
   nsTArray<nsMediaCacheStream*> mStreams;
 
   // The monitor protects all the data members here. Also, off-main-thread
   // readers that need to block will Wait() on this monitor. When new
   // data becomes available in the cache, we NotifyAll() on this monitor.
   ReentrantMonitor         mReentrantMonitor;
   // The Blocks describing the cache entries.
   nsTArray<Block> mIndex;
-  // The file descriptor of the cache file. The file will be deleted
-  // by the operating system when this is closed.
-  PRFileDesc*     mFD;
-  // The current file offset in the cache file.
-  PRInt64         mFDCurrentPos;
+  // Writer which performs IO, asynchronously writing cache blocks.
+  nsRefPtr<FileBlockCache> mFileCache;
   // The list of free blocks; they are not ordered.
   BlockList       mFreeBlocks;
   // True if an event to run Update() has been queued but not processed
   bool            mUpdateQueued;
 #ifdef DEBUG
   bool            mInUpdate;
 #endif
 };
@@ -545,17 +540,17 @@ nsMediaCacheStream::BlockList::NotifyBlo
     e2->mPrevBlock = e2Prev;
   }
 }
 
 nsresult
 nsMediaCache::Init()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-  NS_ASSERTION(!mFD, "Cache file already open?");
+  NS_ASSERTION(!mFileCache, "Cache file already open?");
 
   // In single process Gecko, store the media cache in the profile directory
   // so that multiple users can use separate media caches concurrently.
   // In multi-process Gecko, there is no profile dir, so just store it in the
   // system temp directory instead.
   nsresult rv;
   nsCOMPtr<nsIFile> tmp;
   const char* dir = (XRE_GetProcessType() == GeckoProcessType_Content) ?
@@ -588,18 +583,23 @@ nsMediaCache::Init()
   }
 
   rv = tmpFile->AppendNative(nsDependentCString("media_cache"));
   NS_ENSURE_SUCCESS(rv,rv);
 
   rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
   NS_ENSURE_SUCCESS(rv,rv);
 
+  PRFileDesc* fileDesc = nsnull;
   rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsILocalFile::DELETE_ON_CLOSE,
-                                 PR_IRWXU, &mFD);
+                                 PR_IRWXU, &fileDesc);
+  NS_ENSURE_SUCCESS(rv,rv);
+
+  mFileCache = new FileBlockCache();
+  rv = mFileCache->Open(fileDesc);
   NS_ENSURE_SUCCESS(rv,rv);
 
 #ifdef PR_LOGGING
   if (!gMediaCacheLog) {
     gMediaCacheLog = PR_NewLogModule("nsMediaCache");
   }
 #endif
 
@@ -626,19 +626,19 @@ nsMediaCache::FlushInternal()
 
   for (PRUint32 blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
     FreeBlock(blockIndex);
   }
 
   // Truncate file, close it, and reopen
   Truncate();
   NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
-  if (mFD) {
-    PR_Close(mFD);
-    mFD = nsnull;
+  if (mFileCache) {
+    mFileCache->Close();
+    mFileCache = nsnull;
   }
   Init();
 }
 
 void
 nsMediaCache::MaybeShutdown()
 {
   NS_ASSERTION(NS_IsMainThread(),
@@ -674,31 +674,20 @@ InitMediaCache()
 }
 
 nsresult
 nsMediaCache::ReadCacheFile(PRInt64 aOffset, void* aData, PRInt32 aLength,
                             PRInt32* aBytes)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
-  if (!mFD)
+  if (!mFileCache)
     return NS_ERROR_FAILURE;
 
-  if (mFDCurrentPos != aOffset) {
-    PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
-    if (offset != aOffset)
-      return NS_ERROR_FAILURE;
-    mFDCurrentPos = aOffset;
-  }
-  PRInt32 amount = PR_Read(mFD, aData, aLength);
-  if (amount <= 0)
-    return NS_ERROR_FAILURE;
-  mFDCurrentPos += amount;
-  *aBytes = amount;
-  return NS_OK;
+  return mFileCache->Read(aOffset, reinterpret_cast<PRUint8*>(aData), aLength, aBytes);
 }
 
 nsresult
 nsMediaCache::ReadCacheFileAllBytes(PRInt64 aOffset, void* aData, PRInt32 aLength)
 {
   mReentrantMonitor.AssertCurrentThreadIn();
 
   PRInt64 offset = aOffset;
@@ -714,45 +703,16 @@ nsMediaCache::ReadCacheFileAllBytes(PRIn
       return NS_ERROR_FAILURE;
     count -= bytes;
     data += bytes;
     offset += bytes;
   }
   return NS_OK;
 }
 
-nsresult
-nsMediaCache::WriteCacheFile(PRInt64 aOffset, const void* aData, PRInt32 aLength)
-{
-  mReentrantMonitor.AssertCurrentThreadIn();
-
-  if (!mFD)
-    return NS_ERROR_FAILURE;
-
-  if (mFDCurrentPos != aOffset) {
-    PROffset64 offset = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
-    if (offset != aOffset)
-      return NS_ERROR_FAILURE;
-    mFDCurrentPos = aOffset;
-  }
-
-  const char* data = static_cast<const char*>(aData);
-  PRInt32 length = aLength;
-  while (length > 0) {
-    PRInt32 amount = PR_Write(mFD, data, length);
-    if (amount <= 0)
-      return NS_ERROR_FAILURE;
-    mFDCurrentPos += amount;
-    length -= amount;
-    data += amount;
-  }
-
-  return NS_OK;
-}
-
 static PRInt32 GetMaxBlocks()
 {
   // We look up the cache size every time. This means dynamic changes
   // to the pref are applied.
   // Cache size is in KB
   PRInt32 cacheSize = Preferences::GetInt("media.cache_size", 500*1024);
   PRInt64 maxBlocks = static_cast<PRInt64>(cacheSize)*1024/nsMediaCache::BLOCK_SIZE;
   maxBlocks = NS_MAX<PRInt64>(maxBlocks, 1);
@@ -1159,37 +1119,28 @@ nsMediaCache::Update()
         // place any more overflow blocks.
         break;
       }
 
       if (IsBlockFree(destinationBlockIndex) ||
           PredictNextUse(now, destinationBlockIndex) > latestPredictedUseForOverflow) {
         // Reuse blocks in the main part of the cache that are less useful than
         // the least useful overflow blocks
-        char buf[BLOCK_SIZE];
-        nsresult rv = ReadCacheFileAllBytes(blockIndex*BLOCK_SIZE, buf, sizeof(buf));
+
+        nsresult rv = mFileCache->MoveBlock(blockIndex, destinationBlockIndex);
+
         if (NS_SUCCEEDED(rv)) {
-          rv = WriteCacheFile(destinationBlockIndex*BLOCK_SIZE, buf, BLOCK_SIZE);
-          if (NS_SUCCEEDED(rv)) {
-            // We successfully copied the file data.
-            LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
-                blockIndex, destinationBlockIndex));
-            // Swapping the block metadata here lets us maintain the
-            // correct positions in the linked lists
-            SwapBlocks(blockIndex, destinationBlockIndex);
-          } else {
-            // If the write fails we may have corrupted the destination
-            // block. Free it now.
-            LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
-                destinationBlockIndex));
-            FreeBlock(destinationBlockIndex);
-          }
-          // Free the overflowing block even if the copy failed.
-          LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)",
-              blockIndex));
+          // We successfully copied the file data.
+          LOG(PR_LOG_DEBUG, ("Swapping blocks %d and %d (trimming cache)",
+              blockIndex, destinationBlockIndex));
+          // Swapping the block metadata here lets us maintain the
+          // correct positions in the linked lists
+          SwapBlocks(blockIndex, destinationBlockIndex);
+          //Free the overflowing block even if the copy failed.
+          LOG(PR_LOG_DEBUG, ("Released block %d (trimming cache)", blockIndex));
           FreeBlock(blockIndex);
         }
       } else {
         LOG(PR_LOG_DEBUG, ("Could not trim cache block %d (destination %d, predicted next use %f, latest predicted use for overflow %f",
                            blockIndex, destinationBlockIndex,
                            PredictNextUse(now, destinationBlockIndex).ToSeconds(),
                            latestPredictedUseForOverflow.ToSeconds()));
       }
@@ -1558,17 +1509,17 @@ nsMediaCache::AllocateAndWriteBlock(nsMe
         // This may not be the latest readahead block, although it usually
         // will be. We may have to scan for the right place to insert
         // the block in the list.
         bo->mClass = READAHEAD_BLOCK;
         InsertReadaheadBlock(bo, blockIndex);
       }
     }
 
-    nsresult rv = WriteCacheFile(blockIndex*BLOCK_SIZE, aData, BLOCK_SIZE);
+    nsresult rv = mFileCache->WriteBlock(blockIndex, reinterpret_cast<const PRUint8*>(aData));
     if (NS_FAILED(rv)) {
       LOG(PR_LOG_DEBUG, ("Released block %d from stream %p block %d(%lld)",
           blockIndex, aStream, streamBlockIndex, (long long)streamBlockIndex*BLOCK_SIZE));
       FreeBlock(blockIndex);
     }
   }
 
   // Queue an Update since the cache state has changed (for example