Bug 1108707 - Make reader shutdown asynchronous. r=cpearce (relanding on a CLOSED TREE because it wasn't the source of the leaks)

This commit is contained in:
Bobby Holley 2014-12-09 11:43:21 -08:00
parent f3c9a75df6
commit 55609f006c
22 changed files with 181 additions and 85 deletions

View File

@ -283,18 +283,28 @@ MediaDecoderReader::BreakCycles()
mTaskQueue = nullptr;
}
void
nsRefPtr<ShutdownPromise>
MediaDecoderReader::Shutdown()
{
MOZ_ASSERT(OnDecodeThread());
mShutdown = true;
ReleaseMediaResources();
nsRefPtr<ShutdownPromise> p;
// Spin down the task queue if necessary. We wait until BreakCycles to null
// out mTaskQueue, since otherwise any remaining tasks could crash when they
// invoke GetTaskQueue()->IsCurrentThreadIn().
if (mTaskQueue && !mTaskQueueIsBorrowed) {
// We may be running in the task queue ourselves, so we don't block this
// thread on task queue draining, since that would deadlock.
mTaskQueue->BeginShutdown();
// If we own our task queue, shutdown ends when the task queue is done.
p = mTaskQueue->BeginShutdown();
} else {
// If we don't own our task queue, we resolve immediately (though
// asynchronously).
p = new ShutdownPromise(__func__);
p->Resolve(true, __func__);
}
mTaskQueue = nullptr;
return p;
}
AudioDecodeRendezvous::AudioDecodeRendezvous()

View File

@ -65,7 +65,7 @@ public:
// This is different from ReleaseMediaResources() as it is irreversable,
// whereas ReleaseMediaResources() is. Must be called on the decode
// thread.
virtual void Shutdown();
virtual nsRefPtr<ShutdownPromise> Shutdown();
virtual void SetCallback(RequestSampleCallback* aDecodedSampleCallback);
MediaTaskQueue* EnsureTaskQueue();

View File

@ -2470,6 +2470,53 @@ private:
nsRefPtr<MediaDecoderStateMachine> mStateMachine;
};
void
MediaDecoderStateMachine::ShutdownReader()
{
MOZ_ASSERT(OnDecodeThread());
mReader->Shutdown()->Then(GetStateMachineThread(), __func__, this,
&MediaDecoderStateMachine::FinishShutdown,
&MediaDecoderStateMachine::FinishShutdown);
}
void
MediaDecoderStateMachine::FinishShutdown(bool aSuccess)
{
MOZ_ASSERT(OnStateMachineThread());
MOZ_ASSERT(aSuccess);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
// The reader's listeners hold references to the state machine,
// creating a cycle which keeps the state machine and its shared
// thread pools alive. So break it here.
AudioQueue().ClearListeners();
VideoQueue().ClearListeners();
// Now that those threads are stopped, there's no possibility of
// mPendingWakeDecoder being needed again. Revoke it.
mPendingWakeDecoder = nullptr;
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
"How did we escape from the shutdown state?");
// We must daisy-chain these events to destroy the decoder. We must
// destroy the decoder on the main thread, but we can't destroy the
// decoder while this thread holds the decoder monitor. We can't
// dispatch an event to the main thread to destroy the decoder from
// here, as the event may run before the dispatch returns, and we
// hold the decoder monitor here. We also want to guarantee that the
// state machine is destroyed on the main thread, and so the
// event runner running this function (which holds a reference to the
// state machine) needs to finish and be released in order to allow
// that. So we dispatch an event to run after this event runner has
// finished and released its monitor/references. That event then will
// dispatch an event to the main thread to release the decoder and
// state machine.
GetStateMachineThread()->Dispatch(
new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
DECODER_LOG("Dispose Event Dispatched");
}
nsresult MediaDecoderStateMachine::RunStateMachine()
{
AssertCurrentThreadInMonitor();
@ -2486,47 +2533,14 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
StopAudioThread();
FlushDecoding();
// Put a task in the decode queue to shutdown the reader and wait for
// Put a task in the decode queue to shutdown the reader.
// the queue to spin down.
{
RefPtr<nsIRunnable> task;
task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::Shutdown);
nsRefPtr<MediaTaskQueue> queue = DecodeTaskQueue();
DebugOnly<nsresult> rv = queue->Dispatch(task);
MOZ_ASSERT(NS_SUCCEEDED(rv));
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
queue->AwaitShutdownAndIdle();
}
RefPtr<nsIRunnable> task;
task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader);
DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task);
MOZ_ASSERT(NS_SUCCEEDED(rv));
// The reader's listeners hold references to the state machine,
// creating a cycle which keeps the state machine and its shared
// thread pools alive. So break it here.
AudioQueue().ClearListeners();
VideoQueue().ClearListeners();
// Now that those threads are stopped, there's no possibility of
// mPendingWakeDecoder being needed again. Revoke it.
mPendingWakeDecoder = nullptr;
MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
"How did we escape from the shutdown state?");
// We must daisy-chain these events to destroy the decoder. We must
// destroy the decoder on the main thread, but we can't destroy the
// decoder while this thread holds the decoder monitor. We can't
// dispatch an event to the main thread to destroy the decoder from
// here, as the event may run before the dispatch returns, and we
// hold the decoder monitor here. We also want to guarantee that the
// state machine is destroyed on the main thread, and so the
// event runner running this function (which holds a reference to the
// state machine) needs to finish and be released in order to allow
// that. So we dispatch an event to run after this event runner has
// finished and released its monitor/references. That event then will
// dispatch an event to the main thread to release the decoder and
// state machine.
GetStateMachineThread()->Dispatch(
new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL);
DECODER_LOG("SHUTDOWN OK");
DECODER_LOG("Shutdown started");
return NS_OK;
}

