/* -*- 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/Logging.h" # include "mozilla/Maybe.h" # include "mozilla/Mutex.h" # include "mozilla/Monitor.h" # include "mozilla/RefPtr.h" # include "mozilla/Tuple.h" # include "mozilla/TypeTraits.h" # include "mozilla/Variant.h" # include "nsISerialEventTarget.h" # include "nsTArray.h" # include "nsThreadUtils.h" # if MOZ_DIAGNOSTIC_ASSERT_ENABLED # 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 struct MethodTraitsHelper : MethodTraitsHelper {}; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTrait : MethodTraitsHelper::Type> {}; } // namespace detail template using TakesArgument = IntegralConstant::ArgSize != 0>; template using ReturnTypeIs = IsConvertible::ReturnType, TargetType>; template class MozPromise; template struct IsMozPromise : FalseType {}; template struct IsMozPromise> : TrueType {}; /* * 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 magic object which * will be implicitly converted to a MozPromise::Request or a MozPromise object * depending on how the return value is used. The magic object serves several * purposes for the consumer. * * (1) When converting to a MozPromise::Request, 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) When converting to a MozPromise (which is called a completion promise), * it allows promise chaining so ->Then() can be called again to attach * more resolve and reject callbacks. 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() {} }; class MozPromiseBase : public MozPromiseRefcountable { public: virtual void AssertIsDead() = 0; }; template class MozPromiseHolder; template class MozPromiseRequestHolder; template class MozPromise : public MozPromiseBase { static const uint32_t sMagic = 0xcecace11; // Return a |T&&| to enable move when IsExclusive is true or // a |const T&| to enforce copy otherwise. template ::Type> static R MaybeMove(T& aX) { return static_cast(aX); } public: typedef ResolveValueT ResolveValueType; typedef RejectValueT RejectValueType; class ResolveOrRejectValue { public: template void SetResolve(ResolveValueType_&& aResolveValue) { MOZ_ASSERT(IsNothing()); mValue = Storage(VariantIndex{}, std::forward(aResolveValue)); } template void SetReject(RejectValueType_&& aRejectValue) { MOZ_ASSERT(IsNothing()); mValue = Storage(VariantIndex{}, std::forward(aRejectValue)); } template static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { ResolveOrRejectValue val; val.SetResolve(std::forward(aResolveValue)); return val; } template static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { ResolveOrRejectValue val; val.SetReject(std::forward(aRejectValue)); return val; } bool IsResolve() const { return mValue.template is(); } bool IsReject() const { return mValue.template is(); } bool IsNothing() const { return mValue.template is(); } const ResolveValueType& ResolveValue() const { return mValue.template as(); } ResolveValueType& ResolveValue() { return mValue.template as(); } const RejectValueType& RejectValue() const { return mValue.template as(); } RejectValueType& RejectValue() { return mValue.template as(); } private: enum { NothingIndex, ResolveIndex, RejectIndex }; using Storage = Variant; Storage mValue = Storage(VariantIndex{}); }; 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) # 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) { static_assert(IsConvertible::value, "Resolve() argument must be implicitly convertible to " "MozPromise's ResolveValueT"); RefPtr p = new MozPromise::Private(aResolveSite); p->Resolve(std::forward(aResolveValue), aResolveSite); return p.forget(); } template static RefPtr CreateAndReject(RejectValueType_&& aRejectValue, const char* aRejectSite) { static_assert(IsConvertible::value, "Reject() argument must be implicitly convertible to " "MozPromise's RejectValueT"); RefPtr p = new MozPromise::Private(aRejectSite); p->Reject(std::forward(aRejectValue), aRejectSite); return p.forget(); } template static RefPtr CreateAndResolveOrReject( ResolveOrRejectValueType_&& aValue, const char* aSite) { RefPtr p = new MozPromise::Private(aSite); p->ResolveOrReject(std::forward(aValue), aSite); 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) { MOZ_ASSERT(aDependentPromises > 0); mResolveValues.SetLength(aDependentPromises); } void Resolve(size_t aIndex, ResolveValueType&& aResolveValue) { if (!mPromise) { // Already rejected. return; } mResolveValues[aIndex].emplace(std::move(aResolveValue)); if (--mOutstandingPromises == 0) { nsTArray resolveValues; resolveValues.SetCapacity(mResolveValues.Length()); for (auto&& resolveValue : mResolveValues) { resolveValues.AppendElement(std::move(resolveValue.ref())); } mPromise->Resolve(std::move(resolveValues), __func__); mPromise = nullptr; mResolveValues.Clear(); } } void Reject(RejectValueType&& aRejectValue) { if (!mPromise) { // Already rejected. return; } mPromise->Reject(std::move(aRejectValue), __func__); mPromise = nullptr; mResolveValues.Clear(); } AllPromiseType* Promise() { return mPromise; } private: nsTArray> mResolveValues; RefPtr mPromise; size_t mOutstandingPromises; }; public: static RefPtr All(nsISerialEventTarget* aProcessingTarget, nsTArray>& aPromises) { if (aPromises.Length() == 0) { return AllPromiseType::CreateAndResolve(nsTArray(), __func__); } RefPtr holder = new AllPromiseHolder(aPromises.Length()); RefPtr promise = holder->Promise(); for (size_t i = 0; i < aPromises.Length(); ++i) { aPromises[i]->Then(aProcessingTarget, __func__, [holder, i](ResolveValueType aResolveValue) -> void { holder->Resolve(i, std::move(aResolveValue)); }, [holder](RejectValueType aRejectValue) -> void { holder->Reject(std::move(aRejectValue)); }); } return promise; } class Request : public MozPromiseRefcountable { public: virtual void Disconnect() = 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 { friend class MozPromise; static const uint32_t sMagic = 0xfadece11; public: class ResolveOrRejectRunnable : public CancelableRunnable { public: ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) : CancelableRunnable( "MozPromise::ThenValueBase::ResolveOrRejectRunnable"), 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; } nsresult Cancel() override { return Run(); } private: RefPtr mThenValue; RefPtr mPromise; }; ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { MOZ_ASSERT(aResponseTarget); } # ifdef PROMISE_DEBUG ~ThenValueBase() { mMagic1 = 0; mMagic2 = 0; } # endif void AssertIsDead() { 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 (MozPromiseBase* p = CompletionPromise()) { p->AssertIsDead(); } else { MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected); } } void Dispatch(MozPromise* aPromise) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); aPromise->mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(!aPromise->IsPending()); nsCOMPtr r = new ResolveOrRejectRunnable(this, aPromise); PROMISE_LOG( "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]", aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, r.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(r.forget()); } void Disconnect() override { MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); 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(!CompletionPromise()); } protected: virtual MozPromiseBase* CompletionPromise() const = 0; virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0; void DoResolveOrReject(ResolveOrRejectValue& aValue) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); Request::mComplete = true; if (Request::mDisconnected) { PROMISE_LOG( "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this); return; } // Invoke the resolve or reject method. DoResolveOrRejectInternal(aValue); } nsCOMPtr mResponseTarget; // May be released on any thread. # ifdef PROMISE_DEBUG uint32_t mMagic1 = sMagic; # endif const char* mCallSite; # ifdef PROMISE_DEBUG uint32_t mMagic2 = sMagic; # endif }; /* * We create two overloads for invoking Resolve/Reject Methods so as to * make the resolve/reject value argument "optional". */ template static typename EnableIf< TakesArgument::value, typename detail::MethodTrait::ReturnType>::Type InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return (aThisVal->*aMethod)(std::forward(aValue)); } template static typename EnableIf< !TakesArgument::value, typename detail::MethodTrait::ReturnType>::Type InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return (aThisVal->*aMethod)(); } // Called when promise chaining is supported. template static typename EnableIf::Type InvokeCallbackMethod( ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, CompletionPromiseType&& aCompletionPromise) { auto p = InvokeMethod(aThisVal, aMethod, std::forward(aValue)); if (aCompletionPromise) { p->ChainTo(aCompletionPromise.forget(), ""); } } // Called when promise chaining is not supported. template static typename EnableIf::Type InvokeCallbackMethod( ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, CompletionPromiseType&& aCompletionPromise) { MOZ_DIAGNOSTIC_ASSERT( !aCompletionPromise, "Can't do promise chaining for a non-promise-returning method."); InvokeMethod(aThisVal, aMethod, std::forward(aValue)); } template class ThenCommand; template class ThenValue; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = typename RemoveSmartPointer< typename detail::MethodTrait::ReturnType>::Type; using R2 = typename RemoveSmartPointer< typename detail::MethodTrait::ReturnType>::Type; using SupportChaining = IntegralConstant::value && IsSame::value>; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = typename Conditional::Type; public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod) {} 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: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { if (aValue.IsResolve()) { InvokeCallbackMethod( mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); } else { InvokeCallbackMethod( mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); } // 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; } private: RefPtr mThisVal; // Only accessed and refcounted on dispatch thread. ResolveMethodType mResolveMethod; RejectMethodType mRejectMethod; RefPtr mCompletionPromise; }; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = typename RemoveSmartPointer::ReturnType>::Type; using SupportChaining = IntegralConstant::value>; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = typename Conditional::Type; public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveRejectMethodType aResolveRejectMethod, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveRejectMethod(aResolveRejectMethod) {} 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: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { InvokeCallbackMethod( mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue), std::move(mCompletionPromise)); // 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; } private: RefPtr mThisVal; // Only accessed and refcounted on dispatch thread. ResolveRejectMethodType mResolveRejectMethod; RefPtr mCompletionPromise; }; // NB: We could use std::function here instead of a template if it were // supported. :-( template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = typename RemoveSmartPointer< typename detail::MethodTrait::ReturnType>::Type; using R2 = typename RemoveSmartPointer< typename detail::MethodTrait::ReturnType>::Type; using SupportChaining = IntegralConstant::value && IsSame::value>; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = typename Conditional::Type; public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveFunction.emplace(std::move(aResolveFunction)); mRejectFunction.emplace(std::move(aRejectFunction)); } 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: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(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. if (aValue.IsResolve()) { InvokeCallbackMethod( mResolveFunction.ptr(), &ResolveFunction::operator(), MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); } else { InvokeCallbackMethod( mRejectFunction.ptr(), &RejectFunction::operator(), MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); } // 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(); } private: Maybe mResolveFunction; // Only accessed and deleted on dispatch thread. Maybe mRejectFunction; // Only accessed and deleted on dispatch thread. RefPtr mCompletionPromise; }; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = typename RemoveSmartPointer< typename detail::MethodTrait::ReturnType>::Type; using SupportChaining = IntegralConstant::value>; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = typename Conditional::Type; public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveRejectFunction&& aResolveRejectFunction, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); } 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. mResolveRejectFunction.reset(); } protected: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { // Note: The usage of InvokeCallbackMethod here requires that // ResolveRejectFunction is 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. InvokeCallbackMethod( mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(), MaybeMove(aValue), std::move(mCompletionPromise)); // 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. mResolveRejectFunction.reset(); } private: Maybe mResolveRejectFunction; // Only accessed and deleted on dispatch // thread. RefPtr mCompletionPromise; }; public: void ThenInternal(already_AddRefed aThenValue, const char* aCallSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); RefPtr thenValue = aThenValue; MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT( !IsExclusive || !mHaveRequest, "Using an exclusive promise in a non-exclusive fashion"); mHaveRequest = true; PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", aCallSite, this, thenValue.get(), (int)IsPending()); if (!IsPending()) { thenValue->Dispatch(this); } else { mThenValues.AppendElement(thenValue.forget()); } } protected: /* * A command object to store all information needed to make a request to * the promise. This allows us to delay the request until further use is * known (whether it is ->Then() again for more promise chaining or ->Track() * to terminate chaining and issue the request). * * This allows a unified syntax for promise chaining and disconnection * and feels more like its JS counterpart. */ template class ThenCommand { // Allow Promise1::ThenCommand to access the private constructor, // Promise2::ThenCommand(ThenCommand&&). template friend class MozPromise; using PromiseType = typename ThenValueType::PromiseType; using Private = typename PromiseType::Private; ThenCommand(const char* aCallSite, already_AddRefed aThenValue, MozPromise* aReceiver) : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} ThenCommand(ThenCommand&& aOther) = default; public: ~ThenCommand() { // Issue the request now if the return value of Then() is not used. if (mThenValue) { mReceiver->ThenInternal(mThenValue.forget(), mCallSite); } } // Allow RefPtr p = somePromise->Then(); // p->Then(thread1, ...); // p->Then(thread2, ...); operator RefPtr() { static_assert( ThenValueType::SupportChaining::value, "The resolve/reject callback needs to return a RefPtr " "in order to do promise chaining."); // mCompletionPromise must be created before ThenInternal() to avoid race. RefPtr p = new Private("", true /* aIsCompletionPromise */); mThenValue->mCompletionPromise = p; // Note ThenInternal() might nullify mCompletionPromise before return. // So we need to return p instead of mCompletionPromise. mReceiver->ThenInternal(mThenValue.forget(), mCallSite); return p; } template auto Then(Ts&&... aArgs) -> decltype(DeclVal().Then(std::forward(aArgs)...)) { return static_cast>(*this)->Then( std::forward(aArgs)...); } void Track(MozPromiseRequestHolder& aRequestHolder) { aRequestHolder.Track(do_AddRef(mThenValue)); mReceiver->ThenInternal(mThenValue.forget(), mCallSite); } // Allow calling ->Then() again for more promise chaining or ->Track() to // end chaining and track the request for future disconnection. ThenCommand* operator->() { return this; } private: const char* mCallSite; RefPtr mThenValue; RefPtr mReceiver; }; public: template , typename ReturnType = ThenCommand> ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, ThisType* aThisVal, Methods... aMethods) { RefPtr thenValue = new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); return ReturnType(aCallSite, thenValue.forget(), this); } template , typename ReturnType = ThenCommand> ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, Functions&&... aFunctions) { RefPtr thenValue = new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); return ReturnType(aCallSite, thenValue.forget(), this); } void ChainTo(already_AddRefed aChainedPromise, const char* aCallSite) { MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT( !IsExclusive || !mHaveRequest, "Using an exclusive promise in a non-exclusive fashion"); 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() override { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); for (auto&& then : mThenValues) { then->AssertIsDead(); } for (auto&& chained : mChainedPromises) { chained->AssertIsDead(); } } protected: bool IsPending() const { return mValue.IsNothing(); } ResolveOrRejectValue& Value() { // 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 (auto&& thenValue : mThenValues) { thenValue->Dispatch(this); } mThenValues.Clear(); for (auto&& chainedPromise : mChainedPromises) { ForwardTo(chainedPromise); } mChainedPromises.Clear(); } void ForwardTo(Private* aOther) { MOZ_ASSERT(!IsPending()); if (mValue.IsResolve()) { aOther->Resolve(MaybeMove(mValue.ResolveValue()), ""); } else { aOther->Reject(MaybeMove(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 // Try shows we never have more than 3 elements when IsExclusive is false. // So '3' is a good value to avoid heap allocation in most cases. AutoTArray, IsExclusive ? 1 : 3> 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); MutexAutoLock lock(mMutex); PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aResolveSite, this, mCreationSite); return; } mValue.SetResolve(std::forward(aResolveValue)); DispatchAll(); } template void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aRejectSite, this, mCreationSite); return; } mValue.SetReject(std::forward(aRejectValue)); DispatchAll(); } template void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aSite, this, mCreationSite); return; } mValue = std::forward(aValue); DispatchAll(); } }; // A generic promise type that does the trick for simple use cases. typedef MozPromise GenericPromise; // A generic, non-exclusive promise type that does the trick for simple use // cases. typedef MozPromise GenericNonExclusivePromise; /* * 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) {} MozPromiseHolder(MozPromiseHolder&& aOther) : mMonitor(nullptr), mPromise(aOther.mPromise.forget()) {} // 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(); } return mPromise.forget(); } template void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { static_assert(IsConvertible::value, "Resolve() argument must be implicitly convertible to " "MozPromise's ResolveValueT"); if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } MOZ_ASSERT(mPromise); mPromise->Resolve(std::forward(aResolveValue), aMethodName); mPromise = nullptr; } template void ResolveIfExists(ResolveValueType_&& aResolveValue, const char* aMethodName) { if (!IsEmpty()) { Resolve(std::forward(aResolveValue), aMethodName); } } template void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { static_assert(IsConvertible::value, "Reject() argument must be implicitly convertible to " "MozPromise's RejectValueT"); if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } MOZ_ASSERT(mPromise); mPromise->Reject(std::forward(aRejectValue), aMethodName); mPromise = nullptr; } template void RejectIfExists(RejectValueType_&& aRejectValue, const char* aMethodName) { if (!IsEmpty()) { Reject(std::forward(aRejectValue), aMethodName); } } template void ResolveOrReject(ResolveOrRejectValueType_&& aValue, const char* aMethodName) { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } MOZ_ASSERT(mPromise); mPromise->ResolveOrReject(std::forward(aValue), aMethodName); mPromise = nullptr; } template void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, const char* aMethodName) { if (!IsEmpty()) { ResolveOrReject(std::forward(aValue), 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 Track(already_AddRefed 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 function // (a method and object, or a function object like a lambda) 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 function 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 { // 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: template MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) : mMethod(aMethod), mThisVal(aThisVal), mArgs(std::forward(aArgs)...) { static_assert(sizeof...(Storages) == sizeof...(Args), "Storages and Args should have equal sizes"); } RefPtr Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } private: MethodType mMethod; RefPtr mThisVal; RunnableMethodArguments mArgs; }; template class ProxyRunnable : public CancelableRunnable { public: ProxyRunnable( typename PromiseType::Private* aProxyPromise, MethodCall* aMethodCall) : CancelableRunnable("detail::ProxyRunnable"), mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {} NS_IMETHOD Run() override { RefPtr p = mMethodCall->Invoke(); mMethodCall = nullptr; p->ChainTo(mProxyPromise.forget(), ""); return NS_OK; } nsresult Cancel() override { return Run(); } private: RefPtr mProxyPromise; nsAutoPtr> mMethodCall; }; template static RefPtr InvokeAsyncImpl( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { MOZ_ASSERT(aTarget); typedef RefPtr (ThisType::*MethodType)(ArgTypes...); typedef detail::MethodCall MethodCallType; typedef detail::ProxyRunnable ProxyRunnableType; MethodCallType* methodCall = new MethodCallType( aMethod, aThisVal, std::forward(aArgs)...); RefPtr p = new (typename PromiseType::Private)(aCallerName); RefPtr r = new ProxyRunnableType(p, methodCall); aTarget->Dispatch(r.forget()); return p.forget(); } 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 // InvokeAsync with explicitly-specified storages. // See ParameterStorage in nsThreadUtils.h for help. template ::Type = 0> static RefPtr InvokeAsync( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( sizeof...(Storages) == sizeof...(ArgTypes), "Provided Storages and method's ArgTypes should have equal sizes"); static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes), "Provided Storages and ActualArgTypes should have equal sizes"); return detail::InvokeAsyncImpl( aTarget, aThisVal, aCallerName, aMethod, std::forward(aArgs)...); } // InvokeAsync with no explicitly-specified storages, will copy arguments and // then move them out of the runnable into the target method parameters. template ::Type = 0> static RefPtr InvokeAsync( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( !detail::Any( IsPointer::Type>::value...), "Cannot pass pointer types through InvokeAsync, Storages must be " "provided"); static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes), "Method's ArgTypes and ActualArgTypes should have equal sizes"); return detail::InvokeAsyncImpl< StoreCopyPassByRRef::Type>...>( aTarget, aThisVal, aCallerName, aMethod, std::forward(aArgs)...); } namespace detail { template class ProxyFunctionRunnable : public CancelableRunnable { typedef typename Decay::Type FunctionStorage; public: template ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, F&& aFunction) : CancelableRunnable("detail::ProxyFunctionRunnable"), mProxyPromise(aProxyPromise), mFunction(new FunctionStorage(std::forward(aFunction))) {} NS_IMETHOD Run() override { RefPtr p = (*mFunction)(); mFunction = nullptr; p->ChainTo(mProxyPromise.forget(), ""); return NS_OK; } nsresult Cancel() override { return Run(); } private: RefPtr mProxyPromise; UniquePtr mFunction; }; // Note: The following struct and function are not for public consumption (yet?) // as we would prefer all calls to pass on-the-spot lambdas (or at least moved // function objects). They could be moved outside of detail if really needed. // We prefer getting function objects by non-lvalue-ref (to avoid copying them // and their captures). This struct is a tag that allows the use of objects // through lvalue-refs where necessary. struct AllowInvokeAsyncFunctionLVRef {}; // Invoke a function object (e.g., lambda or std/mozilla::function) // asynchronously; note that the object will be copied if provided by // lvalue-ref. Return a promise that the function should eventually resolve or // reject. template static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, AllowInvokeAsyncFunctionLVRef, Function&& aFunction) -> decltype(aFunction()) { static_assert( IsRefcountedSmartPointer::value && IsMozPromise< typename RemoveSmartPointer::Type>::value, "Function object must return RefPtr"); MOZ_ASSERT(aTarget); typedef typename RemoveSmartPointer::Type PromiseType; typedef detail::ProxyFunctionRunnable ProxyRunnableType; auto p = MakeRefPtr(aCallerName); auto r = MakeRefPtr(p, std::forward(aFunction)); aTarget->Dispatch(r.forget()); return p.forget(); } } // namespace detail // Invoke a function object (e.g., lambda) asynchronously. // Return a promise that the function should eventually resolve or reject. template static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, Function&& aFunction) -> decltype(aFunction()) { static_assert(!IsLvalueReference::value, "Function object must not be passed by lvalue-ref (to avoid " "unplanned copies); Consider move()ing the object."); return detail::InvokeAsync(aTarget, aCallerName, detail::AllowInvokeAsyncFunctionLVRef(), std::forward(aFunction)); } # undef PROMISE_LOG # undef PROMISE_ASSERT # undef PROMISE_DEBUG } // namespace mozilla #endif