Bug 1371882 - MediaCacheFlusher allows for multiple MediaCache's - r=cpearce draft
authorGerald Squelart <gsquelart@mozilla.com>
Thu, 08 Jun 2017 14:32:13 +1200
changeset 595145 1570e30787ba486f9436b4b05aa3cfa0329d1ee7
parent 595097 79cdd4893c4607b8ad19c41ffd4ddde2f11d0151
child 595146 d69d52c68512041dd61c5782e3406aa66a487a28
push id64265
push usergsquelart@mozilla.com
push dateFri, 16 Jun 2017 03:37:56 +0000
reviewerscpearce
bugs1371882
milestone56.0a1
Bug 1371882 - MediaCacheFlusher allows for multiple MediaCache's - r=cpearce MediaCacheFlusher constructs itself when needed by the first MediaCache, and destroys itself when the last MediaCache unregisters itself. Some MediaCache member functions had to be made non-static, so they could be called for each instance. MozReview-Commit-ID: 5Dh9mEKbZHg
dom/media/MediaCache.cpp
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "FileBlockCache.h"
 #include "nsIObserverService.h"
 #include "nsISeekableStream.h"
 #include "nsIPrincipal.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include <algorithm>
 
 namespace mozilla {
 
 #undef LOG
 #undef LOGI
 LazyLogModule gMediaCacheLog("MediaCache");
@@ -64,48 +65,70 @@ static const uint32_t FREE_BLOCK_SCAN_LI
 // There is at most one media cache (although that could quite easily be
 // relaxed if we wanted to manage multiple caches with independent
 // size limits).
 static MediaCache* gMediaCache;
 
 class MediaCacheFlusher final : public nsIObserver,
                                 public nsSupportsWeakReference
 {
-  MediaCacheFlusher() {}
-  ~MediaCacheFlusher();
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
-  static void Init();
+  static void RegisterMediaCache(MediaCache* aMediaCache);
+  static void UnregisterMediaCache(MediaCache* aMediaCache);
+
+private:
+  MediaCacheFlusher() {}
+  ~MediaCacheFlusher() {}
+
+  // Singleton instance created when a first MediaCache is registered, and
+  // released when the last MediaCache is unregistered.
+  // The observer service will keep a weak reference to it, for notifications.
+  static StaticRefPtr<MediaCacheFlusher> gMediaCacheFlusher;
+
+  nsTArray<MediaCache*> mMediaCaches;
 };
 
-static MediaCacheFlusher* gMediaCacheFlusher;
+/* static */ StaticRefPtr<MediaCacheFlusher>
+  MediaCacheFlusher::gMediaCacheFlusher;
 
 NS_IMPL_ISUPPORTS(MediaCacheFlusher, nsIObserver, nsISupportsWeakReference)
 
-MediaCacheFlusher::~MediaCacheFlusher()
+/* static */ void
+MediaCacheFlusher::RegisterMediaCache(MediaCache* aMediaCache)
 {
-  gMediaCacheFlusher = nullptr;
+  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);
+    }
+  }
+
+  gMediaCacheFlusher->mMediaCaches.AppendElement(aMediaCache);
 }
 