View File

@ -162,6 +162,8 @@ public:
// Set/Unset dormant state.
void SetDormant(bool aDormant);
void Shutdown();
void ShutdownReader();
void FinishShutdown(bool aSuccess);
// Called from the main thread to get the duration. The decoder monitor
// must be obtained before calling this. It is in units of microseconds.

View File

@ -134,12 +134,17 @@ MediaTaskQueue::AwaitShutdownAndIdle()
AwaitIdleLocked();
}
void
nsRefPtr<ShutdownPromise>
MediaTaskQueue::BeginShutdown()
{
MonitorAutoLock mon(mQueueMonitor);
mIsShutdown = true;
nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
if (!mIsRunning) {
mShutdownPromise.Resolve(true, __func__);
}
mon.NotifyAll();
return p;
}
nsresult
@ -208,6 +213,7 @@ MediaTaskQueue::Runner::Run()
mQueue->mRunningThread = NS_GetCurrentThread();
if (mQueue->mTasks.size() == 0) {
mQueue->mIsRunning = false;
mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
mon.NotifyAll();
return NS_OK;
}
@ -235,6 +241,7 @@ MediaTaskQueue::Runner::Run()
if (mQueue->mTasks.size() == 0) {
// No more events to run. Exit the task runner.
mQueue->mIsRunning = false;
mQueue->mShutdownPromise.ResolveIfExists(true, __func__);
mon.NotifyAll();
mQueue->mRunningThread = nullptr;
return NS_OK;

View File

@ -12,6 +12,7 @@
#include "mozilla/Monitor.h"
#include "SharedThreadPool.h"
#include "nsThreadUtils.h"
#include "MediaPromise.h"
class nsIRunnable;
@ -19,6 +20,8 @@ namespace mozilla {
class SharedThreadPool;
typedef MediaPromise<bool, bool> ShutdownPromise;
// Abstracts executing runnables in order in a thread pool. The runnables
// dispatched to the MediaTaskQueue will be executed in the order in which
// they're received, and are guaranteed to not be executed concurrently.
@ -50,7 +53,9 @@ public:
// remain alive at least until all the events are drained, because the Runners
// hold a strong reference to the task queue, and one of them is always held
// by the threadpool event queue when the task queue is non-empty.
void BeginShutdown();
//
// The returned promise is resolved when the queue goes empty.
nsRefPtr<ShutdownPromise> BeginShutdown();
// Blocks until all task finish executing.
void AwaitIdle();
@ -105,6 +110,7 @@ private:
// True if we've started our shutdown process.
bool mIsShutdown;
MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
class MOZ_STACK_CLASS AutoSetFlushing
{

View File

@ -99,7 +99,8 @@ nsresult AndroidMediaReader::ReadMetadata(MediaInfo* aInfo,
return NS_OK;
}
void AndroidMediaReader::Shutdown()
nsRefPtr<ShutdownPromise>
AndroidMediaReader::Shutdown()
{
ResetDecode();
if (mPlugin) {
@ -107,7 +108,7 @@ void AndroidMediaReader::Shutdown()
mPlugin = nullptr;
}
MediaDecoderReader::Shutdown();
return MediaDecoderReader::Shutdown();
}
// Resets all state related to decoding, emptying all buffers etc.

View File

@ -71,7 +71,7 @@ public:
MetadataTags** aTags);
virtual void Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime);
virtual void Shutdown() MOZ_OVERRIDE;
virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
class ImageBufferCallback : public MPAPI::BufferCallback {
typedef mozilla::layers::Image Image;

View File

@ -139,7 +139,7 @@ MP4Reader::~MP4Reader()
MOZ_COUNT_DTOR(MP4Reader);
}
void
nsRefPtr<ShutdownPromise>
MP4Reader::Shutdown()
{
MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
@ -172,7 +172,7 @@ MP4Reader::Shutdown()
mPlatform = nullptr;
}
MediaDecoderReader::Shutdown();
return MediaDecoderReader::Shutdown();
}
void

