Bug 1540573 - P4. Use larger MediaCache sizes when on cellular connection. r=jya
authorChris Pearce <cpearce@mozilla.com>
Fri, 03 May 2019 02:44:05 +0000
changeset 531231 3db059b34e40b810394a4ef355534fe707504699
parent 531230 fe196b2dfc6247bd8f9ec102aeb9927fffc1af87
child 531232 270a8917377f46e18a9948c3335bfbd9b8192449
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1540573
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 1540573 - P4. Use larger MediaCache sizes when on cellular connection. r=jya Differential Revision: https://phabricator.services.mozilla.com/D26233
dom/media/FileBlockCache.cpp
dom/media/FileBlockCache.h
dom/media/MediaBlockCacheBase.h
dom/media/MediaCache.cpp
dom/media/MemoryBlockCache.h
modules/libpref/init/StaticPrefList.h
--- a/dom/media/FileBlockCache.cpp
+++ b/dom/media/FileBlockCache.cpp
@@ -120,39 +120,36 @@ void FileBlockCache::Flush() {
     MutexAutoLock mon(self->mDataMutex);
     // Just discard pending changes, assume MediaCache won't read from
     // blocks it hasn't written to.
     self->mChangeIndexList.clear();
     self->mBlockChanges.Clear();
   }));
 }
 
