Bug 1178938 - Introduce a new mechanism for iterative work and prototype it with RawReader. r=jww
authorBobby Holley <bobbyholley@gmail.com>
Mon, 29 Jun 2015 16:49:47 -0700
changeset 251156 a585d0f6ee1282d58f6a2b76ddf9d98da2f01758
parent 251155 54bf2c8de57659338dd0c54a2c3887d34f08250f
child 251157 221ca3aab84249a08247bce35a1f1996c286a51c
push id61784
push userbobbyholley@gmail.com
push dateThu, 02 Jul 2015 19:38:54 +0000
treeherdermozilla-inbound@4272c902d349 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjww
bugs1178938
milestone42.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 1178938 - Introduce a new mechanism for iterative work and prototype it with RawReader. r=jww The current model can hog the task queue indefinitely, and relies on synchronously reading cross-thread state in order to detect shutdown conditions. In order to stop doing that, we need a different model. Thankfully, MediaPromises/closures make this a lot more concise than it could be.
dom/media/MediaPromise.h
dom/media/VideoUtils.h
dom/media/raw/RawReader.cpp
dom/media/raw/RawReader.h
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -696,16 +696,19 @@ public:
     MutexAutoLock lock(mMutex);
     MOZ_ASSERT(IsPending());
     PROMISE_LOG("%s resolveOrRejecting MediaPromise (%p created at %s)", aSite, this, mCreationSite);
     mValue = Forward<ResolveOrRejectValue_>(aValue);
     DispatchAll();
   }
 };
 
+// A generic promise type that does the trick for simple use cases.
+typedef MediaPromise<bool, nsresult, /* IsExclusive = */ false> GenericPromise;
+
 /*
  * Class to encapsulate a promise for a particular role. Use this as the member
  * variable for a class whose method returns a promise.
  */
 template<typename PromiseType>
 class MediaPromiseHolder
 {
 public:
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -8,16 +8,17 @@
 #define VideoUtils_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/CheckedInt.h"
 #include "nsIThread.h"
 #include "nsSize.h"
 #include "nsRect.h"
+#include "MediaPromise.h"
 
 #if !(defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)) || \
     defined(MOZ_ASAN)
 // For MEDIA_THREAD_STACK_SIZE
 #include "nsIThreadManager.h"
 #endif
 #include "nsThreadUtils.h"
 #include "prtime.h"
@@ -276,11 +277,39 @@ class MediaTaskQueue;
 class FlushableMediaTaskQueue;
 
 already_AddRefed<MediaTaskQueue>
 CreateMediaDecodeTaskQueue();
 
 already_AddRefed<FlushableMediaTaskQueue>
 CreateFlushableMediaDecodeTaskQueue();
 
+// Iteratively invokes aWork until aCondition returns true, or aWork returns false.
+// Use this rather than a while loop to avoid bogarting the task queue.
+template<class Work, class Condition>
+nsRefPtr<GenericPromise> InvokeUntil(Work aWork, Condition aCondition) {
+  nsRefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__);
+
+  if (aCondition()) {
+    p->Resolve(true, __func__);
+  }
+
+  struct Helper {
+    static void Iteration(nsRefPtr<GenericPromise::Private> aPromise, Work aWork, Condition aCondition) {
+      if (!aWork()) {
+        aPromise->Reject(NS_ERROR_FAILURE, __func__);
+      } else if (aCondition()) {
+        aPromise->Resolve(true, __func__);
+      } else {
+        nsCOMPtr<nsIRunnable> r =
+          NS_NewRunnableFunction([aPromise, aWork, aCondition] () { Iteration(aPromise, aWork, aCondition); });
+        AbstractThread::GetCurrent()->Dispatch(r.forget());
+      }
+    }
+  };
+
+  Helper::Iteration(p, aWork, aCondition);
+  return p.forget();
+}
+
 } // end namespace mozilla
 
 #endif
