Bug 1368382. P4 - move mCompletionPromise down the class hierarchy so it can store a different promise type. r=gerald
authorJW Wang <jwwang@mozilla.com>
Thu, 25 May 2017 17:35:47 +0800
changeset 409847 d96110d766199d42524558fd83e6467906de4daf
parent 409846 aa971939802838b324623e709034c09346006c21
child 409877 0bcea6bac1797e14b00af45cc7c368d12460ab7f
child 409880 208eb19a2a1ec6d9716eeb1c8f914376409daa05
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1368382
milestone55.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 1368382. P4 - move mCompletionPromise down the class hierarchy so it can store a different promise type. r=gerald MozReview-Commit-ID: 4beSwBzN8b3
xpcom/threads/MozPromise.h
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -129,20 +129,26 @@ using ReturnTypeIs =
 class MozPromiseRefcountable
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)
 protected:
   virtual ~MozPromiseRefcountable() {}
 };
 
+class MozPromiseBase : public MozPromiseRefcountable
+{
+public:
+  virtual void AssertIsDead() = 0;
+};
+
 template<typename T> class MozPromiseHolder;
 template<typename T> class MozPromiseRequestHolder;
 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
-class MozPromise : public MozPromiseRefcountable
+class MozPromise : public MozPromiseBase
 {
   static const uint32_t sMagic = 0xcecace11;
 
   // Return a |T&&| to enable move when IsExclusive is true or
   // a |const T&| to enforce copy otherwise.
   template <typename T,
     typename R = typename Conditional<IsExclusive, T&&, const T&>::Type>
   static R MaybeMove(T& aX)
@@ -422,18 +428,18 @@ protected:
       PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
       // We want to assert that this ThenValues is dead - that is to say, that
       // there are no consumers waiting for the result. In the case of a normal
       // ThenValue, we check that it has been disconnected, which is the way
       // that the consumer signals that it no longer wishes to hear about the
       // result. If this ThenValue has a completion promise (which is mutually
       // exclusive with being disconnectable), we recursively assert that every
       // ThenValue associated with the completion promise is dead.
-      if (mCompletionPromise) {
-        mCompletionPromise->AssertIsDead();
+      if (MozPromiseBase* p = CompletionPromise()) {
+        p->AssertIsDead();
       } else {
         MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
       }
     }
 
     void Dispatch(MozPromise *aPromise)
     {
       PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
@@ -457,52 +463,45 @@ protected:
       MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
       MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
       Request::mDisconnected = true;
 
       // We could support rejecting the completion promise on disconnection, but
       // then we'd need to have some sort of default reject value. The use cases
       // of disconnection and completion promise chaining seem pretty orthogonal,
       // so let's use assert against it.
-      MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise);
+      MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise());
     }
 
   protected:
-    virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0;
+    virtual MozPromiseBase* CompletionPromise() const = 0;
+    virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0;
 
     void DoResolveOrReject(ResolveOrRejectValue& aValue)
     {
       PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
       MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
       Request::mComplete = true;
       if (Request::mDisconnected) {
         PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this);
         return;
       }
 
       // Invoke the resolve or reject method.
-      RefPtr<MozPromise> result = DoResolveOrRejectInternal(aValue);
-
-      MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise || result,
-        "Can't do promise chaining for a non-promise-returning method.");
-
-      if (mCompletionPromise && result) {
-        result->ChainTo(mCompletionPromise.forget(), "<chained completion promise>");
-      }
+      DoResolveOrRejectInternal(aValue);
     }
 
     RefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
 #ifdef PROMISE_DEBUG
     uint32_t mMagic1 = sMagic;
 #endif
-    RefPtr<Private> mCompletionPromise;
+    const char* mCallSite;
 #ifdef PROMISE_DEBUG
     uint32_t mMagic2 = sMagic;
 #endif
-    const char* mCallSite;
   };
 
   /*
    * We create two overloads for invoking Resolve/Reject Methods so as to
    * make the resolve/reject value argument "optional".
    */
 
   template<typename ThisType, typename MethodType, typename ValueType>