-int32_t FileBlockCache::GetMaxBlocks() const {
+size_t FileBlockCache::GetMaxBlocks(size_t aCacheSizeInKB) const {
   // We look up the cache size every time. This means dynamic changes
   // to the pref are applied.
-  const uint32_t cacheSizeKb =
-      std::min(StaticPrefs::MediaCacheSize(), 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`.
+  // aCacheSizeInKB * 1024 / BLOCK_SIZE == aCacheSizeInKB / (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));
+  constexpr size_t blockSizeKb = size_t(MediaCacheStream::BLOCK_SIZE / 1024);
+  const size_t maxBlocks = aCacheSizeInKB / blockSizeKb;
+  return std::max(maxBlocks, size_t(1));
 }
 
 FileBlockCache::FileBlockCache()
     : mFileMutex("MediaCache.Writer.IO.Mutex"),
       mFD(nullptr),
       mFDCurrentPos(0),
       mDataMutex("MediaCache.Writer.Data.Mutex"),
       mIsWriteScheduled(false),
--- a/dom/media/FileBlockCache.h
+++ b/dom/media/FileBlockCache.h
@@ -63,17 +63,17 @@ class FileBlockCache : public MediaBlock
   // Launch thread and open temporary file.
   nsresult Init() override;
 
   // Will discard pending changes if any.
   void Flush() override;
 
   // Maximum number of blocks allowed in this block cache.
   // Calculated from "media.cache_size" pref.
-  int32_t GetMaxBlocks() const override;
+  size_t GetMaxBlocks(size_t aCacheSizeInKB) 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
@@ -51,17 +51,17 @@ class MediaBlockCacheBase {
   virtual nsresult Init() = 0;
 
   // Erase data and discard pending changes to reset the cache to its pristine
   // state as it was after Init().
   virtual void Flush() = 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;
+  virtual size_t GetMaxBlocks(size_t aCacheSizeInKiB) 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
@@ -6,34 +6,37 @@
 
 #include "MediaCache.h"
 
 #include "ChannelMediaResource.h"
 #include "FileBlockCache.h"
 #include "MediaBlockCacheBase.h"
 #include "MediaResource.h"
 #include "MemoryBlockCache.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Telemetry.h"
 #include "nsContentUtils.h"
+#include "nsINetworkLinkService.h"
 #include "nsIObserverService.h"
 #include "nsIPrincipal.h"
 #include "nsPrintfCString.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "prio.h"
+#include "VideoUtils.h"
 #include <algorithm>
 
 namespace mozilla {
 
 #undef LOG
 #undef LOGI
 #undef LOGE
 LazyLogModule gMediaCacheLog("MediaCache");
@@ -109,24 +112,27 @@ StaticRefPtr<MediaCacheFlusher> MediaCac
 NS_IMPL_ISUPPORTS(MediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
 
 /* static */
 void MediaCacheFlusher::RegisterMediaCache(MediaCache* aMediaCache) {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   if (!gMediaCacheFlusher) {
     gMediaCacheFlusher = new MediaCacheFlusher();
-
     nsCOMPtr<nsIObserverService> observerService =
         mozilla::services::GetObserverService();
     if (observerService) {
       observerService->AddObserver(gMediaCacheFlusher, "last-pb-context-exited",
                                    true);
       observerService->AddObserver(gMediaCacheFlusher,
                                    "cacheservice:empty-cache", true);
+      observerService->AddObserver(
+          gMediaCacheFlusher, "contentchild:network-link-type-changed", true);
+      observerService->AddObserver(gMediaCacheFlusher,
+                                   NS_NETWORK_LINK_TYPE_TOPIC, true);
     }
   }
 
   gMediaCacheFlusher->mMediaCaches.AppendElement(aMediaCache);
 }
 
 /* static */
 void MediaCacheFlusher::UnregisterMediaCache(MediaCache* aMediaCache) {
@@ -237,16 +243,20 @@ class MediaCache {
 
   mozilla::Monitor& Monitor() {
     // This method should only be called outside the main thread.
     // The MOZ_DIAGNOSTIC_ASSERT(!NS_IsMainThread()) assertion should be
     // re-added as part of bug 1464045
     return mMonitor;
   }
 
+  // Updates the cache size, readahead limit, and resume threshold, based on
+  // whether we're on a cellular connection or not. Main thread only.
+  static void UpdateGeometryStatics();
+
   /**
    * An iterator that makes it easy to iterate through all streams that
    * have a given resource ID and are not closed.
    * Must be used while holding the media cache lock.
    */
   class ResourceStreamIterator {
    public:
     ResourceStreamIterator(MediaCache* aMediaCache, int64_t aResourceID)
@@ -277,16 +287,17 @@ class MediaCache {
 #ifdef DEBUG
         ,
         mInUpdate(false)
 #endif
   {
     NS_ASSERTION(NS_IsMainThread(), "Only construct MediaCache on main thread");
     MOZ_COUNT_CTOR(MediaCache);
     MediaCacheFlusher::RegisterMediaCache(this);
+    UpdateGeometryStatics();
   }
 
   ~MediaCache() {
     NS_ASSERTION(NS_IsMainThread(), "Only destroy MediaCache on main thread");
     if (this == gMediaCache) {
       LOG("~MediaCache(Global file-backed MediaCache)");
       // This is the file-backed MediaCache, reset the global pointer.
       gMediaCache = nullptr;
@@ -308,16 +319,31 @@ class MediaCache {
     MediaCacheFlusher::UnregisterMediaCache(this);
     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     Truncate();
     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
 
     MOZ_COUNT_DTOR(MediaCache);
   }
 
+  static size_t CacheSize() {
+    MOZ_ASSERT(sThread->IsOnCurrentThread());
+    return sCacheSizeInKB;
+  }
+
+  static size_t ReadaheadLimit() {
+    MOZ_ASSERT(sThread->IsOnCurrentThread());
+    return sReadaheadLimit;
+  }
+
+  static size_t ResumeThreshold() {
+    MOZ_ASSERT(sThread->IsOnCurrentThread());
+    return sResumeThreshold;
+  }
+
   // Find a free or reusable block and return its index. If there are no
   // free blocks and no reusable blocks, add a new block to the cache
   // and return it. Can return -1 on OOM.
   int32_t FindBlockForIncomingData(AutoLock&, TimeStamp aNow,
                                    MediaCacheStream* aStream,
                                    int32_t aStreamBlockIndex);
   // Find a reusable block --- a free block, if there is one, otherwise
   // the reusable block with the latest predicted-next-use, or -1 if
@@ -446,16 +472,21 @@ class MediaCache {
   nsTArray<int64_t> mSuspendedStatusToNotify;
   // The thread on which we will run data callbacks from the channels.
   // Note this thread is shared among all MediaCache instances.
   static StaticRefPtr<nsIThread> sThread;
   // True if we've tried to init sThread. Note we try once only so it is safe
   // to access sThread on all threads.
   static bool sThreadInit;
 
+  // Accesson MediaCache thread only.
+  static size_t sCacheSizeInKB;
+  static size_t sReadaheadLimit;
+  static size_t sResumeThreshold;
+
  private:
   // Used by MediaCacheStream::GetDebugInfo() only for debugging.
   // Don't add new callers to this function.
   friend nsCString MediaCacheStream::GetDebugInfo();
   mozilla::Monitor& GetMonitorOnTheMainThread() {
     MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
     return mMonitor;
   }
@@ -465,16 +496,47 @@ class MediaCache {
 /* static */
 MediaCache* MediaCache::gMediaCache;
 
 /* static */
 StaticRefPtr<nsIThread> MediaCache::sThread;
 /* static */
 bool MediaCache::sThreadInit = false;
 
+/* static */ size_t MediaCache::sCacheSizeInKB = 0;
+/* static */ size_t MediaCache::sReadaheadLimit = 0;
+/* static */ size_t MediaCache::sResumeThreshold = 0;
+
+void MediaCache::UpdateGeometryStatics() {
+  NS_ASSERTION(NS_IsMainThread(),
+               "Only call on main thread");  // JNI required on Android...
+  bool cacheAggressively = !OnCellularConnection();
+  LOG("MediaCache::UpdateGeometryStatics() cacheAggressively=%d",
+      cacheAggressively);
+  // Read the prefs on the main thread, and post their value to the MediaCache
+  // thread. This ensures the values are synchronized.
+  uint32_t cacheSize = 0, readaheadLimit = 0, resumeThreshold = 0;
+  if (cacheAggressively) {
+    cacheSize = StaticPrefs::MediaCacheSize();
+    readaheadLimit = StaticPrefs::MediaCacheReadaheadLimit();
+    resumeThreshold = StaticPrefs::MediaCacheResumeThreshold();
+  } else {
+    cacheSize = StaticPrefs::MediaCacheCellularSize();
+    readaheadLimit = StaticPrefs::MediaCacheCellularReadaheadLimit();
+    resumeThreshold = StaticPrefs::MediaCacheCellularResumeThreshold();
+  }
+  nsCOMPtr<nsIRunnable> r =
+      NS_NewRunnableFunction("MediaCache::UpdateGeometryStatics", [=]() {
+        sCacheSizeInKB = cacheSize;
+        sReadaheadLimit = readaheadLimit;
+        sResumeThreshold = resumeThreshold;
+      });
+  sThread->Dispatch(r.forget());
+}
+
 NS_IMETHODIMP
 MediaCacheFlusher::Observe(nsISupports* aSubject, char const* aTopic,
                            char16_t const* aData) {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
   if (strcmp(aTopic, "last-pb-context-exited") == 0) {
     for (MediaCache* mc : mMediaCaches) {
       mc->CloseStreamsForPrivateBrowsing();
@@ -482,16 +544,20 @@ MediaCacheFlusher::Observe(nsISupports* 
     return NS_OK;
   }
   if (strcmp(aTopic, "cacheservice:empty-cache") == 0) {
     for (MediaCache* mc : mMediaCaches) {
       mc->Flush();
     }
     return NS_OK;
   }
+  if (strcmp(aTopic, "contentchild:network-link-type-changed") == 0 ||
+      strcmp(aTopic, NS_NETWORK_LINK_TYPE_TOPIC) == 0) {
+    MediaCache::UpdateGeometryStatics();
+  }
   return NS_OK;
 }
 
 MediaCacheStream::MediaCacheStream(ChannelMediaResource* aClient,
                                    bool aIsPrivateBrowsing)
     : mMediaCache(nullptr),
       mClient(aClient),
       mIsTransportSeekable(false),
@@ -822,17 +888,18 @@ int32_t MediaCache::FindBlockForIncoming
       FindReusableBlock(aLock, aNow, aStream, aStreamBlockIndex, 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(mBlockCache->GetMaxBlocks()) ||
+    if ((mIndex.Length() <
+             uint32_t(mBlockCache->GetMaxBlocks(MediaCache::CacheSize())) ||
          blockIndex < 0 ||
          PredictNextUseForIncomingData(aLock, aStream) >=
              PredictNextUse(aLock, aNow, blockIndex))) {
       blockIndex = mIndex.Length();
       if (!mIndex.AppendElement()) return -1;
       mIndexWatermark = std::max(mIndexWatermark, blockIndex + 1);
       mFreeBlocks.AddFirstBlock(blockIndex);
       return blockIndex;
@@ -1150,17 +1217,17 @@ void MediaCache::Update() {
   // stream, decoder and element code.
   AutoTArray<StreamAction, 10> actions;
 
   mUpdateQueued = false;
 #ifdef DEBUG
   mInUpdate = true;
 #endif
 
-  int32_t maxBlocks = mBlockCache->GetMaxBlocks();
+  int32_t maxBlocks = mBlockCache->GetMaxBlocks(MediaCache::CacheSize());
   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
@@ -1271,18 +1338,18 @@ void MediaCache::Update() {
   TimeDuration latestNextUse;
   if (freeBlockCount == 0) {
     int32_t reusableBlock = FindReusableBlock(lock, now, nullptr, 0, maxBlocks);
     if (reusableBlock >= 0) {
       latestNextUse = PredictNextUse(lock, now, reusableBlock);
     }
   }
 
-  int32_t resumeThreshold = StaticPrefs::MediaCacheResumeThreshold();
-  int32_t readaheadLimit = StaticPrefs::MediaCacheReadaheadLimit();
+  int32_t resumeThreshold = MediaCache::ResumeThreshold();
+  int32_t readaheadLimit = MediaCache::ReadaheadLimit();
 
   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     actions.AppendElement(StreamAction{});
 
     MediaCacheStream* stream = mStreams[i];
     if (stream->mClosed) {
       LOG("Stream %p closed", stream);
       continue;
--- a/dom/media/MemoryBlockCache.h
+++ b/dom/media/MemoryBlockCache.h
@@ -40,17 +40,17 @@ class MemoryBlockCache : public MediaBlo
   // Allocate initial buffer.
   // If re-initializing, clear buffer.
   virtual nsresult Init() override;
 
   void Flush() 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; }
+  size_t GetMaxBlocks(size_t) 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, int32_t aLength,
                         int32_t* aBytes) override;
@@ -68,17 +68,17 @@ class MemoryBlockCache : public MediaBlo
   // 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;
+  const size_t mMaxBlocks;
 
   // Mutex which controls access to all members below.
   Mutex mMutex;
 
   nsTArray<uint8_t> mBuffer;
   bool mHasGrown = false;
 };
 
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1430,27 +1430,28 @@ VARCACHE_PREF(
 //---------------------------------------------------------------------------
 // Media prefs
 //---------------------------------------------------------------------------
 
 // These prefs use camel case instead of snake case for the getter because one
 // reviewer had an unshakeable preference for that.
 
 // File-backed MediaCache size.
-#ifdef ANDROID
-# define PREF_VALUE  32768  // Measured in KiB
-#else
-# define PREF_VALUE 512000  // Measured in KiB
-#endif
 VARCACHE_PREF(
   "media.cache_size",
    MediaCacheSize,
-  RelaxedAtomicUint32, PREF_VALUE
+  uint32_t, 512000 // Measured in KiB
 )
-#undef PREF_VALUE
+// Size of file backed MediaCache while on a connection which is cellular (3G, etc),
+// and thus assumed to be "expensive".
+VARCACHE_PREF(
+  "media.cache_size.cellular",
+   MediaCacheCellularSize,
+  uint32_t, 32768 // Measured in KiB
+)
 
 // If a resource is known to be smaller than this size (in kilobytes), a
 // memory-backed MediaCache may be used; otherwise the (single shared global)
 // file-backed MediaCache is used.
 VARCACHE_PREF(
   "media.memory_cache_max_size",
    MediaMemoryCacheMaxSize,
   uint32_t, 8192      // Measured in KiB
@@ -1469,42 +1470,40 @@ VARCACHE_PREF(
 VARCACHE_PREF(
   "media.memory_caches_combined_limit_pc_sysmem",
    MediaMemoryCachesCombinedLimitPcSysmem,
   uint32_t, 5         // A percentage
 )
 
 // When a network connection is suspended, don't resume it until the amount of
 // buffered data falls below this threshold (in seconds).
-#ifdef ANDROID
-# define PREF_VALUE 10  // Use a smaller limit to save battery.
-#else
-# define PREF_VALUE 30
-#endif
 VARCACHE_PREF(
   "media.cache_resume_threshold",
    MediaCacheResumeThreshold,
-  RelaxedAtomicInt32, PREF_VALUE
+  uint32_t, 30
 )
-#undef PREF_VALUE
+VARCACHE_PREF(
+  "media.cache_resume_threshold.cellular",
+   MediaCacheCellularResumeThreshold,
+  uint32_t, 10
+)
 
 // Stop reading ahead when our buffered data is this many seconds ahead of the
 // current playback position. This limit can stop us from using arbitrary
 // amounts of network bandwidth prefetching huge videos.
-#ifdef ANDROID
-# define PREF_VALUE 30  // Use a smaller limit to save battery.
-#else
-# define PREF_VALUE 60
-#endif
 VARCACHE_PREF(
   "media.cache_readahead_limit",
    MediaCacheReadaheadLimit,
-  RelaxedAtomicInt32, PREF_VALUE
+  uint32_t, 60
 )
-#undef PREF_VALUE
+VARCACHE_PREF(
+  "media.cache_readahead_limit.cellular",
+   MediaCacheCellularReadaheadLimit,
+  uint32_t, 30
+)
 
 // AudioSink
 VARCACHE_PREF(
   "media.resampling.enabled",
    MediaResamplingEnabled,
   RelaxedAtomicBool, false
 )