Bug 1097823 - Implement Media Promises. r=cpearce
☠☠ backed out by 046a1ce2ffe7 ☠ ☠
authorBobby Holley <bobbyholley@gmail.com>
Sun, 07 Dec 2014 11:21:34 -0800
changeset 218628 b56e5e7623292d04e2518e9688b02cc7631d85ab
parent 218627 8dfbe042e94699d9ac6d7b0ccdbb72e31fa97f62
child 218629 b76636e9f49a3a860deb7915f042b92908dc5c85
push id27937
push usercbook@mozilla.com
push dateMon, 08 Dec 2014 11:50:02 +0000
treeherdermozilla-central@0c399be939d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1097823
milestone37.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 1097823 - Implement Media Promises. r=cpearce
dom/media/MediaDecoder.cpp
dom/media/MediaDecoderReader.cpp
dom/media/MediaPromise.h
dom/media/moz.build
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -452,17 +452,16 @@ MediaDecoder::MediaDecoder() :
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
 #ifdef PR_LOGGING
   if (!gMediaDecoderLog) {
     gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
   }
 #endif
-
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 }
 
 bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mOwner = aOwner;
   mVideoFrameContainer = aOwner->GetVideoFrameContainer();
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -22,16 +22,26 @@ namespace mozilla {
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(x, ...) \
   PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder, ##__VA_ARGS__))
 #else
 #define DECODER_LOG(x, ...)
 #endif
 
+PRLogModuleInfo* gMediaPromiseLog;
+
+void
+EnsureMediaPromiseLog()
+{
+  if (!gMediaPromiseLog) {
+    gMediaPromiseLog = PR_NewLogModule("MediaPromise");
+  }
+}
+
 class VideoQueueMemoryFunctor : public nsDequeFunctor {
 public:
   VideoQueueMemoryFunctor() : mSize(0) {}
 
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
 
   virtual void* operator()(void* aObject) {
     const VideoData* v = static_cast<const VideoData*>(aObject);
@@ -64,16 +74,17 @@ MediaDecoderReader::MediaDecoderReader(A
   , mIgnoreAudioOutputFormat(false)
   , mStartTime(-1)
   , mTaskQueueIsBorrowed(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
+  EnsureMediaPromiseLog();
 }
 
 MediaDecoderReader::~MediaDecoderReader()
 {
   MOZ_ASSERT(mShutdown);
   ResetDecode();
   MOZ_COUNT_DTOR(MediaDecoderReader);
 }
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaPromise.h
@@ -0,0 +1,322 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MediaPromise_h_)
+#define MediaPromise_h_
+
+#include "prlog.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Monitor.h"
+
+#include "MediaTaskQueue.h"
+#include "nsIEventTarget.h"
+
+/* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */
+#ifdef _MSC_VER
+#define __func__ __FUNCTION__
+#endif
+
+namespace mozilla {
+
+extern PRLogModuleInfo* gMediaPromiseLog;
+void EnsureMediaPromiseLog();
+
+#define PROMISE_LOG(x, ...) \
+  MOZ_ASSERT(gMediaPromiseLog); \
+  PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
+
+/*
+ * A promise manages an asynchronous request that may or may not be able to be
+ * fulfilled immediately. When an API returns a promise, the consumer may attach
+ * callbacks to be invoked (asynchronously, on a specified thread) when the
+ * request is either completed (resolved) or cannot be completed (rejected).
+ *
+ * By default, resolve and reject callbacks are always invoked on the same thread
+ * where Then() was invoked.
+ */
+template<typename T> class MediaPromiseHolder;
+template<typename ResolveValueT, typename RejectValueT>
+class MediaPromise
+{
+public:
+  typedef ResolveValueT ResolveValueType;
+  typedef RejectValueT RejectValueType;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPromise)
+  MediaPromise(const char* aCreationSite)
+    : mCreationSite(aCreationSite)
+    , mMutex("MediaPromise Mutex")
+  {
+    MOZ_COUNT_CTOR(MediaPromise);
+    PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this);
+  }
+
+protected:
+
+  /*
+   * A ThenValue tracks a single consumer waiting on the promise. When a consumer
+   * invokes promise->Then(...), a ThenValue is created. Once the Promise is
+   * resolved or rejected, a ThenValueRunnable is dispatched, which invokes the
+   * resolve/reject method and then deletes the ThenValue.
+   */
+  class ThenValueBase
+  {
+  public:
+    class ThenValueRunnable : public nsRunnable
+    {
+    public:
+      ThenValueRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue)
+        : mThenValue(aThenValue)
+      {
+        MOZ_COUNT_CTOR(ThenValueRunnable);
+        mResolveValue.emplace(aResolveValue);
+      }
+
+      ThenValueRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue)
+        : mThenValue(aThenValue)
+      {
+        MOZ_COUNT_CTOR(ThenValueRunnable);
+        mRejectValue.emplace(aRejectValue);
+      }
+
+      ~ThenValueRunnable()
+      {
+        MOZ_COUNT_DTOR(ThenValueRunnable);
+        MOZ_ASSERT(!mThenValue);
+      }
+
+      NS_IMETHODIMP Run()
+      {
+        PROMISE_LOG("ThenValueRunnable::Run() [this=%p]", this);
+        if (mResolveValue.isSome()) {
+          mThenValue->DoResolve(mResolveValue.ref());
+        } else {
+          mThenValue->DoReject(mRejectValue.ref());
+        }
+
+        delete mThenValue;
+        mThenValue = nullptr;
+        return NS_OK;
+      }
+
+    private:
+      ThenValueBase* mThenValue;
+      Maybe<ResolveValueType> mResolveValue;
+      Maybe<RejectValueType> mRejectValue;
+    };
+
+    ThenValueBase(const char* aCallSite) : mCallSite(aCallSite)
+    {
+      MOZ_COUNT_CTOR(ThenValueBase);
+    }
+
+    virtual void Dispatch(MediaPromise *aPromise) = 0;
+
+  protected:
+    // This may only be deleted by ThenValueRunnable::Run.
+    virtual ~ThenValueBase() { MOZ_COUNT_DTOR(ThenValueBase); }
+
+    virtual void DoResolve(ResolveValueType aResolveValue) = 0;
+    virtual void DoReject(RejectValueType aRejectValue) = 0;
+
+    const char* mCallSite;
+  };
+
+  template<typename TargetType, typename ThisType,
+           typename ResolveMethodType, typename RejectMethodType>
+  class ThenValue : public ThenValueBase
+  {
+  public:
+    ThenValue(TargetType* aResponseTarget, ThisType* aThisVal,
+              ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
+              const char* aCallSite)
+      : ThenValueBase(aCallSite)
+      , mResponseTarget(aResponseTarget)
+      , mThisVal(aThisVal)
+      , mResolveMethod(aResolveMethod)
+      , mRejectMethod(aRejectMethod) {}
+
+    void Dispatch(MediaPromise *aPromise) MOZ_OVERRIDE
+    {
+      aPromise->mMutex.AssertCurrentThreadOwns();
+      MOZ_ASSERT(!aPromise->IsPending());
+      bool resolved = aPromise->mResolveValue.isSome();
+      nsRefPtr<nsRunnable> runnable =
+        resolved ? new (typename ThenValueBase::ThenValueRunnable)(this, aPromise->mResolveValue.ref())
+                 : new (typename ThenValueBase::ThenValueRunnable)(this, aPromise->mRejectValue.ref());
+      PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
+                  resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
+                  runnable.get(), aPromise, this);
+      DebugOnly<nsresult> rv = DoDispatch(mResponseTarget, runnable);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
+
+  protected:
+    virtual void DoResolve(ResolveValueType aResolveValue)
+    {
+      ((*mThisVal).*mResolveMethod)(aResolveValue);
+    }
+
+    virtual void DoReject(RejectValueType aRejectValue)
+    {
+      ((*mThisVal).*mRejectMethod)(aRejectValue);
+    }
+
+    static nsresult DoDispatch(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable)
+    {
+      return aTaskQueue->ForceDispatch(aRunnable);
+    }
+
+    static nsresult DoDispatch(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable)
+    {
+      return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
+    }
+
+    virtual ~ThenValue() {}
+
+  private:
+    nsRefPtr<TargetType> mResponseTarget;
+    nsRefPtr<ThisType> mThisVal;
+    ResolveMethodType mResolveMethod;
+    RejectMethodType mRejectMethod;
+  };
+public:
+
+  template<typename TargetType, typename ThisType,
+           typename ResolveMethodType, typename RejectMethodType>
+  void Then(TargetType* aResponseTarget, ThisType* aThisVal,
+            ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
+            const char* aCallSite)
+  {
+    MutexAutoLock lock(mMutex);
+    ThenValueBase* thenValue = new ThenValue<TargetType, ThisType, ResolveMethodType,
+                                             RejectMethodType>(aResponseTarget, aThisVal,
+                                                               aResolveMethod, aRejectMethod,
+                                                               aCallSite);
+    PROMISE_LOG("%s invoking Then() [this=%p, thenValue=%p, aThisVal=%p, isPending=%d]",
+                aCallSite, this, thenValue, aThisVal, (int) IsPending());
+    if (!IsPending()) {
+      thenValue->Dispatch(this);
+    } else {
+      mThenValues.AppendElement(thenValue);
+    }
+  }
+
+private:
+  // Resolve and Reject may only be invoked via MediaPromiseHolder.
+  friend class MediaPromiseHolder<MediaPromise<ResolveValueType, RejectValueType>>;
+  void Resolve(ResolveValueType aResolveValue, const char* aResolveSite)
+  {
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(IsPending());
+    PROMISE_LOG("%s resolving MediaPromise (%p created at %s)", aResolveSite, this, mCreationSite);
+    mResolveValue.emplace(aResolveValue);
+    DispatchAll();
+  }
+
+  void Reject(RejectValueType aRejectValue, const char* aRejectSite)
+  {
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(IsPending());
+    PROMISE_LOG("%s rejecting MediaPromise (%p created at %s)", aRejectSite, this, mCreationSite);
+    mRejectValue.emplace(aRejectValue);
+    DispatchAll();
+  }
+
+protected:
+  bool IsPending() { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
+  void DispatchAll()
+  {
+    mMutex.AssertCurrentThreadOwns();
+    for (size_t i = 0; i < mThenValues.Length(); ++i)
+      mThenValues[i]->Dispatch(this);
+    mThenValues.Clear();
+  }
+
+  ~MediaPromise()
+  {
+    MOZ_COUNT_DTOR(MediaPromise);
+    PROMISE_LOG("MediaPromise::~MediaPromise [this=%p]", this);
+    MOZ_ASSERT(!IsPending());
+  };
+
+  const char* mCreationSite; // For logging
+  Mutex mMutex;
+  Maybe<ResolveValueType> mResolveValue;
+  Maybe<RejectValueType> mRejectValue;
+  nsTArray<ThenValueBase*> mThenValues;
+};
+
+/*
+ * 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:
+  MediaPromiseHolder()
+    : mMonitor(nullptr) {}
+
+  ~MediaPromiseHolder() { MOZ_ASSERT(!mPromise); }
+
+  already_AddRefed<PromiseType> Ensure(const char* aMethodName) {
+    if (mMonitor) {
+      mMonitor->AssertCurrentThreadOwns();
+    }
+    if (!mPromise) {
+      mPromise = new PromiseType(aMethodName);
+    }
+    nsRefPtr<PromiseType> p = mPromise;
+    return p.forget();
+  }
+
+  // Provide a Monitor that should always be held when accessing this instance.
+  void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; }
+
+  bool IsEmpty()
+  {
+    if (mMonitor) {
+      mMonitor->AssertCurrentThreadOwns();
+    }
+    return !mPromise;
+  }
+
+  void Resolve(typename PromiseType::ResolveValueType aResolveValue,
+               const char* aMethodName)
+  {
+    if (mMonitor) {
+      mMonitor->AssertCurrentThreadOwns();
+    }
+    MOZ_ASSERT(mPromise);
+    mPromise->Resolve(aResolveValue, aMethodName);
+    mPromise = nullptr;
+  }
+
+  void Reject(typename PromiseType::RejectValueType aRejectValue,
+              const char* aMethodName)
+  {
+    if (mMonitor) {
+      mMonitor->AssertCurrentThreadOwns();
+    }
+    MOZ_ASSERT(mPromise);
+    mPromise->Reject(aRejectValue, aMethodName);
+    mPromise = nullptr;
+  }
+
+private:
+  Monitor* mMonitor;
+  nsRefPtr<PromiseType> mPromise;
+};
+
+#undef PROMISE_LOG
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -100,16 +100,17 @@ EXPORTS += [
     'MediaData.h',
     'MediaDataDecodedListener.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
     'MediaInfo.h',
     'MediaMetadataManager.h',
+    'MediaPromise.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MediaTaskQueue.h',
     'MediaTrack.h',
     'MediaTrackList.h',