summaryrefslogtreecommitdiffstats
path: root/xpcom/threads/MozPromise.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/threads/MozPromise.h')
-rw-r--r--xpcom/threads/MozPromise.h1067
1 files changed, 1067 insertions, 0 deletions
diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h
new file mode 100644
index 000000000..7a2921d2a
--- /dev/null
+++ b/xpcom/threads/MozPromise.h
@@ -0,0 +1,1067 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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(MozPromise_h_)
+#define MozPromise_h_
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/IndexSequence.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/TypeTraits.h"
+
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
+#define PROMISE_DEBUG
+#endif
+
+#ifdef PROMISE_DEBUG
+#define PROMISE_ASSERT MOZ_RELEASE_ASSERT
+#else
+#define PROMISE_ASSERT(...) do { } while (0)
+#endif
+
+namespace mozilla {
+
+extern LazyLogModule gMozPromiseLog;
+
+#define PROMISE_LOG(x, ...) \
+ MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
+
+namespace detail {
+template<typename ThisType, typename Ret, typename ArgType>
+static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType));
+template<typename ThisType, typename Ret, typename ArgType>
+static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType) const);
+template<typename ThisType, typename Ret>
+static FalseType TakesArgumentHelper(Ret (ThisType::*)());
+template<typename ThisType, typename Ret>
+static FalseType TakesArgumentHelper(Ret (ThisType::*)() const);
+
+template<typename ThisType, typename Ret, typename ArgType>
+static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType));
+template<typename ThisType, typename Ret, typename ArgType>
+static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType) const);
+template<typename ThisType, typename Ret>
+static Ret ReturnTypeHelper(Ret (ThisType::*)());
+template<typename ThisType, typename Ret>
+static Ret ReturnTypeHelper(Ret (ThisType::*)() const);
+
+template<typename MethodType>
+struct ReturnType {
+ typedef decltype(detail::ReturnTypeHelper(DeclVal<MethodType>())) Type;
+};
+
+} // namespace detail
+
+template<typename MethodType>
+struct TakesArgument {
+ static const bool value = decltype(detail::TakesArgumentHelper(DeclVal<MethodType>()))::value;
+};
+
+template<typename MethodType, typename TargetType>
+struct ReturnTypeIs {
+ static const bool value = IsConvertible<typename detail::ReturnType<MethodType>::Type, TargetType>::value;
+};
+
+/*
+ * 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).
+ * Whereas JS promise callbacks are dispatched from Microtask checkpoints,
+ * MozPromises resolution/rejection make a normal round-trip through the event
+ * loop, which simplifies their ordering semantics relative to other native code.
+ *
+ * MozPromises attempt to mirror the spirit of JS Promises to the extent that
+ * is possible (and desirable) in C++. While the intent is that MozPromises
+ * feel familiar to programmers who are accustomed to their JS-implemented cousin,
+ * we don't shy away from imposing restrictions and adding features that make
+ * sense for the use cases we encounter.
+ *
+ * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then()
+ * call accepts resolve and reject callbacks, and returns a MozPromise::Request.
+ * The Request object serves several purposes for the consumer.
+ *
+ * (1) It allows the caller to cancel the delivery of the resolve/reject value
+ * if it has not already occurred, via Disconnect() (this must be done on
+ * the target thread to avoid racing).
+ *
+ * (2) It provides access to a "Completion Promise", which is roughly analagous
+ * to the Promise returned directly by ->then() calls on JS promises. If
+ * the resolve/reject callback returns a new MozPromise, that promise is
+ * chained to the completion promise, such that its resolve/reject value
+ * will be forwarded along when it arrives. If the resolve/reject callback
+ * returns void, the completion promise is resolved/rejected with the same
+ * value that was passed to the callback.
+ *
+ * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs
+ * (rather than already_AddRefed) from various methods. This is done to allow elegant
+ * chaining of calls without cluttering up the code with intermediate variables, and
+ * without introducing separate API variants for callers that want a return value
+ * (from, say, ->Then()) from those that don't.
+ *
+ * When IsExclusive is true, the MozPromise does a release-mode assertion that
+ * there is at most one call to either Then(...) or ChainTo(...).
+ */
+
+class MozPromiseRefcountable
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)
+protected:
+ virtual ~MozPromiseRefcountable() {}
+};
+
+template<typename T> class MozPromiseHolder;
+template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise : public MozPromiseRefcountable
+{
+ static const uint32_t sMagic = 0xcecace11;
+
+public:
+ typedef ResolveValueT ResolveValueType;
+ typedef RejectValueT RejectValueType;
+ class ResolveOrRejectValue
+ {
+ public:
+ template<typename ResolveValueType_>
+ void SetResolve(ResolveValueType_&& aResolveValue)
+ {
+ MOZ_ASSERT(IsNothing());
+ mResolveValue.emplace(Forward<ResolveValueType_>(aResolveValue));
+ }
+
+ template<typename RejectValueType_>
+ void SetReject(RejectValueType_&& aRejectValue)
+ {
+ MOZ_ASSERT(IsNothing());
+ mRejectValue.emplace(Forward<RejectValueType_>(aRejectValue));
+ }
+
+ template<typename ResolveValueType_>
+ static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue)
+ {
+ ResolveOrRejectValue val;
+ val.SetResolve(Forward<ResolveValueType_>(aResolveValue));
+ return val;
+ }
+
+ template<typename RejectValueType_>
+ static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue)
+ {
+ ResolveOrRejectValue val;
+ val.SetReject(Forward<RejectValueType_>(aRejectValue));
+ return val;
+ }
+
+ bool IsResolve() const { return mResolveValue.isSome(); }
+ bool IsReject() const { return mRejectValue.isSome(); }
+ bool IsNothing() const { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
+
+ const ResolveValueType& ResolveValue() const { return mResolveValue.ref(); }
+ const RejectValueType& RejectValue() const { return mRejectValue.ref(); }
+
+ private:
+ Maybe<ResolveValueType> mResolveValue;
+ Maybe<RejectValueType> mRejectValue;
+ };
+
+protected:
+ // MozPromise is the public type, and never constructed directly. Construct
+ // a MozPromise::Private, defined below.
+ MozPromise(const char* aCreationSite, bool aIsCompletionPromise)
+ : mCreationSite(aCreationSite)
+ , mMutex("MozPromise Mutex")
+ , mHaveRequest(false)
+ , mIsCompletionPromise(aIsCompletionPromise)
+#ifdef PROMISE_DEBUG
+ , mMagic4(mMutex.mLock)
+#endif
+ {
+ PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this);
+ }
+
+public:
+ // MozPromise::Private allows us to separate the public interface (upon which
+ // consumers of the promise may invoke methods like Then()) from the private
+ // interface (upon which the creator of the promise may invoke Resolve() or
+ // Reject()). APIs should create and store a MozPromise::Private (usually
+ // via a MozPromiseHolder), and return a MozPromise to consumers.
+ //
+ // NB: We can include the definition of this class inline once B2G ICS is gone.
+ class Private;
+
+ template<typename ResolveValueType_>
+ static RefPtr<MozPromise>
+ CreateAndResolve(ResolveValueType_&& aResolveValue, const char* aResolveSite)
+ {
+ RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aResolveSite);
+ p->Resolve(Forward<ResolveValueType_>(aResolveValue), aResolveSite);
+ return p.forget();
+ }
+
+ template<typename RejectValueType_>
+ static RefPtr<MozPromise>
+ CreateAndReject(RejectValueType_&& aRejectValue, const char* aRejectSite)
+ {
+ RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aRejectSite);
+ p->Reject(Forward<RejectValueType_>(aRejectValue), aRejectSite);
+ return p.forget();
+ }
+
+ typedef MozPromise<nsTArray<ResolveValueType>, RejectValueType, IsExclusive> AllPromiseType;
+private:
+ class AllPromiseHolder : public MozPromiseRefcountable
+ {
+ public:
+ explicit AllPromiseHolder(size_t aDependentPromises)
+ : mPromise(new typename AllPromiseType::Private(__func__))
+ , mOutstandingPromises(aDependentPromises)
+ {
+ mResolveValues.SetLength(aDependentPromises);
+ }
+
+ void Resolve(size_t aIndex, const ResolveValueType& aResolveValue)
+ {
+ if (!mPromise) {
+ // Already rejected.
+ return;
+ }
+
+ mResolveValues[aIndex].emplace(aResolveValue);
+ if (--mOutstandingPromises == 0) {
+ nsTArray<ResolveValueType> resolveValues;
+ resolveValues.SetCapacity(mResolveValues.Length());
+ for (size_t i = 0; i < mResolveValues.Length(); ++i) {
+ resolveValues.AppendElement(mResolveValues[i].ref());
+ }
+
+ mPromise->Resolve(resolveValues, __func__);
+ mPromise = nullptr;
+ mResolveValues.Clear();
+ }
+ }
+
+ void Reject(const RejectValueType& aRejectValue)
+ {
+ if (!mPromise) {
+ // Already rejected.
+ return;
+ }
+
+ mPromise->Reject(aRejectValue, __func__);
+ mPromise = nullptr;
+ mResolveValues.Clear();
+ }
+
+ AllPromiseType* Promise() { return mPromise; }
+
+ private:
+ nsTArray<Maybe<ResolveValueType>> mResolveValues;
+ RefPtr<typename AllPromiseType::Private> mPromise;
+ size_t mOutstandingPromises;
+ };
+public:
+
+ static RefPtr<AllPromiseType> All(AbstractThread* aProcessingThread, nsTArray<RefPtr<MozPromise>>& aPromises)
+ {
+ RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length());
+ for (size_t i = 0; i < aPromises.Length(); ++i) {
+ aPromises[i]->Then(aProcessingThread, __func__,
+ [holder, i] (ResolveValueType aResolveValue) -> void { holder->Resolve(i, aResolveValue); },
+ [holder] (RejectValueType aRejectValue) -> void { holder->Reject(aRejectValue); }
+ );
+ }
+ return holder->Promise();
+ }
+
+ class Request : public MozPromiseRefcountable
+ {
+ public:
+ virtual void Disconnect() = 0;
+
+ virtual MozPromise* CompletionPromise() = 0;
+
+ virtual void AssertIsDead() = 0;
+
+ protected:
+ Request() : mComplete(false), mDisconnected(false) {}
+ virtual ~Request() {}
+
+ 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
+ * invokes the resolve/reject method and then deletes the ThenValue.
+ */
+ class ThenValueBase : public Request
+ {
+ static const uint32_t sMagic = 0xfadece11;
+
+ public:
+ class ResolveOrRejectRunnable : public Runnable
+ {
+ public:
+ ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise)
+ : mThenValue(aThenValue)
+ , mPromise(aPromise)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending());
+ }
+
+ ~ResolveOrRejectRunnable()
+ {
+ if (mThenValue) {
+ mThenValue->AssertIsDead();
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this);
+ mThenValue->DoResolveOrReject(mPromise->Value());
+ mThenValue = nullptr;
+ mPromise = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ThenValueBase> mThenValue;
+ RefPtr<MozPromise> mPromise;
+ };
+
+ explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite)
+ : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {}
+
+#ifdef PROMISE_DEBUG
+ ~ThenValueBase()
+ {
+ mMagic1 = 0;
+ mMagic2 = 0;
+ }
+#endif
+
+ MozPromise* CompletionPromise() override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
+ MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
+ if (!mCompletionPromise) {
+ mCompletionPromise = new MozPromise::Private(
+ "<completion promise>", true /* aIsCompletionPromise */);
+ }
+ return mCompletionPromise;
+ }
+
+ void AssertIsDead() override
+ {
+ 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();
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
+ }
+ }
+
+ void Dispatch(MozPromise *aPromise)
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
+ aPromise->mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!aPromise->IsPending());
+
+ RefPtr<Runnable> runnable =
+ static_cast<Runnable*>(new (typename ThenValueBase::ResolveOrRejectRunnable)(this, aPromise));
+ PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
+ aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
+ runnable.get(), aPromise, this);
+
+ // Promise consumers are allowed to disconnect the Request object and
+ // then shut down the thread or task queue that the promise result would
+ // be dispatched on. So we unfortunately can't assert that promise
+ // dispatch succeeds. :-(
+ mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
+ }
+
+ virtual void Disconnect() override
+ {
+ MOZ_DIAGNOSTIC_ASSERT(ThenValueBase::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);
+ }
+
+ protected:
+ virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) = 0;
+
+ void DoResolveOrReject(const 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> p = DoResolveOrRejectInternal(aValue);
+
+ // If there's a completion promise, resolve it appropriately with the
+ // result of the method.
+ //
+ // We jump through some hoops to cast to MozPromise::Private here. This
+ // can go away when we can just declare mCompletionPromise as
+ // MozPromise::Private. See the declaration below.
+ RefPtr<MozPromise::Private> completionPromise =
+ dont_AddRef(static_cast<MozPromise::Private*>(mCompletionPromise.forget().take()));
+ if (completionPromise) {
+ if (p) {
+ p->ChainTo(completionPromise.forget(), "<chained completion promise>");
+ } else {
+ completionPromise->ResolveOrReject(aValue, "<completion of non-promise-returning method>");
+ }
+ }
+ }
+
+ RefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
+#ifdef PROMISE_DEBUG
+ uint32_t mMagic1 = sMagic;
+#endif
+ // Declaring RefPtr<MozPromise::Private> here causes build failures
+ // on MSVC because MozPromise::Private is only forward-declared at this
+ // point. This hack can go away when we inline-declare MozPromise::Private,
+ // which is blocked on the B2G ICS compiler being too old.
+ RefPtr<MozPromise> mCompletionPromise;
+#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>
+ static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
+ TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ {
+ return ((*aThisVal).*aMethod)(Forward<ValueType>(aValue)).forget();
+ }
+
+ template<typename ThisType, typename MethodType, typename ValueType>
+ static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
+ TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ {
+ ((*aThisVal).*aMethod)(Forward<ValueType>(aValue));
+ return nullptr;
+ }
+
+ template<typename ThisType, typename MethodType, typename ValueType>
+ static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
+ !TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ {
+ return ((*aThisVal).*aMethod)().forget();
+ }
+
+ template<typename ThisType, typename MethodType, typename ValueType>
+ static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
+ !TakesArgument<MethodType>::value,
+ already_AddRefed<MozPromise>>::Type
+ InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
+ {
+ ((*aThisVal).*aMethod)();
+ return nullptr;
+ }
+
+ template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+ class MethodThenValue : public ThenValueBase
+ {
+ public:
+ MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
+ ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite)
+ , mThisVal(aThisVal)
+ , mResolveMethod(aResolveMethod)
+ , mRejectMethod(aRejectMethod) {}
+
+ virtual void Disconnect() override
+ {
+ ThenValueBase::Disconnect();
+
+ // 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:
+ virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) override
+ {
+ RefPtr<MozPromise> completion;
+ if (aValue.IsResolve()) {
+ completion = InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aValue.ResolveValue());
+ } else {
+ completion = InvokeCallbackMethod(mThisVal.get(), mRejectMethod, 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();
+ }
+
+ private:
+ RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
+ ResolveMethodType mResolveMethod;
+ RejectMethodType mRejectMethod;
+ };
+
+ // NB: We could use std::function here instead of a template if it were supported. :-(
+ template<typename ResolveFunction, typename RejectFunction>
+ class FunctionThenValue : public ThenValueBase
+ {
+ public:
+ FunctionThenValue(AbstractThread* aResponseTarget,
+ ResolveFunction&& aResolveFunction,
+ RejectFunction&& aRejectFunction,
+ const char* aCallSite)
+ : ThenValueBase(aResponseTarget, aCallSite)
+ {
+ mResolveFunction.emplace(Move(aResolveFunction));
+ mRejectFunction.emplace(Move(aRejectFunction));
+ }
+
+ virtual void Disconnect() override
+ {
+ ThenValueBase::Disconnect();
+
+ // 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.
+ mResolveFunction.reset();
+ mRejectFunction.reset();
+ }
+
+ protected:
+ virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const 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;
+ if (aValue.IsResolve()) {
+ completion = InvokeCallbackMethod(mResolveFunction.ptr(), &ResolveFunction::operator(), aValue.ResolveValue());
+ } else {
+ completion = InvokeCallbackMethod(mRejectFunction.ptr(), &RejectFunction::operator(), 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();
+ }
+
+ private:
+ Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
+ Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
+ };
+
+public:
+ void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
+ const char* aCallSite)
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(aResponseThread->IsDispatchReliable());
+ MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
+ mHaveRequest = true;
+ PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
+ aCallSite, this, aThenValue, (int) IsPending());
+ if (!IsPending()) {
+ aThenValue->Dispatch(this);
+ } else {
+ mThenValues.AppendElement(aThenValue);
+ }
+ }
+
+public:
+
+ template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+ RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
+ ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+ {
+ RefPtr<ThenValueBase> thenValue = new MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>(
+ aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite);
+ ThenInternal(aResponseThread, thenValue, aCallSite);
+ return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
+ }
+
+ template<typename ResolveFunction, typename RejectFunction>
+ RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite,
+ ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+ {
+ RefPtr<ThenValueBase> thenValue = new FunctionThenValue<ResolveFunction, RejectFunction>(aResponseThread,
+ Move(aResolveFunction), Move(aRejectFunction), aCallSite);
+ ThenInternal(aResponseThread, thenValue, aCallSite);
+ return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
+ }
+
+ void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
+ mHaveRequest = true;
+ RefPtr<Private> chainedPromise = aChainedPromise;
+ PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
+ aCallSite, this, chainedPromise.get(), (int) IsPending());
+ if (!IsPending()) {
+ ForwardTo(chainedPromise);
+ } else {
+ 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()
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
+ MutexAutoLock lock(mMutex);
+ for (auto&& then : mThenValues) {
+ then->AssertIsDead();
+ }
+ for (auto&& chained : mChainedPromises) {
+ chained->AssertIsDead();
+ }
+ }
+
+protected:
+ bool IsPending() const { return mValue.IsNothing(); }
+ const ResolveOrRejectValue& Value() const
+ {
+ // This method should only be called once the value has stabilized. As
+ // such, we don't need to acquire the lock here.
+ MOZ_DIAGNOSTIC_ASSERT(!IsPending());
+ return mValue;
+ }
+
+ void DispatchAll()
+ {
+ mMutex.AssertCurrentThreadOwns();
+ for (size_t i = 0; i < mThenValues.Length(); ++i) {
+ mThenValues[i]->Dispatch(this);
+ }
+ mThenValues.Clear();
+
+ for (size_t i = 0; i < mChainedPromises.Length(); ++i) {
+ ForwardTo(mChainedPromises[i]);
+ }
+ mChainedPromises.Clear();
+ }
+
+ void ForwardTo(Private* aOther)
+ {
+ MOZ_ASSERT(!IsPending());
+ if (mValue.IsResolve()) {
+ aOther->Resolve(mValue.ResolveValue(), "<chained promise>");
+ } else {
+ aOther->Reject(mValue.RejectValue(), "<chained promise>");
+ }
+ }
+
+ virtual ~MozPromise()
+ {
+ PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this);
+ AssertIsDead();
+ // We can't guarantee a completion promise will always be revolved or
+ // rejected since ResolveOrRejectRunnable might not run when dispatch fails.
+ if (!mIsCompletionPromise) {
+ MOZ_ASSERT(!IsPending());
+ MOZ_ASSERT(mThenValues.IsEmpty());
+ MOZ_ASSERT(mChainedPromises.IsEmpty());
+ }
+#ifdef PROMISE_DEBUG
+ mMagic1 = 0;
+ mMagic2 = 0;
+ mMagic3 = 0;
+ mMagic4 = nullptr;
+#endif
+ };
+
+ const char* mCreationSite; // For logging
+ Mutex mMutex;
+ ResolveOrRejectValue mValue;
+#ifdef PROMISE_DEBUG
+ uint32_t mMagic1 = sMagic;
+#endif
+ nsTArray<RefPtr<ThenValueBase>> mThenValues;
+#ifdef PROMISE_DEBUG
+ uint32_t mMagic2 = sMagic;
+#endif
+ nsTArray<RefPtr<Private>> mChainedPromises;
+#ifdef PROMISE_DEBUG
+ uint32_t mMagic3 = sMagic;
+#endif
+ bool mHaveRequest;
+ const bool mIsCompletionPromise;
+#ifdef PROMISE_DEBUG
+ void* mMagic4;
+#endif
+};
+
+template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
+class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private
+ : public MozPromise<ResolveValueT, RejectValueT, IsExclusive>
+{
+public:
+ explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false)
+ : MozPromise(aCreationSite, aIsCompletionPromise) {}
+
+ template<typename ResolveValueT_>
+ void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite)
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(IsPending());
+ PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite);
+ mValue.SetResolve(Forward<ResolveValueT_>(aResolveValue));
+ DispatchAll();
+ }
+
+ template<typename RejectValueT_>
+ void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite)
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(IsPending());
+ PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite);
+ mValue.SetReject(Forward<RejectValueT_>(aRejectValue));
+ DispatchAll();
+ }
+
+ template<typename ResolveOrRejectValue_>
+ void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite)
+ {
+ PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(IsPending());
+ PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite);
+ mValue = Forward<ResolveOrRejectValue_>(aValue);
+ DispatchAll();
+ }
+};
+
+// A generic promise type that does the trick for simple use cases.
+typedef MozPromise<bool, nsresult, /* IsExclusive = */ false> GenericPromise;
+
+/*
+ * 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 MozPromiseHolder
+{
+public:
+ MozPromiseHolder()
+ : mMonitor(nullptr) {}
+
+ // Move semantics.
+ MozPromiseHolder& operator=(MozPromiseHolder&& aOther)
+ {
+ MOZ_ASSERT(!mMonitor && !aOther.mMonitor);
+ MOZ_DIAGNOSTIC_ASSERT(!mPromise);
+ mPromise = aOther.mPromise;
+ aOther.mPromise = nullptr;
+ return *this;
+ }
+
+ ~MozPromiseHolder() { MOZ_ASSERT(!mPromise); }
+
+ already_AddRefed<PromiseType> Ensure(const char* aMethodName) {
+ if (mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+ if (!mPromise) {
+ mPromise = new (typename PromiseType::Private)(aMethodName);
+ }
+ RefPtr<PromiseType> p = mPromise.get();
+ return p.forget();
+ }
+
+ // Provide a Monitor that should always be held when accessing this instance.
+ void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; }
+
+ bool IsEmpty() const
+ {
+ if (mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+ return !mPromise;
+ }
+
+ already_AddRefed<typename PromiseType::Private> Steal()
+ {
+ if (mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+
+ RefPtr<typename PromiseType::Private> p = mPromise;
+ mPromise = nullptr;
+ return p.forget();
+ }
+
+ void Resolve(typename PromiseType::ResolveValueType aResolveValue,
+ const char* aMethodName)
+ {
+ if (mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+ MOZ_ASSERT(mPromise);
+ mPromise->Resolve(aResolveValue, aMethodName);
+ mPromise = nullptr;
+ }
+
+
+ void ResolveIfExists(typename PromiseType::ResolveValueType aResolveValue,
+ const char* aMethodName)
+ {
+ if (!IsEmpty()) {
+ Resolve(aResolveValue, aMethodName);
+ }
+ }
+
+ void Reject(typename PromiseType::RejectValueType aRejectValue,
+ const char* aMethodName)
+ {
+ if (mMonitor) {
+ mMonitor->AssertCurrentThreadOwns();
+ }
+ MOZ_ASSERT(mPromise);
+ mPromise->Reject(aRejectValue, aMethodName);
+ mPromise = nullptr;
+ }
+
+
+ void RejectIfExists(typename PromiseType::RejectValueType aRejectValue,
+ const char* aMethodName)
+ {
+ if (!IsEmpty()) {
+ Reject(aRejectValue, aMethodName);
+ }
+ }
+
+private:
+ Monitor* mMonitor;
+ RefPtr<typename PromiseType::Private> mPromise;
+};
+
+/*
+ * Class to encapsulate a MozPromise::Request reference. Use this as the member
+ * variable for a class waiting on a MozPromise.
+ */
+template<typename PromiseType>
+class MozPromiseRequestHolder
+{
+public:
+ MozPromiseRequestHolder() {}
+ ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); }
+
+ void Begin(RefPtr<typename PromiseType::Request>&& aRequest)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(!Exists());
+ mRequest = Move(aRequest);
+ }
+
+ void Begin(typename PromiseType::Request* aRequest)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(!Exists());
+ mRequest = aRequest;
+ }
+
+ void Complete()
+ {
+ MOZ_DIAGNOSTIC_ASSERT(Exists());
+ mRequest = nullptr;
+ }
+
+ // Disconnects and forgets an outstanding promise. The resolve/reject methods
+ // will never be called.
+ void Disconnect() {
+ MOZ_ASSERT(Exists());
+ mRequest->Disconnect();
+ mRequest = nullptr;
+ }
+
+ void DisconnectIfExists() {
+ if (Exists()) {
+ Disconnect();
+ }
+ }
+
+ bool Exists() const { return !!mRequest; }
+
+private:
+ RefPtr<typename PromiseType::Request> mRequest;
+};
+
+// Asynchronous Potentially-Cross-Thread Method Calls.
+//
+// This machinery allows callers to schedule a promise-returning method to be
+// invoked asynchronously on a given thread, while at the same time receiving
+// a promise upon which to invoke Then() immediately. InvokeAsync dispatches
+// a task to invoke the method on the proper thread and also chain the resulting
+// promise to the one that the caller received, so that resolve/reject values
+// are forwarded through.
+
+namespace detail {
+
+template<typename ReturnType, typename ThisType, typename... ArgTypes, size_t... Indices>
+ReturnType
+MethodCallInvokeHelper(ReturnType(ThisType::*aMethod)(ArgTypes...), ThisType* aThisVal,
+ Tuple<ArgTypes...>& aArgs, IndexSequence<Indices...>)
+{
+ return ((*aThisVal).*aMethod)(Get<Indices>(aArgs)...);
+}
+
+// Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause
+// assertions when used on templated types.
+class MethodCallBase
+{
+public:
+ MethodCallBase() { MOZ_COUNT_CTOR(MethodCallBase); }
+ virtual ~MethodCallBase() { MOZ_COUNT_DTOR(MethodCallBase); }
+};
+
+template<typename PromiseType, typename ThisType, typename... ArgTypes>
+class MethodCall : public MethodCallBase
+{
+public:
+ typedef RefPtr<PromiseType>(ThisType::*MethodType)(ArgTypes...);
+ MethodCall(MethodType aMethod, ThisType* aThisVal, ArgTypes... aArgs)
+ : mMethod(aMethod)
+ , mThisVal(aThisVal)
+ , mArgs(Forward<ArgTypes>(aArgs)...)
+ {}
+
+ RefPtr<PromiseType> Invoke()
+ {
+ return MethodCallInvokeHelper(mMethod, mThisVal.get(), mArgs, typename IndexSequenceFor<ArgTypes...>::Type());
+ }
+
+private:
+ MethodType mMethod;
+ RefPtr<ThisType> mThisVal;
+ Tuple<ArgTypes...> mArgs;
+};
+
+template<typename PromiseType, typename ThisType, typename ...ArgTypes>
+class ProxyRunnable : public Runnable
+{
+public:
+ ProxyRunnable(typename PromiseType::Private* aProxyPromise, MethodCall<PromiseType, ThisType, ArgTypes...>* aMethodCall)
+ : mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {}
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<PromiseType> p = mMethodCall->Invoke();
+ mMethodCall = nullptr;
+ p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>");
+ return NS_OK;
+ }
+
+private:
+ RefPtr<typename PromiseType::Private> mProxyPromise;
+ nsAutoPtr<MethodCall<PromiseType, ThisType, ArgTypes...>> mMethodCall;
+};
+
+constexpr bool Any()
+{
+ return false;
+}
+
+template <typename T1>
+constexpr bool Any(T1 a)
+{
+ return static_cast<bool>(a);
+}
+
+template <typename T1, typename... Ts>
+constexpr bool Any(T1 a, Ts... aOthers)
+{
+ return a || Any(aOthers...);
+}
+
+} // namespace detail
+
+template<typename PromiseType, typename ThisType, typename ...ArgTypes, typename ...ActualArgTypes>
+static RefPtr<PromiseType>
+InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName,
+ RefPtr<PromiseType>(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs)
+{
+ static_assert(!detail::Any(IsReference<ArgTypes>::value...),
+ "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it");
+ typedef detail::MethodCall<PromiseType, ThisType, ArgTypes...> MethodCallType;
+ typedef detail::ProxyRunnable<PromiseType, ThisType, ArgTypes...> ProxyRunnableType;
+
+ MethodCallType* methodCall = new MethodCallType(aMethod, aThisVal, Forward<ActualArgTypes>(aArgs)...);
+ RefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
+ RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall);
+ MOZ_ASSERT(aTarget->IsDispatchReliable());
+ aTarget->Dispatch(r.forget());
+ return p.forget();
+}
+
+#undef PROMISE_LOG
+#undef PROMISE_ASSERT
+#undef PROMISE_DEBUG
+
+} // namespace mozilla
+
+#endif