View File

@ -71,7 +71,7 @@ public:
virtual nsresult ResetDecode() MOZ_OVERRIDE;
virtual void Shutdown() MOZ_OVERRIDE;
virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
private:

View File

@ -11,6 +11,7 @@
#include "nsCOMPtr.h"
#include "nsError.h"
#include "MediaDecoder.h"
#include "MediaSourceReader.h"
class nsIStreamListener;
@ -18,7 +19,6 @@ namespace mozilla {
class MediaResource;
class MediaDecoderStateMachine;
class MediaSourceReader;
class SourceBufferDecoder;
class TrackBuffer;
@ -71,6 +71,8 @@ public:
virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE;
#endif
MediaSourceReader* GetReader() { return mReader; }
private:
// The owning MediaSource holds a strong reference to this decoder, and
// calls Attach/DetachMediaSource on this decoder to set and clear

View File

@ -236,17 +236,35 @@ MediaSourceReader::OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason)
GetCallback()->OnNotDecoded(aType, WAITING_FOR_DATA);
}
void
nsRefPtr<ShutdownPromise>
MediaSourceReader::Shutdown()
{
MediaDecoderReader::Shutdown();
for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
mTrackBuffers[i]->Shutdown();
MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
nsRefPtr<ShutdownPromise> p = mMediaSourceShutdownPromise.Ensure(__func__);
ContinueShutdown(true);
return p;
}
void
MediaSourceReader::ContinueShutdown(bool aSuccess)
{
MOZ_ASSERT(aSuccess);
if (mTrackBuffers.Length()) {
mTrackBuffers[0]->Shutdown()->Then(GetTaskQueue(), __func__, this,
&MediaSourceReader::ContinueShutdown,
&MediaSourceReader::ContinueShutdown);
mShutdownTrackBuffers.AppendElement(mTrackBuffers[0]);
mTrackBuffers.RemoveElementAt(0);
return;
}
mAudioTrack = nullptr;
mAudioReader = nullptr;
mVideoTrack = nullptr;
mVideoReader = nullptr;
MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
}
void
@ -259,11 +277,12 @@ MediaSourceReader::BreakCycles()
MOZ_ASSERT(!mAudioReader);
MOZ_ASSERT(!mVideoTrack);
MOZ_ASSERT(!mVideoReader);
MOZ_ASSERT(!mTrackBuffers.Length());
for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
mTrackBuffers[i]->BreakCycles();
for (uint32_t i = 0; i < mShutdownTrackBuffers.Length(); ++i) {
mShutdownTrackBuffers[i]->BreakCycles();
}
mTrackBuffers.Clear();
mShutdownTrackBuffers.Clear();
}
already_AddRefed<MediaDecoderReader>

