Bug 1126465 - Implement the ability to disconnect outstanding promises. r=mattwoodrow
authorBobby Holley <bobbyholley@gmail.com>
Thu, 29 Jan 2015 22:11:11 -0800
changeset 226763 33588deed1313b43cdcf99ca6d54013697583e8b
parent 226762 b5f0920defbe3afefd5b69b66914c4f62674684f
child 226764 7e85ee706260cc79257cc60082626afbc2c42661
push idunknown
push userunknown
push dateunknown
reviewersmattwoodrow
bugs1126465
milestone38.0a1
Bug 1126465 - Implement the ability to disconnect outstanding promises. r=mattwoodrow
dom/media/MediaPromise.cpp
dom/media/MediaPromise.h
--- a/dom/media/MediaPromise.cpp
+++ b/dom/media/MediaPromise.cpp
@@ -18,10 +18,23 @@ DispatchMediaPromiseRunnable(MediaTaskQu
 }
 
 nsresult
 DispatchMediaPromiseRunnable(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable)
 {
   return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
 }
 
+void
+AssertOnThread(MediaTaskQueue* aQueue)
+{
+  MOZ_ASSERT(aQueue->IsCurrentThreadIn());
+}
+
+void AssertOnThread(nsIEventTarget* aTarget)
+{
+  nsCOMPtr<nsIThread> targetThread = do_QueryInterface(aTarget);
+  MOZ_ASSERT(targetThread, "Don't know how to deal with threadpools etc here");
+  MOZ_ASSERT(NS_GetCurrentThread() == targetThread);
+}
+
 }
 } // namespace mozilla
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -32,16 +32,21 @@ extern PRLogModuleInfo* gMediaPromiseLog
   PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
 
 class MediaTaskQueue;
 namespace detail {
 
 nsresult DispatchMediaPromiseRunnable(MediaTaskQueue* aQueue, nsIRunnable* aRunnable);
 nsresult DispatchMediaPromiseRunnable(nsIEventTarget* aTarget, nsIRunnable* aRunnable);
 
+#ifdef DEBUG
+void AssertOnThread(MediaTaskQueue* aQueue);
+void AssertOnThread(nsIEventTarget* aTarget);
+#endif
+
 } // namespace detail
 
 /*
  * 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).
  *
@@ -80,19 +85,36 @@ public:
     p->Reject(aRejectValue, aRejectSite);
     return p;
   }
 
   class Consumer
   {
   public:
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Consumer)
+
+    void Disconnect()
+    {
+      AssertOnDispatchThread();
+      MOZ_RELEASE_ASSERT(!mComplete);
+      mDisconnected = true;
+    }
+
+#ifdef DEBUG
+    virtual void AssertOnDispatchThread() = 0;
+#else
+    void AssertOnDispatchThread() {}
+#endif
+
   protected:
-    Consumer() {}
+    Consumer() : mComplete(false), mDisconnected(false) {}
     virtual ~Consumer() {}
+
+    bool mComplete;
+    bool mDisconnected;
   };
 
 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 {Resolve,Reject}Runnable is dispatched, which
@@ -212,31 +234,49 @@ protected:
                  : static_cast<nsRunnable*>(new (typename ThenValueBase::RejectRunnable)(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 = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
 
+#ifdef DEBUG
+  // Can't mark MOZ_OVERRIDE due to bug in clang builders we use for osx b2g desktop. :-(
+  virtual void AssertOnDispatchThread()
+  {
+    detail::AssertOnThread(mResponseTarget);
+  }
+#endif
+
   protected:
     virtual void DoResolve(ResolveValueType aResolveValue) MOZ_OVERRIDE
     {
+      Consumer::mComplete = true;
+      if (Consumer::mDisconnected) {
+        PROMISE_LOG("ThenValue::DoResolve disconnected - bailing out [this=%p]", this);
+        return;
+      }
       InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aResolveValue);
 
       // Null these out after invoking the callback so that any references are
       // released predictably on the target thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mResponseTarget = nullptr;
       mThisVal = nullptr;
     }
 
     virtual void DoReject(RejectValueType aRejectValue) MOZ_OVERRIDE
     {
+      Consumer::mComplete = true;
+      if (Consumer::mDisconnected) {
+        PROMISE_LOG("ThenValue::DoReject disconnected - bailing out [this=%p]", this);
+        return;
+      }
       InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aRejectValue);
 
       // Null these out after invoking the callback so that any references are
       // released predictably on the target thread. Otherwise, they would be
       // released on whatever thread last drops its reference to the ThenValue,
       // which may or may not be ok.
       mResponseTarget = nullptr;
       mThisVal = nullptr;
@@ -466,16 +506,30 @@ public:
   }
 
   void Complete()
   {
     MOZ_RELEASE_ASSERT(Exists());
     mConsumer = nullptr;
   }
 
+  // Disconnects and forgets an outstanding promise. The resolve/reject methods
+  // will never be called.
+  void Disconnect() {
+    MOZ_ASSERT(Exists());
+    mConsumer->Disconnect();
+    mConsumer = nullptr;
+  }
+
+  void DisconnectIfExists() {
+    if (Exists()) {
+      Disconnect();
+    }
+  }
+
   bool Exists() { return !!mConsumer; }
 
 private:
   nsRefPtr<typename PromiseType::Consumer> mConsumer;
 };
 
 #undef PROMISE_LOG