-void MediaCacheFlusher::Init()
+/* static */ void
+MediaCacheFlusher::UnregisterMediaCache(MediaCache* aMediaCache)
 {
-  if (gMediaCacheFlusher) {
-    return;
-  }
+  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
 
-  gMediaCacheFlusher = new MediaCacheFlusher();
-  NS_ADDREF(gMediaCacheFlusher);
+  gMediaCacheFlusher->mMediaCaches.RemoveElement(aMediaCache);
 
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-  if (observerService) {
-    observerService->AddObserver(gMediaCacheFlusher, "last-pb-context-exited", true);
-    observerService->AddObserver(gMediaCacheFlusher, "cacheservice:empty-cache", true);
+  if (gMediaCacheFlusher->mMediaCaches.Length() == 0) {
+    gMediaCacheFlusher = nullptr;
   }
 }
 
 class MediaCache {
 public:
   friend class MediaCacheStream::BlockList;
   typedef MediaCacheStream::BlockList BlockList;
   static const int64_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
@@ -113,18 +136,20 @@ public:
   MediaCache() : mNextResourceID(1),
     mReentrantMonitor("MediaCache.mReentrantMonitor"),
     mUpdateQueued(false)
 #ifdef DEBUG
     , mInUpdate(false)
 #endif
   {
     MOZ_COUNT_CTOR(MediaCache);
+    MediaCacheFlusher::RegisterMediaCache(this);
   }
   ~MediaCache() {
+    MediaCacheFlusher::UnregisterMediaCache(this);
     NS_ASSERTION(mStreams.IsEmpty(), "Stream(s) still open!");
     Truncate();
     NS_ASSERTION(mIndex.Length() == 0, "Blocks leaked?");
     if (mFileCache) {
       mFileCache->Close();
       mFileCache = nullptr;
     }
     LOG("MediaCache::~MediaCache(this=%p) MEDIACACHE_WATERMARK_KB=%u",
@@ -145,26 +170,25 @@ public:
   // 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
   // the cache as soon as there are no streams. This means that during
   // normal operation we are likely to start up the cache and shut it down
   // many times, but that's OK since starting it up is cheap and
   // shutting it down cleans things up and releases disk space.
-  static void MaybeShutdown();
+  void MaybeShutdown();
 
   // Brutally flush the cache contents. Main thread only.
-  static void Flush();
-  void FlushInternal();
+  void Flush();
 
   // Close all streams associated with private browsing windows. This will
   // also remove the blocks from the cache since we don't want to leave any
   // traces when PB is done.
-  static void CloseStreamsForPrivateBrowsing();
+  void CloseStreamsForPrivateBrowsing();
 
   // 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.
   // Note mReentrantMonitor will be dropped while doing IO. The caller need
   // to handle changes happening when the monitor is not held.
   nsresult ReadCacheFile(int64_t aOffset, void* aData, int32_t aLength,
                          int32_t* aBytes);
@@ -375,22 +399,28 @@ protected:
 #endif
   // A list of resource IDs to notify about the change in suspended status.
   nsTArray<int64_t> mSuspendedStatusToNotify;
 };
 
 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) {
-    MediaCache::CloseStreamsForPrivateBrowsing();
+    for (MediaCache* mc : mMediaCaches) {
+      mc->CloseStreamsForPrivateBrowsing();
+    }
     return NS_OK;
   }
   if (strcmp(aTopic, "cacheservice:empty-cache") == 0) {
-    MediaCache::Flush();
+    for (MediaCache* mc : mMediaCaches) {
+      mc->Flush();
+    }
     return NS_OK;
   }
   return NS_OK;
 }
 
 MediaCacheStream::MediaCacheStream(ChannelMediaResource* aClient,
                                    bool aIsPrivateBrowsing)
   : mClient(aClient),
@@ -601,35 +631,23 @@ MediaCache::Init()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mFileCache, "Cache file already open?");
 
   mFileCache = new FileBlockCache();
   nsresult rv = mFileCache->Init();
   NS_ENSURE_SUCCESS(rv,rv);
 
-  MediaCacheFlusher::Init();
-
   return NS_OK;
 }
 
 void
 MediaCache::Flush()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
-
-  if (!gMediaCache)
-    return;
-
-  gMediaCache->FlushInternal();
-}
-
-void
-MediaCache::FlushInternal()
-{
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
   for (uint32_t blockIndex = 0; blockIndex < mIndex.Length(); ++blockIndex) {
     FreeBlock(blockIndex);
   }
 
   // Truncate file, close it, and reopen
   Truncate();
@@ -640,42 +658,38 @@ MediaCache::FlushInternal()
   }
   Init();
 }
 
 void
 MediaCache::CloseStreamsForPrivateBrowsing()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!gMediaCache) {
-    return;
-  }
-  for (auto& s : gMediaCache->mStreams) {
+  for (MediaCacheStream* s : mStreams) {
     if (s->mIsPrivateBrowsing) {
       s->Close();
     }
   }
 }
 
 void
 MediaCache::MaybeShutdown()
 {
   NS_ASSERTION(NS_IsMainThread(),
                "MediaCache::MaybeShutdown called on non-main thread");
-  if (!gMediaCache->mStreams.IsEmpty()) {
+  if (!mStreams.IsEmpty()) {
     // Don't shut down yet, streams are still alive
     return;
   }
 
   // Since we're on the main thread, no-one is going to add a new stream
   // while we shut down.
   // This function is static so we don't have to delete 'this'.
   delete gMediaCache;
   gMediaCache = nullptr;
-  NS_IF_RELEASE(gMediaCacheFlusher);
 }
 
 static void
 InitMediaCache()
 {
   if (gMediaCache)
     return;
 
@@ -1969,17 +1983,17 @@ MediaCacheStream::NotifyChannelRecreated
 MediaCacheStream::~MediaCacheStream()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   NS_ASSERTION(!mPinCount, "Unbalanced Pin");
 
   if (gMediaCache) {
     NS_ASSERTION(mClosed, "Stream was not closed");
     gMediaCache->ReleaseStream(this);
-    MediaCache::MaybeShutdown();
+    gMediaCache->MaybeShutdown();
   }
 
   uint32_t lengthKb = uint32_t(
     std::min(std::max(mStreamLength, int64_t(0)) / 1024, int64_t(UINT32_MAX)));
   LOG("MediaCacheStream::~MediaCacheStream(this=%p) "
       "MEDIACACHESTREAM_LENGTH_KB=%" PRIu32,
       this,
       lengthKb);