View File

@ -97,7 +97,7 @@ public:
void RemoveTrackBuffer(TrackBuffer* aTrackBuffer);
void OnTrackBufferConfigured(TrackBuffer* aTrackBuffer, const MediaInfo& aInfo);
void Shutdown();
nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
virtual void BreakCycles();
@ -135,6 +135,7 @@ private:
nsRefPtr<MediaDecoderReader> mVideoReader;
nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
nsRefPtr<TrackBuffer> mAudioTrack;
nsRefPtr<TrackBuffer> mVideoTrack;
@ -176,6 +177,9 @@ private:
bool mVideoIsSeeking;
bool mHasEssentialTrackBuffers;
void ContinueShutdown(bool aSuccess);
MediaPromiseHolder<ShutdownPromise> mMediaSourceShutdownPromise;
#ifdef MOZ_FMP4
nsRefPtr<SharedDecoderManager> mSharedDecoderManager;
#endif

View File

@ -96,23 +96,39 @@ private:
nsAutoTArray<nsRefPtr<SourceBufferDecoder>,2> mDecoders;
};
void
nsRefPtr<ShutdownPromise>
TrackBuffer::Shutdown()
{
// Finish any decoder initialization, which may add to mInitializedDecoders.
// Shutdown waits for any pending events, which may require the monitor,
// so we must not hold the monitor during this call.
mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
mTaskQueue->BeginShutdown();
mTaskQueue->AwaitShutdownAndIdle();
mTaskQueue = nullptr;
MOZ_ASSERT(mShutdownPromise.IsEmpty());
nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
RefPtr<MediaTaskQueue> queue = mTaskQueue;
mTaskQueue = nullptr;
queue->BeginShutdown()
->Then(mParentDecoder->GetReader()->GetTaskQueue(), __func__, this,
&TrackBuffer::ContinueShutdown, &TrackBuffer::ContinueShutdown);
return p;
}
void
TrackBuffer::ContinueShutdown(bool aSuccess)
{
MOZ_ASSERT(aSuccess);
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
mDecoders[i]->GetReader()->Shutdown();
if (mDecoders.Length()) {
mDecoders[0]->GetReader()->Shutdown()
->Then(mParentDecoder->GetReader()->GetTaskQueue(), __func__, this,
&TrackBuffer::ContinueShutdown, &TrackBuffer::ContinueShutdown);
mShutdownDecoders.AppendElement(mDecoders[0]);
mDecoders.RemoveElementAt(0);
return;
}
mInitializedDecoders.Clear();
mParentDecoder = nullptr;
mShutdownPromise.Resolve(true, __func__);
}
bool
@ -433,12 +449,13 @@ TrackBuffer::BreakCycles()
{
MOZ_ASSERT(NS_IsMainThread());
for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
mDecoders[i]->BreakCycles();
for (uint32_t i = 0; i < mShutdownDecoders.Length(); ++i) {
mShutdownDecoders[i]->BreakCycles();
}
mDecoders.Clear();
mShutdownDecoders.Clear();
// These are cleared in Shutdown()
MOZ_ASSERT(!mDecoders.Length());
MOZ_ASSERT(mInitializedDecoders.IsEmpty());
MOZ_ASSERT(!mParentDecoder);
}

View File

