--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -132,17 +132,17 @@ public:
friend class MediaCacheStream::BlockList;
typedef MediaCacheStream::BlockList BlockList;
static const int64_t BLOCK_SIZE = MediaCacheStream::BLOCK_SIZE;
// Get an instance of the file-backed MediaCache.
// Returns nullptr if initialization failed.
static MediaCache* GetMediaCache();
- // Shut down the global cache if it's no longer needed. We shut down
+ // Shut down the 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.
void MaybeShutdown();
// Brutally flush the cache contents. Main thread only.
void Flush();
@@ -254,16 +254,17 @@ public:
uint32_t mNext;
};
protected:
MediaCache()
: mNextResourceID(1)
, mReentrantMonitor("MediaCache.mReentrantMonitor")
, mUpdateQueued(false)
+ , mShutdownInsteadOfUpdating(false)
#ifdef DEBUG
, mInUpdate(false)
#endif
{
MOZ_COUNT_CTOR(MediaCache);
MediaCacheFlusher::RegisterMediaCache(this);
}
@@ -382,16 +383,21 @@ protected:
TimeDuration PredictNextUse(TimeStamp aNow, int32_t aBlock);
// Guess the duration until the next incoming data on aStream will be used
TimeDuration PredictNextUseForIncomingData(MediaCacheStream* aStream);
// Truncate the file and index array if there are free blocks at the
// end
void Truncate();
+ // Shutdown this MediaCache, and reset gMediaCache if we are the global one.
+ // If there is no queued update, destroy the MediaCache immediately.
+ // Otherwise when the update is processed, it will destroy the MediaCache.
+ void ShutdownAndDestroyThis();
+
// This member is main-thread only. It's used to allocate unique
// resource IDs to streams.
int64_t mNextResourceID;
// 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;
@@ -405,16 +411,19 @@ protected:
// Keep track for highest number of blocks owners, for telemetry purposes.
uint32_t mBlockOwnersWatermark = 0;
// Writer which performs IO, asynchronously writing cache blocks.
RefPtr<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;
+ // Main-thread only. True when shutting down, and the update task should
+ // destroy this MediaCache.
+ bool mShutdownInsteadOfUpdating;
#ifdef DEBUG
bool mInUpdate;
#endif
// A list of resource IDs to notify about the change in suspended status.
nsTArray<int64_t> mSuspendedStatusToNotify;
};
NS_IMETHODIMP
@@ -691,21 +700,40 @@ MediaCache::MaybeShutdown()
{
NS_ASSERTION(NS_IsMainThread(),
"MediaCache::MaybeShutdown called on non-main thread");
if (!mStreams.IsEmpty()) {
// Don't shut down yet, streams are still alive
return;
}
+ ShutdownAndDestroyThis();
+}
+
+void
+MediaCache::ShutdownAndDestroyThis()
+{
+ NS_ASSERTION(NS_IsMainThread(),
+ "MediaCache::Shutdown called on non-main thread");
// 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;
+
+ if (this == gMediaCache) {
+ // This is the global MediaCache, reset the global pointer to ensure it's
+ // not used anymore (in case it doesn't get destroyed immediately).
+ gMediaCache = nullptr;
+ }
+
+ if (mUpdateQueued) {
+ // An update is queued, let it destroy this MediaCache object.
+ mShutdownInsteadOfUpdating = true;
+ return;
+ }
+
+ delete this;
}
/* static */ MediaCache*
MediaCache::GetMediaCache()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
if (gMediaCache) {
return gMediaCache;
@@ -1091,16 +1119,24 @@ MediaCache::PredictNextUseForIncomingDat
enum StreamAction { NONE, SEEK, SEEK_AND_RESUME, RESUME, SUSPEND };
void
MediaCache::Update()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
+ if (mShutdownInsteadOfUpdating) {
+ // Safe to modify mUpdateQueued as we are shutting down and nobody should
+ // call MediaCache functions now.
+ mUpdateQueued = false;
+ ShutdownAndDestroyThis();
+ return;
+ }
+
// The action to use for each stream. We store these so we can make
// decisions while holding the cache lock but implement those decisions
// without holding the cache lock, since we need to call out to
// stream, decoder and element code.
AutoTArray<StreamAction,10> actions;
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@@ -1436,25 +1472,30 @@ MediaCache::Update()
}
}
mSuspendedStatusToNotify.Clear();
}
class UpdateEvent : public Runnable
{
public:
- UpdateEvent() : Runnable("MediaCache::UpdateEvent") {}
+ explicit UpdateEvent(MediaCache& aMediaCache)
+ : Runnable("MediaCache::UpdateEvent")
+ , mMediaCache(aMediaCache)
+ {
+ }
NS_IMETHOD Run() override
{
- if (gMediaCache) {
- gMediaCache->Update();
- }
+ mMediaCache.Update();
return NS_OK;
}
+
+private:
+ MediaCache& mMediaCache;
};
void
MediaCache::QueueUpdate()
{
mReentrantMonitor.AssertCurrentThreadIn();
// Queuing an update while we're in an update raises a high risk of
@@ -1462,17 +1503,17 @@ MediaCache::QueueUpdate()
NS_ASSERTION(!mInUpdate,
"Queuing an update while we're in an update");
if (mUpdateQueued)
return;
mUpdateQueued = true;
// XXX MediaCache does updates when decoders are still running at
// shutdown and get freed in the final cycle-collector cleanup. So
// don't leak a runnable in that case.
- nsCOMPtr<nsIRunnable> event = new UpdateEvent();
+ nsCOMPtr<nsIRunnable> event = new UpdateEvent(*this);
SystemGroup::Dispatch("MediaCache::UpdateEvent",
TaskCategory::Other,
event.forget());
}
void
MediaCache::QueueSuspendedStatusUpdate(int64_t aResourceID)
{