Bug 1379091 - Let block cache tell MediaCache its block use limit - r?cpearce draft
authorGerald Squelart <gsquelart@mozilla.com>
Mon, 10 Jul 2017 10:23:02 +1200
changeset 605873 c93f2fcd51516d03cbd22c240e81033a4a5453b7
parent 605863 a418121d46250f91728b86d9eea331029c264c30
child 605874 c16cbdd7eb63c6628c5ac005c4aab9a8277cb280
push id67535
push usergsquelart@mozilla.com
push dateSun, 09 Jul 2017 23:34:56 +0000
reviewerscpearce
bugs1379091
milestone56.0a1
Bug 1379091 - Let block cache tell MediaCache its block use limit - r?cpearce MozReview-Commit-ID: 5ZCD3NoeYEP
dom/media/FileBlockCache.cpp
dom/media/FileBlockCache.h
dom/media/MediaBlockCacheBase.h
dom/media/MediaCache.cpp
dom/media/MemoryBlockCache.cpp
dom/media/MemoryBlockCache.h
--- a/dom/media/FileBlockCache.cpp
+++ b/dom/media/FileBlockCache.cpp
@@ -1,15 +1,17 @@
 /* -*- 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 "MediaCache.h"
+#include "MediaPrefs.h"
 #include "mozilla/SharedThreadPool.h"
 #include "VideoUtils.h"
 #include "prio.h"
 #include <algorithm>
 #include "nsAnonymousTemporaryFile.h"
 #include "mozilla/dom/ContentChild.h"
 #include "nsXULAppAPI.h"
 
@@ -116,16 +118,43 @@ FileBlockCache::Init()
 
   if (NS_FAILED(rv)) {
     Close();
   }
 
   return rv;
 }
 
+int32_t
+FileBlockCache::GetMaxBlocks() const
+{
+  // We look up the cache size every time. This means dynamic changes
+  // to the pref are applied.
+  const uint32_t cacheSizeKb =
+    std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
+  // Ensure we can divide BLOCK_SIZE by 1024.
+  static_assert(MediaCacheStream::BLOCK_SIZE % 1024 == 0,
+                "BLOCK_SIZE should be a multiple of 1024");
+  // Ensure BLOCK_SIZE/1024 is at least 2.
+  static_assert(MediaCacheStream::BLOCK_SIZE / 1024 >= 2,
+                "BLOCK_SIZE / 1024 should be at least 2");
+  // Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
+  static_assert(MediaCacheStream::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
+                "BLOCK_SIZE / 1024 should be at most UINT32_MAX");
+  // Since BLOCK_SIZE is a strict multiple of 1024,
+  // cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
+  // but the latter formula avoids a potential overflow from `* 1024`.
+  // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
+  // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
+  constexpr uint32_t blockSizeKb =
+    uint32_t(MediaCacheStream::BLOCK_SIZE / 1024);
+  const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
+  return std::max(maxBlocks, int32_t(1));
+}
+
 FileBlockCache::FileBlockCache()
   : mFileMutex("MediaCache.Writer.IO.Mutex")
   , mFD(nullptr)
   , mFDCurrentPos(0)
   , mDataMutex("MediaCache.Writer.Data.Mutex")
   , mIsWriteScheduled(false)
   , mIsReading(false)
 {
--- a/dom/media/FileBlockCache.h
+++ b/dom/media/FileBlockCache.h
@@ -60,16 +60,20 @@ public:
 protected:
   virtual ~FileBlockCache();
 
 public:
   // Launch thread and open temporary file.
   // If re-initializing, just discard pending writes if any.
   nsresult Init() override;
 
+  // Maximum number of blocks allowed in this block cache.
+  // Calculated from "media.cache_size" pref.
+  int32_t GetMaxBlocks() const override;
+
   // Can be called on any thread. This defers to a non-main thread.
   nsresult WriteBlock(uint32_t aBlockIndex,
                       Span<const uint8_t> aData1,
                       Span<const uint8_t> aData2) override;
 
   // Synchronously reads data from file. May read from file or memory
   // depending on whether written blocks have been flushed to file yet.
   // Not recommended to be called from the main thread, as can cause jank.
--- a/dom/media/MediaBlockCacheBase.h
+++ b/dom/media/MediaBlockCacheBase.h
@@ -47,16 +47,20 @@ public:
 protected:
   virtual ~MediaBlockCacheBase() {}
 
 public:
   // Initialize this cache.
   // If called again, re-initialize cache with minimal chance of failure.
   virtual nsresult Init() = 0;
 
+  // Maximum number of blocks expected in this block cache. (But allow overflow
+  // to accomodate incoming traffic before MediaCache can handle it.)
+  virtual int32_t GetMaxBlocks() const = 0;
+
   // Can be called on any thread. This defers to a non-main thread.
   virtual nsresult WriteBlock(uint32_t aBlockIndex,
                               Span<const uint8_t> aData1,
                               Span<const uint8_t> aData2) = 0;
 
   // Synchronously reads data from file. May read from file or memory
   // depending on whether written blocks have been flushed to file yet.
   // Not recommended to be called from the main thread, as can cause jank.
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -3,16 +3,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/. */
 
 #include "MediaCache.h"
 
 #include "FileBlockCache.h"
 #include "MediaBlockCacheBase.h"