@ -8,6 +8,7 @@
#define MOZILLA_TRACKBUFFER_H_
#include "SourceBufferDecoder.h"
#include "MediaPromise.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/mozalloc.h"
@ -33,7 +34,7 @@ public:
TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
void Shutdown();
nsRefPtr<ShutdownPromise> Shutdown();
// Append data to the current decoder. Also responsible for calling
// NotifyDataArrived on the decoder to keep buffered range computation up
@ -131,6 +132,11 @@ private:
// mParentDecoder's monitor.
nsTArray<nsRefPtr<SourceBufferDecoder>> mDecoders;
// During shutdown, we move decoders from mDecoders to mShutdownDecoders after
// invoking Shutdown. This is all so that we can avoid destroying the decoders
// off-main-thread. :-(
nsTArray<nsRefPtr<SourceBufferDecoder>> mShutdownDecoders;
// Contains only the initialized decoders managed by this TrackBuffer.
// Access protected by mParentDecoder's monitor.
nsTArray<nsRefPtr<SourceBufferDecoder>> mInitializedDecoders;
@ -153,6 +159,9 @@ private:
// Set when the first decoder used by this TrackBuffer is initialized.
// Protected by mParentDecoder's monitor.
MediaInfo mInfo;
void ContinueShutdown(bool aSuccess);
MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
};
} // namespace mozilla

View File

@ -8,6 +8,7 @@ MOCHITEST_MANIFESTS += ['test/mochitest.ini']
EXPORTS += [
'AsyncEventRunner.h',
'MediaSourceDecoder.h',
'MediaSourceReader.h',
]
EXPORTS.mozilla.dom += [

View File

@ -344,11 +344,11 @@ MediaCodecReader::ReleaseMediaResources()
ReleaseCriticalResources();
}
void
nsRefPtr<ShutdownPromise>
MediaCodecReader::Shutdown()
{
ReleaseResources();
MediaDecoderReader::Shutdown();
return MediaDecoderReader::Shutdown();
}
void

View File

@ -68,7 +68,7 @@ public:
// Destroys the decoding state. The reader cannot be made usable again.
// This is different from ReleaseMediaResources() as Shutdown() is
// irreversible, whereas ReleaseMediaResources() is reversible.
virtual void Shutdown();
virtual nsRefPtr<ShutdownPromise> Shutdown();
// Used to retrieve some special information that can only be retrieved after
// all contents have been continuously parsed. (ex. total duration of some

View File

@ -175,17 +175,20 @@ void MediaOmxReader::ReleaseDecoder()
mOmxDecoder.clear();
}
void MediaOmxReader::Shutdown()
nsRefPtr<ShutdownPromise>
MediaOmxReader::Shutdown()
{
nsCOMPtr<nsIRunnable> cancelEvent =
NS_NewRunnableMethod(this, &MediaOmxReader::CancelProcessCachedData);
NS_DispatchToMainThread(cancelEvent);
MediaDecoderReader::Shutdown();
nsRefPtr<ShutdownPromise> p = MediaDecoderReader::Shutdown();
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaOmxReader::ReleaseDecoder);
NS_DispatchToMainThread(event);
return p;
}
bool MediaOmxReader::IsWaitingMediaResources()

View File

@ -104,7 +104,7 @@ public:
virtual void SetIdle() MOZ_OVERRIDE;
virtual void Shutdown() MOZ_OVERRIDE;
virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
bool IsShutdown() {
MutexAutoLock lock(mMutex);

View File

@ -218,7 +218,8 @@ WebMReader::~WebMReader()
MOZ_COUNT_DTOR(WebMReader);
}
void WebMReader::Shutdown()
nsRefPtr<ShutdownPromise>
WebMReader::Shutdown()
{
#if defined(MOZ_PDM_VPX)
if (mVideoTaskQueue) {
@ -232,7 +233,7 @@ void WebMReader::Shutdown()
mVideoDecoder = nullptr;
}
MediaDecoderReader::Shutdown();
return MediaDecoderReader::Shutdown();
}
nsresult WebMReader::Init(MediaDecoderReader* aCloneDonor)

View File

@ -133,7 +133,7 @@ protected:
~WebMReader();
public:
virtual void Shutdown() MOZ_OVERRIDE;
virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
virtual nsresult Init(MediaDecoderReader* aCloneDonor);
virtual nsresult ResetDecode();
virtual bool DecodeAudioData();