Bug 1125776: Part10. Handle concurrent aborts. r=mattwoodrow

Under some circumstances, it was possible for a cancelled queued task to run
and the following one would have been cancelled.
Also add more robust handling in cancelling the trackbuffer initialization
task.
This commit is contained in:
Jean-Yves Avenard 2015-02-04 20:20:16 +11:00
parent 882199f026
commit 00bc1aec0d
5 changed files with 86 additions and 50 deletions

View File

@ -248,11 +248,11 @@ MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
return;
}
if (sourceBuffer->Updating()) {
// TODO:
// abort stream append loop (if running)
sourceBuffer->Abort();
}
sourceBuffer->AbortBufferAppend();
// TODO:
// abort stream append loop (if running)
// TODO:
// For all sourceBuffer audioTracks, videoTracks, textTracks:
// set sourceBuffer to null

View File

@ -46,16 +46,18 @@ class AppendDataRunnable : public nsRunnable {
public:
AppendDataRunnable(SourceBuffer* aSourceBuffer,
LargeDataBuffer* aData,
double aTimestampOffset)
double aTimestampOffset,
uint32_t aUpdateID)
: mSourceBuffer(aSourceBuffer)
, mData(aData)
, mTimestampOffset(aTimestampOffset)
, mUpdateID(aUpdateID)
{
}
NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL {
mSourceBuffer->AppendData(mData, mTimestampOffset);
mSourceBuffer->AppendData(mData, mTimestampOffset, mUpdateID);
return NS_OK;
}
@ -64,6 +66,7 @@ private:
nsRefPtr<SourceBuffer> mSourceBuffer;
nsRefPtr<LargeDataBuffer> mData;
double mTimestampOffset;
uint32_t mUpdateID;
};
class RangeRemovalRunnable : public nsRunnable {
@ -214,22 +217,23 @@ SourceBuffer::Abort(ErrorResult& aRv)
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
Abort();
AbortBufferAppend();
mTrackBuffer->ResetParserState();
mAppendWindowStart = 0;
mAppendWindowEnd = PositiveInfinity<double>();
// Discard the current decoder so no new data will be added to it.
MSE_DEBUG("SourceBuffer(%p)::Abort() Discarding decoder", this);
mTrackBuffer->DiscardDecoder();
mTrackBuffer->DiscardCurrentDecoder();
}
void
SourceBuffer::Abort()
SourceBuffer::AbortBufferAppend()
{
if (mUpdating) {
// abort any pending buffer append.
mTrackBuffer->Abort();
mPendingAppend.DisconnectIfExists();
// TODO: Abort segment parser loop, and stream append loop algorithms.
// cancel any pending buffer append.
mTrackBuffer->AbortAppendData();
AbortUpdating();
}
}
@ -291,7 +295,7 @@ SourceBuffer::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
MSE_DEBUG("SourceBuffer(%p)::Detach", this);
Abort();
AbortBufferAppend();
if (mTrackBuffer) {
mTrackBuffer->Detach();
}
@ -316,6 +320,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
, mTimestampOffset(0)
, mAppendMode(SourceBufferAppendMode::Segments)
, mUpdating(false)
, mUpdateID(0)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aMediaSource);
@ -367,6 +372,7 @@ SourceBuffer::StartUpdating()
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mUpdating);
mUpdating = true;
mUpdateID++;
QueueAsyncSimpleEvent("updatestart");
}
@ -424,14 +430,15 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments,
"We don't handle timestampOffset for sequence mode yet");
nsRefPtr<nsIRunnable> task =
new AppendDataRunnable(this, data, mTimestampOffset);
new AppendDataRunnable(this, data, mTimestampOffset, mUpdateID);
NS_DispatchToMainThread(task);
}
void
SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset)
SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset,
uint32_t aUpdateID)
{
if (!mUpdating) {
if (!mUpdating || aUpdateID != mUpdateID) {
// The buffer append algorithm has been interrupted by abort().
//
// If the sequence appendBuffer(), abort(), appendBuffer() occurs before
@ -442,21 +449,23 @@ SourceBuffer::AppendData(LargeDataBuffer* aData, double aTimestampOffset)
}
MOZ_ASSERT(mMediaSource);
MOZ_ASSERT(!mPendingAppend.Exists());
if (!aData->Length()) {
StopUpdating();
return;
}
mTrackBuffer->AppendData(aData, aTimestampOffset * USECS_PER_S)
->Then(NS_GetCurrentThread(), __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored);
mPendingAppend.Begin(mTrackBuffer->AppendData(aData, aTimestampOffset * USECS_PER_S)
->RefableThen(NS_GetCurrentThread(), __func__, this,
&SourceBuffer::AppendDataCompletedWithSuccess,
&SourceBuffer::AppendDataErrored));
}
void
SourceBuffer::AppendDataCompletedWithSuccess(bool aGotMedia)
{
mPendingAppend.Complete();
if (!mUpdating) {
// The buffer append algorithm has been interrupted by abort().
return;
@ -476,10 +485,11 @@ SourceBuffer::AppendDataCompletedWithSuccess(bool aGotMedia)
void
SourceBuffer::AppendDataErrored(nsresult aError)
{
mPendingAppend.Complete();
switch (aError) {
case NS_ERROR_ABORT:
// Nothing further to do as the handling of the events is being done
// by Abort().
// Nothing further to do as the trackbuffer has been shutdown.
// or append was aborted and abort() has handled all the events.
break;
default:
AppendError(true);

View File

@ -7,6 +7,7 @@
#ifndef mozilla_dom_SourceBuffer_h_
#define mozilla_dom_SourceBuffer_h_
#include "MediaPromise.h"
#include "MediaSource.h"
#include "js/RootingAPI.h"
#include "mozilla/Assertions.h"
@ -32,6 +33,7 @@ class ErrorResult;
class LargeDataBuffer;
class TrackBuffer;
template <typename T> class AsyncEventRunner;
typedef MediaPromise<bool, nsresult, /* IsExclusive = */ true> TrackBufferAppendPromise;
namespace dom {
@ -80,7 +82,7 @@ public:
void AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv);
void Abort(ErrorResult& aRv);
void Abort();
void AbortBufferAppend();
void Remove(double aStart, double aEnd, ErrorResult& aRv);
/** End WebIDL Methods. */
@ -139,7 +141,8 @@ private:
// Shared implementation of AppendBuffer overloads.
void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
void AppendData(LargeDataBuffer* aData, double aTimestampOffset);
void AppendData(LargeDataBuffer* aData, double aTimestampOffset,
uint32_t aAppendID);
// Implement the "Append Error Algorithm".
// Will call endOfStream() with "decode" error if aDecodeError is true.
@ -169,6 +172,13 @@ private:
SourceBufferAppendMode mAppendMode;
bool mUpdating;
// Each time mUpdating is set to true, mUpdateID will be incremented.
// This allows for a queued AppendData task to identify if it was earlier
// aborted and another AppendData queued.
uint32_t mUpdateID;
MediaPromiseConsumerHolder<TrackBufferAppendPromise> mPendingAppend;
};
} // namespace dom

View File

@ -122,6 +122,7 @@ TrackBuffer::Shutdown()
{
mParentDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mShutdown = true;
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
MOZ_ASSERT(mShutdownPromise.IsEmpty());
nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
@ -148,18 +149,21 @@ TrackBuffer::ContinueShutdown()
return;
}
mCurrentDecoder = nullptr;
mInitializedDecoders.Clear();
mParentDecoder = nullptr;
mShutdownPromise.Resolve(true, __func__);
}
nsRefPtr<TrackBuffer::InitializationPromise>
nsRefPtr<TrackBufferAppendPromise>
TrackBuffer::AppendData(LargeDataBuffer* aData, int64_t aTimestampOffset)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mInitializationPromise.IsEmpty());
DecodersToInitialize decoders(this);
nsRefPtr<InitializationPromise> p = mInitializationPromise.Ensure(__func__);
nsRefPtr<TrackBufferAppendPromise> p = mInitializationPromise.Ensure(__func__);
bool hadInitData = mParser->HasInitData();
bool hadCompleteInitData = mParser->HasCompleteInitData();
nsRefPtr<LargeDataBuffer> oldInit = mParser->InitData();
@ -439,7 +443,7 @@ TrackBuffer::NewDecoder(int64_t aTimestampOffset)
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mParentDecoder);
DiscardDecoder();
DiscardCurrentDecoder();
nsRefPtr<SourceBufferDecoder> decoder = mParentDecoder->CreateSubDecoder(mType, aTimestampOffset);
if (!decoder) {
@ -488,13 +492,21 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
if (mCurrentDecoder != aDecoder) {
MSE_DEBUG("TrackBuffer(%p) append was cancelled. Aborting initialization.",
this);
// If we reached this point, the SourceBuffer would have disconnected
// the promise. So no need to reject it.
return;
}
// We may be shut down at any time by the reader on another thread. So we need
// to check for this each time we acquire the monitor. If that happens, we
// need to abort immediately, because the reader has forgotten about us, and
// important pieces of our state (like mTaskQueue) have also been torn down.
if (mShutdown) {
MSE_DEBUG("TrackBuffer(%p) was shut down. Aborting initialization.", this);
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
RemoveDecoder(aDecoder);
return;
}
@ -532,7 +544,6 @@ TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
reader->SetIdle();
if (mShutdown) {
MSE_DEBUG("TrackBuffer(%p) was shut down while reading metadata. Aborting initialization.", this);
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
return;
}
@ -577,10 +588,17 @@ void
TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
if (mCurrentDecoder != aDecoder) {
MSE_DEBUG("TrackBuffer(%p) append was cancelled. Aborting initialization.",
this);
// If we reached this point, the SourceBuffer would have disconnected
// the promise. So no need to reject it.
return;
}
if (mShutdown) {
MSE_DEBUG("TrackBuffer(%p) was shut down while reading metadata. Aborting initialization.", this);
MSE_DEBUG("TrackBuffer(%p) was shut down. Aborting initialization.", this);
RemoveDecoder(aDecoder);
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
return;
}
@ -648,12 +666,10 @@ TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder)
}
void
TrackBuffer::DiscardDecoder()
TrackBuffer::DiscardCurrentDecoder()
{
ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
if (mCurrentDecoder) {
mCurrentDecoder->GetResource()->Ended();
}
EndCurrentDecoder();
mCurrentDecoder = nullptr;
}
@ -671,10 +687,8 @@ TrackBuffer::Detach()
{
MOZ_ASSERT(NS_IsMainThread());
if (mCurrentDecoder) {
DiscardDecoder();
DiscardCurrentDecoder();
}
// Cancel the promise should the current decoder hadn't be initialized yet.
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
}
bool
@ -741,13 +755,17 @@ TrackBuffer::ResetParserState()
// We have an incomplete init segment pending. reset current parser and
// discard the current decoder.
mParser = ContainerParser::CreateForMIMEType(mType);
DiscardDecoder();
DiscardCurrentDecoder();
}
}
void
TrackBuffer::Abort()
TrackBuffer::AbortAppendData()
{
DiscardCurrentDecoder();
// The SourceBuffer would have disconnected its promise.
// However we must ensure that the MediaPromiseHolder handle all pending
// promises.
mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
}
@ -839,7 +857,7 @@ TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
mDecoders.RemoveElement(aDecoder);
if (mCurrentDecoder == aDecoder) {
DiscardDecoder();
DiscardCurrentDecoder();
}
}
aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);

