/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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) #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 static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType)); template static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType) const); template static FalseType TakesArgumentHelper(Ret (ThisType::*)()); template static FalseType TakesArgumentHelper(Ret (ThisType::*)() const); template static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType)); template static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType) const); template static Ret ReturnTypeHelper(Ret (ThisType::*)()); template static Ret ReturnTypeHelper(Ret (ThisType::*)() const); template struct ReturnType { typedef decltype(detail::ReturnTypeHelper(DeclVal())) Type; }; } // namespace detail template struct TakesArgument { static const bool value = decltype(detail::TakesArgumentHelper(DeclVal()))::value; }; template struct ReturnTypeIs { static const bool value = IsConvertible::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 class MozPromiseHolder; template class MozPromise : public MozPromiseRefcountable { static const uint32_t sMagic = 0xcecace11; public: typedef ResolveValueT ResolveValueType; typedef RejectValueT RejectValueType; class ResolveOrRejectValue { public: template void SetResolve(ResolveValueType_&& aResolveValue) { MOZ_ASSERT(IsNothing()); mResolveValue.emplace(Forward(aResolveValue)); } template void SetReject(RejectValueType_&& aRejectValue) { MOZ_ASSERT(IsNothing()); mRejectValue.emplace(Forward(aRejectValue)); } template static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { ResolveOrRejectValue val; val.SetResolve(Forward(aResolveValue)); return val; } template static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { ResolveOrRejectValue val; val.SetReject(Forward(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 mResolveValue; Maybe 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 static RefPtr CreateAndResolve(ResolveValueType_&& aResolveValue, const char* aResolveSite) { RefPtr p = new MozPromise::Private(aResolveSite); p->Resolve(Forward(aResolveValue), aResolveSite); return p.forget(); } template static RefPtr CreateAndReject(RejectValueType_&& aRejectValue, const char* aRejectSite) { RefPtr p = new MozPromise::Private(aRejectSite); p->Reject(Forward(aRejectValue), aRejectSite); return p.forget(); } typedef MozPromise, 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 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> mResolveValues; RefPtr mPromise; size_t mOutstandingPromises; }; public: static RefPtr All(AbstractThread* aProcessingThread, nsTArray>& aPromises) { RefPtr 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 mThenValue; RefPtr 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( "", 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 = static_cast(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 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 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 completionPromise = dont_AddRef(static_cast(mCompletionPromise.forget().take())); if (completionPromise) { if (p) { p->ChainTo(completionPromise.forget(), ""); } else { completionPromise->ResolveOrReject(aValue, ""); } } } RefPtr mResponseTarget; // May be released on any thread. #ifdef PROMISE_DEBUG uint32_t mMagic1 = sMagic; #endif // Declaring RefPtr 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 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 static typename EnableIf>::value && TakesArgument::value, already_AddRefed>::Type InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return ((*aThisVal).*aMethod)(Forward(aValue)).forget(); } template static typename EnableIf::value && TakesArgument::value, already_AddRefed>::Type InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { ((*aThisVal).*aMethod)(Forward(aValue)); return nullptr; } template static typename EnableIf>::value && !TakesArgument::value, already_AddRefed>::Type InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return ((*aThisVal).*aMethod)().forget(); } template static typename EnableIf::value && !TakesArgument::value, already_AddRefed>::Type InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { ((*aThisVal).*aMethod)(); return nullptr; } template 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 DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) override { RefPtr 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 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 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 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 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 mResolveFunction; // Only accessed and deleted on dispatch thread. Maybe 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 RefPtr Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) { RefPtr thenValue = new MethodThenValue( aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite); ThenInternal(aResponseThread, thenValue, aCallSite); return thenValue.forget(); // Implicit conversion from already_AddRefed to RefPtr. } template RefPtr Then(AbstractThread* aResponseThread, const char* aCallSite, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { RefPtr thenValue = new FunctionThenValue(aResponseThread, Move(aResolveFunction), Move(aRejectFunction), aCallSite); ThenInternal(aResponseThread, thenValue, aCallSite); return thenValue.forget(); // Implicit conversion from already_AddRefed to RefPtr. } void ChainTo(already_AddRefed aChainedPromise, const char* aCallSite) { MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest); mHaveRequest = true; RefPtr 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(), ""); } else { aOther->Reject(mValue.RejectValue(), ""); } } 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> mThenValues; #ifdef PROMISE_DEBUG uint32_t mMagic2 = sMagic; #endif nsTArray> mChainedPromises; #ifdef PROMISE_DEBUG uint32_t mMagic3 = sMagic; #endif bool mHaveRequest; const bool mIsCompletionPromise; #ifdef PROMISE_DEBUG void* mMagic4; #endif }; template class MozPromise::Private : public MozPromise { public: explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false) : MozPromise(aCreationSite, aIsCompletionPromise) {} template 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(aResolveValue)); DispatchAll(); } template 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(aRejectValue)); DispatchAll(); } template 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(aValue); DispatchAll(); } }; // A generic promise type that does the trick for simple use cases. typedef MozPromise 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 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 Ensure(const char* aMethodName) { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } if (!mPromise) { mPromise = new (typename PromiseType::Private)(aMethodName); } RefPtr 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 Steal() { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } RefPtr 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 mPromise; }; /* * Class to encapsulate a MozPromise::Request reference. Use this as the member * variable for a class waiting on a MozPromise. */ template class MozPromiseRequestHolder { public: MozPromiseRequestHolder() {} ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } void Begin(RefPtr&& 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 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 ReturnType MethodCallInvokeHelper(ReturnType(ThisType::*aMethod)(ArgTypes...), ThisType* aThisVal, Tuple& aArgs, IndexSequence) { return ((*aThisVal).*aMethod)(Get(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 class MethodCall : public MethodCallBase { public: typedef RefPtr(ThisType::*MethodType)(ArgTypes...); MethodCall(MethodType aMethod, ThisType* aThisVal, ArgTypes... aArgs) : mMethod(aMethod) , mThisVal(aThisVal) , mArgs(Forward(aArgs)...) {} RefPtr Invoke() { return MethodCallInvokeHelper(mMethod, mThisVal.get(), mArgs, typename IndexSequenceFor::Type()); } private: MethodType mMethod; RefPtr mThisVal; Tuple mArgs; }; template class ProxyRunnable : public Runnable { public: ProxyRunnable(typename PromiseType::Private* aProxyPromise, MethodCall* aMethodCall) : mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {} NS_IMETHOD Run() override { RefPtr p = mMethodCall->Invoke(); mMethodCall = nullptr; p->ChainTo(mProxyPromise.forget(), ""); return NS_OK; } private: RefPtr mProxyPromise; nsAutoPtr> mMethodCall; }; constexpr bool Any() { return false; } template constexpr bool Any(T1 a) { return static_cast(a); } template constexpr bool Any(T1 a, Ts... aOthers) { return a || Any(aOthers...); } } // namespace detail template static RefPtr InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert(!detail::Any(IsReference::value...), "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it"); typedef detail::MethodCall MethodCallType; typedef detail::ProxyRunnable ProxyRunnableType; MethodCallType* methodCall = new MethodCallType(aMethod, aThisVal, Forward(aArgs)...); RefPtr p = new (typename PromiseType::Private)(aCallerName); RefPtr 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