@@ -580,40 +579,53 @@ protected:
 
       // If a Request has been disconnected, we don't guarantee that the
       // resolve/reject runnable will be dispatched. Null out our refcounted
       // this-value now so that it's released predictably on the dispatch thread.
       mThisVal = nullptr;
     }
 
   protected:
-    already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    MozPromiseBase* CompletionPromise() const override
     {
-      RefPtr<MozPromise> completion;
+      return mCompletionPromise;
+    }
+
+    void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    {
+      RefPtr<MozPromise> result;
       if (aValue.IsResolve()) {
-        completion = InvokeCallbackMethod(
+        result = InvokeCallbackMethod(
           mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()));
       } else {
-        completion = InvokeCallbackMethod(
+        result = InvokeCallbackMethod(
           mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()));
       }
 
       // Null out mThisVal after invoking the callback so that any references are
       // released predictably on the dispatch thread. Otherwise, it would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mThisVal = nullptr;
 
-      return completion.forget();
+      MOZ_DIAGNOSTIC_ASSERT(
+        !mCompletionPromise || result,
+        "Can't do promise chaining for a non-promise-returning method.");
+
+      if (mCompletionPromise && result) {
+        result->ChainTo(mCompletionPromise.forget(),
+                        "<chained completion promise>");
+      }
     }
 
   private:
     RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
     ResolveMethodType mResolveMethod;
     RejectMethodType mRejectMethod;
+    RefPtr<Private> mCompletionPromise;
   };
 
   template<typename ThisType, typename ResolveRejectMethodType>
   class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase
   {
     friend class ThenCommand<ThenValue>;
     using SupportChaining =
       ReturnTypeIs<ResolveRejectMethodType, RefPtr<MozPromise>>;
@@ -634,33 +646,46 @@ protected:
 
       // If a Request has been disconnected, we don't guarantee that the
       // resolve/reject runnable will be dispatched. Null out our refcounted
       // this-value now so that it's released predictably on the dispatch thread.
       mThisVal = nullptr;
     }
 
   protected:
-    already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    MozPromiseBase* CompletionPromise() const override
     {
-      RefPtr<MozPromise> completion = InvokeCallbackMethod(
+      return mCompletionPromise;
+    }
+
+    void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    {
+      RefPtr<MozPromise> result = InvokeCallbackMethod(
         mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue));
 
       // Null out mThisVal after invoking the callback so that any references are
       // released predictably on the dispatch thread. Otherwise, it would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mThisVal = nullptr;
 
-      return completion.forget();
+      MOZ_DIAGNOSTIC_ASSERT(
+        !mCompletionPromise || result,
+        "Can't do promise chaining for a non-promise-returning method.");
+
+      if (mCompletionPromise && result) {
+        result->ChainTo(mCompletionPromise.forget(),
+                        "<chained completion promise>");
+      }
     }
 
   private:
     RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
     ResolveRejectMethodType mResolveRejectMethod;
+    RefPtr<Private> mCompletionPromise;
   };
 
   // NB: We could use std::function here instead of a template if it were supported. :-(
   template<typename ResolveFunction, typename RejectFunction>
   class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase
   {
     friend class ThenCommand<ThenValue>;
     using SupportChaining = IntegralConstant<
@@ -687,45 +712,60 @@ protected:
       // resolve/reject runnable will be dispatched. Destroy our callbacks
       // now so that any references in closures are released predictable on
       // the dispatch thread.
       mResolveFunction.reset();
       mRejectFunction.reset();
     }
 
   protected:
-    already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    MozPromiseBase* CompletionPromise() const override
+    {
+      return mCompletionPromise;
+    }
+
+    void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
     {
       // Note: The usage of InvokeCallbackMethod here requires that
       // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous
       // classes with ::operator()), since it allows us to share code more easily.
       // We could fix this if need be, though it's quite easy to work around by
       // just capturing something.
-      RefPtr<MozPromise> completion;
+      RefPtr<MozPromise> result;
       if (aValue.IsResolve()) {
-        completion = InvokeCallbackMethod(mResolveFunction.ptr(),
-          &ResolveFunction::operator(), MaybeMove(aValue.ResolveValue()));
+        result = InvokeCallbackMethod(mResolveFunction.ptr(),
+                                      &ResolveFunction::operator(),
+                                      MaybeMove(aValue.ResolveValue()));
       } else {
-        completion = InvokeCallbackMethod(mRejectFunction.ptr(),
-          &RejectFunction::operator(), MaybeMove(aValue.RejectValue()));
+        result = InvokeCallbackMethod(mRejectFunction.ptr(),
+                                      &RejectFunction::operator(),
+                                      MaybeMove(aValue.RejectValue()));
       }
 
       // Destroy callbacks after invocation so that any references in closures are
       // released predictably on the dispatch thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mResolveFunction.reset();
       mRejectFunction.reset();
 
-      return completion.forget();
+      MOZ_DIAGNOSTIC_ASSERT(
+        !mCompletionPromise || result,
+        "Can't do promise chaining for a non-promise-returning method.");
+
+      if (mCompletionPromise && result) {
+        result->ChainTo(mCompletionPromise.forget(),
+                        "<chained completion promise>");
+      }
     }
 
   private:
     Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
     Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
+    RefPtr<Private> mCompletionPromise;
   };
 
   template<typename ResolveRejectFunction>
   class ThenValue<ResolveRejectFunction> : public ThenValueBase
   {
     friend class ThenCommand<ThenValue>;
     using SupportChaining =
       ReturnTypeIs<ResolveRejectFunction, RefPtr<MozPromise>>;
@@ -746,39 +786,52 @@ protected:
       // If a Request has been disconnected, we don't guarantee that the
       // resolve/reject runnable will be dispatched. Destroy our callbacks
       // now so that any references in closures are released predictable on
       // the dispatch thread.
       mResolveRejectFunction.reset();
     }
 
   protected:
-    already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+    MozPromiseBase* CompletionPromise() const override
+    {
+      return mCompletionPromise;
+    }
+
+    void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
     {
       // Note: The usage of InvokeCallbackMethod here requires that
       // ResolveRejectFunction is capture-lambdas (i.e. anonymous
       // classes with ::operator()), since it allows us to share code more easily.
       // We could fix this if need be, though it's quite easy to work around by
       // just capturing something.
-      RefPtr<MozPromise> completion =
+      RefPtr<MozPromise> result =
         InvokeCallbackMethod(mResolveRejectFunction.ptr(),
                              &ResolveRejectFunction::operator(),
                              MaybeMove(aValue));
 
       // Destroy callbacks after invocation so that any references in closures are
       // released predictably on the dispatch thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mResolveRejectFunction.reset();
 
-      return completion.forget();
+      MOZ_DIAGNOSTIC_ASSERT(
+        !mCompletionPromise || result,
+        "Can't do promise chaining for a non-promise-returning method.");
+
+      if (mCompletionPromise && result) {
+        result->ChainTo(mCompletionPromise.forget(),
+                        "<chained completion promise>");
+      }
     }
 
   private:
     Maybe<ResolveRejectFunction> mResolveRejectFunction; // Only accessed and deleted on dispatch thread.
+    RefPtr<Private> mCompletionPromise;
   };
 
 public:
   void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
                     const char* aCallSite)
   {
     PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex);
     MOZ_ASSERT(aResponseThread);
@@ -923,17 +976,17 @@ public:
       mChainedPromises.AppendElement(chainedPromise);
     }
   }
 
   // Note we expose the function AssertIsDead() instead of IsDead() since
   // checking IsDead() is a data race in the situation where the request is not
   // dead. Therefore we enforce the form |Assert(IsDead())| by exposing
   // AssertIsDead() only.
-  void AssertIsDead()
+  void AssertIsDead() override
   {
     PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex);
     MutexAutoLock lock(mMutex);
     for (auto&& then : mThenValues) {
       then->AssertIsDead();
     }
     for (auto&& chained : mChainedPromises) {
       chained->AssertIsDead();