View File

@ -7,8 +7,8 @@
#ifndef MOZILLA_TRACKBUFFER_H_
#define MOZILLA_TRACKBUFFER_H_
#include "SourceBuffer.h"
#include "SourceBufferDecoder.h"
#include "MediaPromise.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/mozalloc.h"
@ -33,8 +33,6 @@ class TrackBuffer MOZ_FINAL {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffer);
typedef MediaPromise<bool, nsresult, /* IsExclusive = */ false> InitializationPromise;
TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType);
nsRefPtr<ShutdownPromise> Shutdown();
@ -42,8 +40,8 @@ public:
// Append data to the current decoder. Also responsible for calling
// NotifyDataArrived on the decoder to keep buffered range computation up
// to date. Returns false if the append failed.
nsRefPtr<InitializationPromise> AppendData(LargeDataBuffer* aData,
int64_t aTimestampOffset /* microseconds */);
nsRefPtr<TrackBufferAppendPromise> AppendData(LargeDataBuffer* aData,
int64_t aTimestampOffset /* microseconds */);
// Evicts data held in the current decoders SourceBufferResource from the
// start of the buffer through to aPlaybackTime. aThreshold is used to
@ -64,7 +62,7 @@ public:
// Mark the current decoder's resource as ended, clear mCurrentDecoder and
// reset mLast{Start,End}Timestamp.
void DiscardDecoder();
void DiscardCurrentDecoder();
// Mark the current decoder's resource as ended.
void EndCurrentDecoder();
@ -104,7 +102,7 @@ public:
bool RangeRemoval(int64_t aStart, int64_t aEnd);
// Abort any pending appendBuffer by rejecting any pending promises.
void Abort();
void AbortAppendData();
#ifdef MOZ_EME
nsresult SetCDMProxy(CDMProxy* aProxy);
@ -205,7 +203,7 @@ private:
bool mDecoderPerSegment;
bool mShutdown;
MediaPromiseHolder<InitializationPromise> mInitializationPromise;
MediaPromiseHolder<TrackBufferAppendPromise> mInitializationPromise;
};