Bug 1126465 - Implement the ability to disconnect outstanding promises. r=mattwoodrow
--- 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