+#include "MediaPrefs.h"
 #include "MediaResource.h"
 #include "MemoryBlockCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
@@ -732,41 +733,16 @@ MediaCache::ReadCacheFile(
     // Since the monitor might be acquired on the main thread, we need to drop
     // the monitor while doing IO in order not to block the main thread.
     ReentrantMonitorAutoExit unlock(mReentrantMonitor);
     return blockCache->Read(
       aOffset, reinterpret_cast<uint8_t*>(aData), aLength, aBytes);
   }
 }
 
-static int32_t GetMaxBlocks()
-{
-  // We look up the cache size every time. This means dynamic changes
-  // to the pref are applied.
-  const uint32_t cacheSizeKb =
-    std::min(MediaPrefs::MediaCacheSizeKb(), uint32_t(INT32_MAX) * 2);
-  // Ensure we can divide BLOCK_SIZE by 1024.
-  static_assert(MediaCache::BLOCK_SIZE % 1024 == 0,
-                "BLOCK_SIZE should be a multiple of 1024");
-  // Ensure BLOCK_SIZE/1024 is at least 2.
-  static_assert(MediaCache::BLOCK_SIZE / 1024 >= 2,
-                "BLOCK_SIZE / 1024 should be at least 2");
-  // Ensure we can convert BLOCK_SIZE/1024 to a uint32_t without truncation.
-  static_assert(MediaCache::BLOCK_SIZE / 1024 <= int64_t(UINT32_MAX),
-                "BLOCK_SIZE / 1024 should be at most UINT32_MAX");
-  // Since BLOCK_SIZE is a strict multiple of 1024,
-  // cacheSizeKb * 1024 / BLOCK_SIZE == cacheSizeKb / (BLOCK_SIZE / 1024),
-  // but the latter formula avoids a potential overflow from `* 1024`.
-  // And because BLOCK_SIZE/1024 is at least 2, the maximum cache size
-  // INT32_MAX*2 will give a maxBlocks that can fit in an int32_t.
-  constexpr uint32_t blockSizeKb = uint32_t(MediaCache::BLOCK_SIZE / 1024);
-  const int32_t maxBlocks = int32_t(cacheSizeKb / blockSizeKb);
-  return std::max(maxBlocks, int32_t(1));
-}
-
 // Allowed range is whatever can be accessed with an int32_t block index.
 static bool
 IsOffsetAllowed(int64_t aOffset)
 {
   return aOffset < (int64_t(INT32_MAX) + 1) * MediaCache::BLOCK_SIZE &&
          aOffset >= 0;
 }
 
