Bug 1126465 - Implement the ability to disconnect outstanding promises. r=mattwoodrow, a=sledru
authorBobby Holley <bobbyholley@gmail.com>
Thu, 29 Jan 2015 22:11:11 -0800
changeset 243644 29c741d65b11
parent 243643 54d7f88c8b75
child 243645 26df0dd2cceb
push id4421
push userryanvm@gmail.com
push date2015-02-02 19:52 +0000
treeherdermozilla-beta@08a02585bc60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow, sledru
bugs1126465
milestone36.0
Bug 1126465 - Implement the ability to disconnect outstanding promises. r=mattwoodrow, a=sledru
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).
  *
@@ -81,19 +86,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
@@ -213,31 +235,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;
@@ -462,16 +502,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