Bug 1097823 - Implement Media Promises. r=cpearce
☠☠ backed out by 09ca5cf1876f ☠ ☠
authorBobby Holley <bobbyholley@gmail.com>
Mon, 08 Dec 2014 11:34:50 -0800
changeset 218734 ae63e2dda6e095f8220aec861300d4626eef0b34
parent 218733 a9ca0113db3c74d8a134077bb1d524e3ed070e2e
child 218735 09ca5cf1876ff10d25546da446c68101d36ef3ce
push id27942
push userryanvm@gmail.com
push dateMon, 08 Dec 2014 20:46:16 +0000
treeherdermozilla-central@f1f48ccb2d4e [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
@@ -456,17 +456,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',