@@ -813,18 +789,20 @@ MediaCache::FindBlockForIncomingData(Tim
                       INT32_MAX);
 
   if (blockIndex < 0 || !IsBlockFree(blockIndex)) {
     // The block returned is already allocated.
     // Don't reuse it if a) there's room to expand the cache or
     // b) the data we're going to store in the free block is not higher
     // priority than the data already stored in the free block.
     // The latter can lead us to go over the cache limit a bit.
-    if ((mIndex.Length() < uint32_t(GetMaxBlocks()) || blockIndex < 0 ||
-         PredictNextUseForIncomingData(aStream) >= PredictNextUse(aNow, blockIndex))) {
+    if ((mIndex.Length() < uint32_t(mBlockCache->GetMaxBlocks()) ||
+         blockIndex < 0 ||
+         PredictNextUseForIncomingData(aStream) >=
+           PredictNextUse(aNow, blockIndex))) {
       blockIndex = mIndex.Length();
       if (!mIndex.AppendElement())
         return -1;
       mIndexWatermark = std::max(mIndexWatermark, blockIndex + 1);
       mFreeBlocks.AddFirstBlock(blockIndex);
       return blockIndex;
     }
   }
@@ -1158,17 +1136,17 @@ MediaCache::Update()
 
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mUpdateQueued = false;
 #ifdef DEBUG
     mInUpdate = true;
 #endif
 
-    int32_t maxBlocks = GetMaxBlocks();
+    int32_t maxBlocks = mBlockCache->GetMaxBlocks();
     TimeStamp now = TimeStamp::Now();
 
     int32_t freeBlockCount = mFreeBlocks.GetCount();
     TimeDuration latestPredictedUseForOverflow = 0;
     if (mIndex.Length() > uint32_t(maxBlocks)) {
       // Try to trim back the cache to its desired maximum size. The cache may
       // have overflowed simply due to data being received when we have
       // no blocks in the main part of the cache that are free or lower
--- a/dom/media/MemoryBlockCache.cpp
+++ b/dom/media/MemoryBlockCache.cpp
@@ -132,19 +132,33 @@ enum MemoryBlockCacheTelemetryErrors
   ReadOverrun = 2,
   WriteBlockOverflow = 3,
   WriteBlockCannotGrow = 4,
   MoveBlockSourceOverrun = 5,
   MoveBlockDestOverflow = 6,
   MoveBlockCannotGrow = 7,
 };
 
+static int32_t
+CalculateMaxBlocks(int64_t aContentLength)
+{
+  // Note: It doesn't matter if calculations overflow, Init() would later fail.
+  // We want at least enough blocks to contain the original content length.
+  const int32_t requiredBlocks =
+    int32_t((aContentLength - 1) / MediaBlockCacheBase::BLOCK_SIZE + 1);
+  // Allow at least 1s of ultra HD (25Mbps).
+  const int32_t workableBlocks =
+    25 * 1024 * 1024 / 8 / MediaBlockCacheBase::BLOCK_SIZE;
+  return std::max(requiredBlocks, workableBlocks);
+}
+
 MemoryBlockCache::MemoryBlockCache(int64_t aContentLength)
   // Buffer whole blocks.
   : mInitialContentLength((aContentLength >= 0) ? size_t(aContentLength) : 0)
+  , mMaxBlocks(CalculateMaxBlocks(aContentLength))
   , mMutex("MemoryBlockCache")
   , mHasGrown(false)
 {
   if (aContentLength <= 0) {
     LOG("MemoryBlockCache() MEMORYBLOCKCACHE_ERRORS='InitUnderuse'");
     Telemetry::Accumulate(Telemetry::HistogramID::MEMORYBLOCKCACHE_ERRORS,
                           InitUnderuse);
   }
--- a/dom/media/MemoryBlockCache.h
+++ b/dom/media/MemoryBlockCache.h
@@ -37,16 +37,20 @@ public:
 protected:
   virtual ~MemoryBlockCache();
 
 public:
   // Allocate initial buffer.
   // If re-initializing, clear buffer.
   virtual nsresult Init() override;
 
+  // Maximum number of blocks allowed in this block cache.
+  // Based on initial content length, and minimum usable block cache.
+  int32_t GetMaxBlocks() const override { return mMaxBlocks; }
+
   // Can be called on any thread.
   virtual nsresult WriteBlock(uint32_t aBlockIndex,
                               Span<const uint8_t> aData1,
                               Span<const uint8_t> aData2) override;
 
   // Synchronously reads data from buffer.
   virtual nsresult Read(int64_t aOffset,
                         uint8_t* aData,
@@ -66,16 +70,19 @@ private:
   // Ensure the buffer has at least a multiple of BLOCK_SIZE that can contain
   // aContentLength bytes. Buffer size can only grow.
   // Returns false if allocation failed.
   bool EnsureBufferCanContain(size_t aContentLength);
 
   // Initial content length.
   const size_t mInitialContentLength;
 
+  // Maximum number of blocks that this MemoryBlockCache expects.
+  const int32_t mMaxBlocks;
+
   // Mutex which controls access to all members below.
   Mutex mMutex;
 
   nsTArray<uint8_t> mBuffer;
   bool mHasGrown;
 };
 
 } // End namespace mozilla.