--- a/dom/media/raw/RawReader.cpp
+++ b/dom/media/raw/RawReader.cpp
@@ -226,65 +226,56 @@ bool RawReader::DecodeVideoFrame(bool &a
   a.mDecoded++;
 
   return true;
 }
 
 nsRefPtr<MediaDecoderReader::SeekPromise>
 RawReader::Seek(int64_t aTime, int64_t aEndTime)
 {
-  nsresult res = SeekInternal(aTime);
-  if (NS_FAILED(res)) {
-    return SeekPromise::CreateAndReject(res, __func__);
-  } else {
-    return SeekPromise::CreateAndResolve(aTime, __func__);
-  }
-}
-
-nsresult RawReader::SeekInternal(int64_t aTime)
-{
   MOZ_ASSERT(OnTaskQueue());
 
   MediaResource *resource = mDecoder->GetResource();
   NS_ASSERTION(resource, "Decoder has no media resource");
 
   uint32_t frame = mCurrentFrame;
   if (aTime >= UINT_MAX)
-    return NS_ERROR_FAILURE;
+    return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   mCurrentFrame = aTime * mFrameRate / USECS_PER_S;
 
   CheckedUint32 offset = CheckedUint32(mCurrentFrame) * mFrameSize;
   offset += sizeof(RawVideoHeader);
-  NS_ENSURE_TRUE(offset.isValid(), NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(offset.isValid(), SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__));
 
   nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value());
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(rv, SeekPromise::CreateAndReject(rv, __func__));
 
   mVideoQueue.Reset();
-
-  while(mVideoQueue.GetSize() == 0) {
-    bool keyframeSkip = false;
-    if (!DecodeVideoFrame(keyframeSkip, 0)) {
-      mCurrentFrame = frame;
-      return NS_ERROR_FAILURE;
+  nsRefPtr<SeekPromise::Private> p = new SeekPromise::Private(__func__);
+  nsRefPtr<RawReader> self = this;
+  InvokeUntil([self] () {
+    MOZ_ASSERT(self->OnTaskQueue());
+    NS_ENSURE_TRUE(!self->mShutdown, false);
+    bool skip = false;
+    return self->DecodeVideoFrame(skip, 0);
+  }, [self, aTime] () {
+    MOZ_ASSERT(self->OnTaskQueue());
+    return self->mVideoQueue.Peek() &&
+           self->mVideoQueue.Peek()->GetEndTime() >= aTime;
+  })->Then(TaskQueue(), __func__, [self, p, aTime] () {
+    while (self->mVideoQueue.GetSize() >= 2) {
+      nsRefPtr<VideoData> releaseMe = self->mVideoQueue.PopFront();
     }
+    p->Resolve(aTime, __func__);
+  }, [self, p, frame] {
+    self->mCurrentFrame = frame;
+    self->mVideoQueue.Reset();
+    p->Reject(NS_ERROR_FAILURE, __func__);
+  });
 
-    {
-      ReentrantMonitorAutoEnter autoMonitor(mDecoder->GetReentrantMonitor());
-      if (mDecoder->IsShutdown()) {
-        mCurrentFrame = frame;
-        return NS_ERROR_FAILURE;
-      }
-    }
-
-    if (mVideoQueue.PeekFront() && mVideoQueue.PeekFront()->GetEndTime() < aTime) {
-      nsRefPtr<VideoData> releaseMe = mVideoQueue.PopFront();
-    }
-  }
-
-  return NS_OK;
+  return p.forget();
 }
 
 media::TimeIntervals RawReader::GetBuffered()
 {
   MOZ_ASSERT(OnTaskQueue());
   return media::TimeIntervals();
 }
--- a/dom/media/raw/RawReader.h
+++ b/dom/media/raw/RawReader.h
@@ -44,18 +44,16 @@ public:
 
   virtual media::TimeIntervals GetBuffered() override;
 
   virtual bool IsMediaSeekable() override;
 
 private:
   bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
 
-  nsresult SeekInternal(int64_t aTime);
-
   RawVideoHeader mMetadata;
   uint32_t mCurrentFrame;
   double mFrameRate;
   uint32_t mFrameSize;
   nsIntRect mPicture;
 };
 
 } // namespace mozilla