diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /xpcom/threads/MozPromise.h | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpcom/threads/MozPromise.h')
-rw-r--r-- | xpcom/threads/MozPromise.h | 1757 |
1 files changed, 1757 insertions, 0 deletions
diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h new file mode 100644 index 0000000000..f17e085c2f --- /dev/null +++ b/xpcom/threads/MozPromise.h @@ -0,0 +1,1757 @@ +/* -*- 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 <type_traits> +# include <utility> + +# include "mozilla/ErrorNames.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/Monitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RefPtr.h" +# include "mozilla/UniquePtr.h" +# include "mozilla/Variant.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISerialEventTarget.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +# ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/GeckoResultUtils.h" +# endif + +# 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 + +# if DEBUG +# include "nsPrintfCString.h" +# endif + +namespace mozilla { + +namespace dom { +class Promise; +} + +extern LazyLogModule gMozPromiseLog; + +# define PROMISE_LOG(x, ...) \ + MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) + +namespace detail { +template <typename F> +struct MethodTraitsHelper : MethodTraitsHelper<decltype(&F::operator())> {}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...)> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename ThisType, typename Ret, typename... ArgTypes> +struct MethodTraitsHelper<Ret (ThisType::*)(ArgTypes...) const volatile> { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template <typename T> +struct MethodTrait : MethodTraitsHelper<std::remove_reference_t<T>> {}; + +} // namespace detail + +template <typename MethodType> +using TakesArgument = + std::integral_constant<bool, detail::MethodTrait<MethodType>::ArgSize != 0>; + +template <typename MethodType, typename TargetType> +using ReturnTypeIs = + std::is_convertible<typename detail::MethodTrait<MethodType>::ReturnType, + TargetType>; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +class MozPromise; + +template <typename Return> +struct IsMozPromise : std::false_type {}; + +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +struct IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>> + : std::true_type {}; + +/* + * 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() = default; +}; + +class MozPromiseBase : public MozPromiseRefcountable { + public: + virtual void AssertIsDead() = 0; +}; + +template <typename T> +class MozPromiseHolder; +template <typename T> +class MozPromiseRequestHolder; +template <typename ResolveValueT, typename RejectValueT, bool IsExclusive> +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 <typename T, + typename R = std::conditional_t<IsExclusive, T&&, const T&>> + static R MaybeMove(T& aX) { + return static_cast<R>(aX); + } + + public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + class ResolveOrRejectValue { + public: + template <typename ResolveValueType_> + void SetResolve(ResolveValueType_&& aResolveValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<ResolveIndex>{}, + std::forward<ResolveValueType_>(aResolveValue)); + } + + template <typename RejectValueType_> + void SetReject(RejectValueType_&& aRejectValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex<RejectIndex>{}, + std::forward<RejectValueType_>(aRejectValue)); + } + + template <typename ResolveValueType_> + static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { + ResolveOrRejectValue val; + val.SetResolve(std::forward<ResolveValueType_>(aResolveValue)); + return val; + } + + template <typename RejectValueType_> + static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { + ResolveOrRejectValue val; + val.SetReject(std::forward<RejectValueType_>(aRejectValue)); + return val; + } + + bool IsResolve() const { return mValue.template is<ResolveIndex>(); } + bool IsReject() const { return mValue.template is<RejectIndex>(); } + bool IsNothing() const { return mValue.template is<NothingIndex>(); } + + const ResolveValueType& ResolveValue() const { + return mValue.template as<ResolveIndex>(); + } + ResolveValueType& ResolveValue() { + return mValue.template as<ResolveIndex>(); + } + const RejectValueType& RejectValue() const { + return mValue.template as<RejectIndex>(); + } + RejectValueType& RejectValue() { return mValue.template as<RejectIndex>(); } + + private: + enum { NothingIndex, ResolveIndex, RejectIndex }; + using Storage = Variant<Nothing, ResolveValueType, RejectValueType>; + Storage mValue = Storage(VariantIndex<NothingIndex>{}); + }; + + 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 <typename ResolveValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolve( + ResolveValueType_&& aResolveValue, const char* aResolveSite) { + static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aResolveSite); + p->Resolve(std::forward<ResolveValueType_>(aResolveValue), aResolveSite); + return p; + } + + template <typename RejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndReject( + RejectValueType_&& aRejectValue, const char* aRejectSite) { + static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + RefPtr<typename MozPromise::Private> p = + new MozPromise::Private(aRejectSite); + p->Reject(std::forward<RejectValueType_>(aRejectValue), aRejectSite); + return p; + } + + template <typename ResolveOrRejectValueType_> + [[nodiscard]] static RefPtr<MozPromise> CreateAndResolveOrReject( + ResolveOrRejectValueType_&& aValue, const char* aSite) { + RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite); + p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite); + return p; + } + + typedef MozPromise<CopyableTArray<ResolveValueType>, RejectValueType, + IsExclusive> + AllPromiseType; + + typedef MozPromise<CopyableTArray<ResolveOrRejectValue>, bool, IsExclusive> + AllSettledPromiseType; + + 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); + } + + template <typename ResolveValueType_> + void Resolve(size_t aIndex, ResolveValueType_&& aResolveValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mResolveValues[aIndex].emplace( + std::forward<ResolveValueType_>(aResolveValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveValueType> 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(); + } + } + + template <typename RejectValueType_> + void Reject(RejectValueType_&& aRejectValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + + AllPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveValueType>> mResolveValues; + RefPtr<typename AllPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + // Trying to pass ResolveOrRejectValue by value fails static analysis checks, + // so we need to use either a const& or an rvalue reference, depending on + // whether IsExclusive is true or not. + typedef std::conditional_t<IsExclusive, ResolveOrRejectValue&&, + const ResolveOrRejectValue&> + ResolveOrRejectValueParam; + + typedef std::conditional_t<IsExclusive, ResolveValueType&&, + const ResolveValueType&> + ResolveValueTypeParam; + + typedef std::conditional_t<IsExclusive, RejectValueType&&, + const RejectValueType&> + RejectValueTypeParam; + + class AllSettledPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllSettledPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllSettledPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mValues.SetLength(aDependentPromises); + } + + void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mValues[aIndex].emplace(MaybeMove(aValue)); + if (--mOutstandingPromises == 0) { + nsTArray<ResolveOrRejectValue> values; + values.SetCapacity(mValues.Length()); + for (auto&& value : mValues) { + values.AppendElement(std::move(value.ref())); + } + + mPromise->Resolve(std::move(values), __func__); + mPromise = nullptr; + mValues.Clear(); + } + } + + AllSettledPromiseType* Promise() { return mPromise; } + + private: + nsTArray<Maybe<ResolveOrRejectValue>> mValues; + RefPtr<typename AllSettledPromiseType::Private> mPromise; + size_t mOutstandingPromises; + }; + + public: + [[nodiscard]] static RefPtr<AllPromiseType> All( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllPromiseType::CreateAndResolve( + CopyableTArray<ResolveValueType>(), __func__); + } + + RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length()); + RefPtr<AllPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then( + aProcessingTarget, __func__, + [holder, i](ResolveValueTypeParam aResolveValue) -> void { + holder->Resolve(i, MaybeMove(aResolveValue)); + }, + [holder](RejectValueTypeParam aRejectValue) -> void { + holder->Reject(MaybeMove(aRejectValue)); + }); + } + return promise; + } + + [[nodiscard]] static RefPtr<AllSettledPromiseType> AllSettled( + nsISerialEventTarget* aProcessingTarget, + nsTArray<RefPtr<MozPromise>>& aPromises) { + if (aPromises.Length() == 0) { + return AllSettledPromiseType::CreateAndResolve( + CopyableTArray<ResolveOrRejectValue>(), __func__); + } + + RefPtr<AllSettledPromiseHolder> holder = + new AllSettledPromiseHolder(aPromises.Length()); + RefPtr<AllSettledPromiseType> promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then(aProcessingTarget, __func__, + [holder, i](ResolveOrRejectValueParam aValue) -> void { + holder->Settle(i, MaybeMove(aValue)); + }); + } + return promise; + } + + class Request : public MozPromiseRefcountable { + public: + virtual void Disconnect() = 0; + + protected: + Request() : mComplete(false), mDisconnected(false) {} + virtual ~Request() = default; + + 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 final + : public PrioritizableCancelableRunnable { + public: + ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) + : PrioritizableCancelableRunnable( + aPromise->mPriority, + "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<ThenValueBase> mThenValue; + RefPtr<MozPromise> 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 { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (MOZ_UNLIKELY(!Request::mDisconnected)) { + MOZ_CRASH_UNSAFE_PRINTF( + "MozPromise::ThenValue created from '%s' destroyed without being " + "either disconnected, resolved, or rejected (dispatchRv: %s)", + mCallSite, + mDispatchRv ? GetStaticErrorName(*mDispatchRv) + : "not dispatched"); + } +# endif + } + } + + void Dispatch(MozPromise* aPromise) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + + nsCOMPtr<nsIRunnable> r = new ResolveOrRejectRunnable(this, aPromise); + PROMISE_LOG( + "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] " + "%s dispatch", + aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, + r.get(), aPromise, this, + aPromise->mUseSynchronousTaskDispatch ? "synchronous" + : aPromise->mUseDirectTaskDispatch ? "directtask" + : "normal"); + + if (aPromise->mUseSynchronousTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]", + this); + r->Run(); + return; + } + + if (aPromise->mUseDirectTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG( + "ThenValue::Dispatch dispatch task via direct task queue [this=%p]", + this); + nsCOMPtr<nsIDirectTaskDispatcher> dispatcher = + do_QueryInterface(mResponseTarget); + if (dispatcher) { + SetDispatchRv(dispatcher->DispatchDirectTask(r.forget())); + return; + } + NS_WARNING( + nsPrintfCString( + "Direct Task dispatching not available for thread \"%s\"", + PR_GetThreadName(PR_GetCurrentThread())) + .get()); + MOZ_DIAGNOSTIC_ASSERT( + false, + "mResponseTarget must implement nsIDirectTaskDispatcher for direct " + "task dispatching"); + } + + // 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. :-( + // We do record whether or not it succeeds so that if the ThenValueBase is + // then destroyed and it was not disconnected, we can include that + // information in the assertion message. + SetDispatchRv(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); + } + + void SetDispatchRv(nsresult aRv) { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mDispatchRv = Some(aRv); +# endif + } + + nsCOMPtr<nsISerialEventTarget> + 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 +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + Maybe<nsresult> mDispatchRv; +# endif + }; + + /* + * 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 std::enable_if_t<TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(std::forward<ValueType>(aValue)); + } + + template <typename ThisType, typename MethodType, typename ValueType> + static std::enable_if_t<!TakesArgument<MethodType>::value, + typename detail::MethodTrait<MethodType>::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(); + } + + // Called when promise chaining is supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<SupportChaining, void> InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + auto p = InvokeMethod(aThisVal, aMethod, std::forward<ValueType>(aValue)); + if (aCompletionPromise) { + p->ChainTo(aCompletionPromise.forget(), "<chained completion promise>"); + } + } + + // Called when promise chaining is not supported. + template <bool SupportChaining, typename ThisType, typename MethodType, + typename ValueType, typename CompletionPromiseType> + static std::enable_if_t<!SupportChaining, void> 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<ValueType>(aValue)); + } + + template <typename> + class ThenCommand; + + template <typename...> + class ThenValue; + + template <typename ThisType, typename ResolveMethodType, + typename RejectMethodType> + class ThenValue<ThisType*, ResolveMethodType, RejectMethodType> + : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveMethodType>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + 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<SupportChaining::value>( + mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), + std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + 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<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ThisType, typename ResolveRejectMethodType> + class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer<typename detail::MethodTrait< + ResolveRejectMethodType>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + 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<SupportChaining::value>( + 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<ThisType> + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveRejectMethodType mResolveRejectMethod; + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + // NB: We could use std::function here instead of a template if it were + // supported. :-( + template <typename ResolveFunction, typename RejectFunction> + class ThenValue<ResolveFunction, RejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveFunction>::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait<RejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value && + std::is_same_v<R1, R2>>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + 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<SupportChaining::value>( + mResolveFunction.ptr(), &ResolveFunction::operator(), + MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod<SupportChaining::value>( + 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<ResolveFunction> + mResolveFunction; // Only accessed and deleted on dispatch thread. + Maybe<RejectFunction> + mRejectFunction; // Only accessed and deleted on dispatch thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + template <typename ResolveRejectFunction> + class ThenValue<ResolveRejectFunction> : public ThenValueBase { + friend class ThenCommand<ThenValue>; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait<ResolveRejectFunction>::ReturnType>::Type; + using SupportChaining = + std::integral_constant<bool, IsMozPromise<R1>::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t<SupportChaining::value, R1, MozPromise>; + + 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<SupportChaining::value>( + 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<ResolveRejectFunction> + mResolveRejectFunction; // Only accessed and deleted on dispatch + // thread. + RefPtr<typename PromiseType::Private> mCompletionPromise; + }; + + public: + void ThenInternal(already_AddRefed<ThenValueBase> aThenValue, + const char* aCallSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + RefPtr<ThenValueBase> 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 <typename ThenValueType> + class ThenCommand { + // Allow Promise1::ThenCommand to access the private constructor, + // Promise2::ThenCommand(ThenCommand&&). + template <typename, typename, bool> + friend class MozPromise; + + using PromiseType = typename ThenValueType::PromiseType; + using Private = typename PromiseType::Private; + + ThenCommand(const char* aCallSite, + already_AddRefed<ThenValueType> 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<MozPromise> p = somePromise->Then(); + // p->Then(thread1, ...); + // p->Then(thread2, ...); + operator RefPtr<PromiseType>() { + static_assert( + ThenValueType::SupportChaining::value, + "The resolve/reject callback needs to return a RefPtr<MozPromise> " + "in order to do promise chaining."); + + // mCompletionPromise must be created before ThenInternal() to avoid race. + RefPtr<Private> p = + new Private("<completion promise>", 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 <typename... Ts> + auto Then(Ts&&... aArgs) -> decltype(std::declval<PromiseType>().Then( + std::forward<Ts>(aArgs)...)) { + return static_cast<RefPtr<PromiseType>>(*this)->Then( + std::forward<Ts>(aArgs)...); + } + + void Track(MozPromiseRequestHolder<MozPromise>& 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<ThenValueType> mThenValue; + RefPtr<MozPromise> mReceiver; + }; + + public: + template <typename ThisType, typename... Methods, + typename ThenValueType = ThenValue<ThisType*, Methods...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ThisType* aThisVal, Methods... aMethods) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + template <typename... Functions, + typename ThenValueType = ThenValue<Functions...>, + typename ReturnType = ThenCommand<ThenValueType>> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + Functions&&... aFunctions) { + RefPtr<ThenValueType> thenValue = + new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + void ChainTo(already_AddRefed<Private> aChainedPromise, + const char* aCallSite) { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + RefPtr<Private> chainedPromise = aChainedPromise; + PROMISE_LOG( + "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", + aCallSite, this, chainedPromise.get(), (int)IsPending()); + + // We want to use the same type of dispatching method with the chained + // promises. + + // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken + // at compilation time to ensure we're not triggering the static_assert in + // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures + // that. + if (mUseDirectTaskDispatch) { + chainedPromise->UseDirectTaskDispatch(aCallSite); + } else if constexpr (IsExclusive) { + if (mUseSynchronousTaskDispatch) { + chainedPromise->UseSynchronousTaskDispatch(aCallSite); + } + } else { + chainedPromise->SetTaskPriority(mPriority, aCallSite); + } + + if (!IsPending()) { + ForwardTo(chainedPromise); + } else { + mChainedPromises.AppendElement(chainedPromise); + } + } + +# ifdef MOZ_WIDGET_ANDROID + // Creates a C++ MozPromise from its Java counterpart, GeckoResult. + [[nodiscard]] static RefPtr<MozPromise> FromGeckoResult( + java::GeckoResult::Param aGeckoResult) { + using jni::GeckoResultCallback; + RefPtr<Private> p = new Private("GeckoResult Glue", false); + auto resolve = GeckoResultCallback::CreateAndAttach<ResolveValueType>( + [p](ResolveValueType&& aArg) { + p->Resolve(MaybeMove(aArg), __func__); + }); + auto reject = GeckoResultCallback::CreateAndAttach<RejectValueType>( + [p](RejectValueType&& aArg) { p->Reject(MaybeMove(aArg), __func__); }); + aGeckoResult->NativeThen(resolve, reject); + return p; + } +# endif + + // 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(); + } + } + + bool IsResolved() const { return mValue.IsResolve(); } + + 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()), "<chained promise>"); + } else { + aOther->Reject(MaybeMove(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 MOZ_UNANNOTATED; + ResolveOrRejectValue mValue; + bool mUseSynchronousTaskDispatch = false; + bool mUseDirectTaskDispatch = false; + uint32_t mPriority = nsIRunnablePriority::PRIORITY_NORMAL; +# 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<RefPtr<ThenValueBase>, IsExclusive ? 1 : 3> 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); + 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<ResolveValueT_>(aResolveValue)); + DispatchAll(); + } + + template <typename RejectValueT_> + 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<RejectValueT_>(aRejectValue)); + DispatchAll(); + } + + template <typename ResolveOrRejectValue_> + 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<ResolveOrRejectValue_>(aValue); + DispatchAll(); + } + + // If the caller and target are both on the same thread, run the the resolve + // or reject callback synchronously. Otherwise, the task will be dispatched + // via the target Dispatch method. + void UseSynchronousTaskDispatch(const char* aSite) { + static_assert( + IsExclusive, + "Synchronous dispatch can only be used with exclusive promises"); + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", + aSite, this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + mUseSynchronousTaskDispatch = true; + } + + // If the caller and target are both on the same thread, run the + // resolve/reject callback off the direct task queue instead. This avoids a + // full trip to the back of the event queue for each additional asynchronous + // step when using MozPromise, and is similar (but not identical to) the + // microtask semantics of JS promises. + void UseDirectTaskDispatch(const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, + this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + mUseDirectTaskDispatch = true; + } + + // If the resolve/reject will be handled on a thread supporting priorities, + // one may want to tweak the priority of the task by passing a + // nsIRunnablePriority::PRIORITY_* to SetTaskPriority. + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s TaskPriority MozPromise (%p created at %s)", aSite, this, + mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + MOZ_ASSERT(!mUseDirectTaskDispatch, + "Promise already set for direct dispatch"); + mPriority = aPriority; + } +}; + +// A generic promise type that does the trick for simple use cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> GenericPromise; + +// A generic, non-exclusive promise type that does the trick for simple use +// cases. +typedef MozPromise<bool, nsresult, /* IsExclusive = */ false> + 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 <typename PromiseType, typename ImplType> +class MozPromiseHolderBase { + public: + MozPromiseHolderBase() = default; + + MozPromiseHolderBase(MozPromiseHolderBase&& aOther) = default; + MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) = default; + + ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } + + already_AddRefed<PromiseType> Ensure(const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + if (!mPromise) { + mPromise = new (typename PromiseType::Private)(aMethodName); + } + RefPtr<PromiseType> p = mPromise.get(); + return p.forget(); + } + + bool IsEmpty() const { + static_cast<const ImplType*>(this)->Check(); + return !mPromise; + } + + already_AddRefed<typename PromiseType::Private> Steal() { + static_cast<ImplType*>(this)->Check(); + return mPromise.forget(); + } + + template <typename ResolveValueType_> + void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { + static_assert(std::is_convertible_v<ResolveValueType_, + typename PromiseType::ResolveValueType>, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Resolve(std::forward<ResolveValueType_>(aResolveValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveValueType_> + void ResolveIfExists(ResolveValueType_&& aResolveValue, + const char* aMethodName) { + if (!IsEmpty()) { + Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName); + } + } + + template <typename RejectValueType_> + void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { + static_assert(std::is_convertible_v<RejectValueType_, + typename PromiseType::RejectValueType>, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + mPromise = nullptr; + } + + template <typename RejectValueType_> + void RejectIfExists(RejectValueType_&& aRejectValue, + const char* aMethodName) { + if (!IsEmpty()) { + Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName); + } + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrReject(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + static_cast<ImplType*>(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + mPromise = nullptr; + } + + template <typename ResolveOrRejectValueType_> + void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + if (!IsEmpty()) { + ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), + aMethodName); + } + } + + void UseSynchronousTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseSynchronousTaskDispatch(aSite); + } + + void UseDirectTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseDirectTaskDispatch(aSite); + } + + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->SetTaskPriority(aPriority, aSite); + } + + private: + RefPtr<typename PromiseType::Private> mPromise; +}; + +template <typename PromiseType> +class MozPromiseHolder + : public MozPromiseHolderBase<PromiseType, MozPromiseHolder<PromiseType>> { + public: + using MozPromiseHolderBase< + PromiseType, MozPromiseHolder<PromiseType>>::MozPromiseHolderBase; + static constexpr void Check(){}; +}; + +template <typename PromiseType> +class MozMonitoredPromiseHolder + : public MozPromiseHolderBase<PromiseType, + MozMonitoredPromiseHolder<PromiseType>> { + public: + // Provide a Monitor that should always be held when accessing this instance. + explicit MozMonitoredPromiseHolder(Monitor* const aMonitor) + : mMonitor(aMonitor) { + MOZ_ASSERT(aMonitor); + } + + MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete; + MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) = + delete; + + void Check() const { mMonitor->AssertCurrentThreadOwns(); } + + private: + Monitor* const mMonitor; +}; + +/* + * 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() = default; + ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } + + void Track(already_AddRefed<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 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: + MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase) + MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase) +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class MethodCall : public MethodCallBase { + public: + template <typename... Args> + MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) + : mMethod(aMethod), + mThisVal(aThisVal), + mArgs(std::forward<Args>(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + RefPtr<PromiseType> Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } + + private: + MethodType mMethod; + RefPtr<ThisType> mThisVal; + RunnableMethodArguments<Storages...> mArgs; +}; + +template <typename PromiseType, typename MethodType, typename ThisType, + typename... Storages> +class ProxyRunnable : public CancelableRunnable { + public: + ProxyRunnable( + typename PromiseType::Private* aProxyPromise, + MethodCall<PromiseType, MethodType, ThisType, Storages...>* aMethodCall) + : CancelableRunnable("detail::ProxyRunnable"), + mProxyPromise(aProxyPromise), + mMethodCall(aMethodCall) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = mMethodCall->Invoke(); + mMethodCall = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<MethodCall<PromiseType, MethodType, ThisType, Storages...>> + mMethodCall; +}; + +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes> +static RefPtr<PromiseType> InvokeAsyncImpl( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + MOZ_ASSERT(aTarget); + + typedef RefPtr<PromiseType> (ThisType::*MethodType)(ArgTypes...); + typedef detail::MethodCall<PromiseType, MethodType, ThisType, Storages...> + MethodCallType; + typedef detail::ProxyRunnable<PromiseType, MethodType, ThisType, Storages...> + ProxyRunnableType; + + MethodCallType* methodCall = new MethodCallType( + aMethod, aThisVal, std::forward<ActualArgTypes>(aArgs)...); + RefPtr<typename PromiseType::Private> p = + new (typename PromiseType::Private)(aCallerName); + RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall); + aTarget->Dispatch(r.forget()); + return p; +} + +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 + +// InvokeAsync with explicitly-specified storages. +// See ParameterStorage in nsThreadUtils.h for help. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) != 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (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<Storages...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +// InvokeAsync with no explicitly-specified storages, will copy arguments and +// then move them out of the runnable into the target method parameters. +template <typename... Storages, typename PromiseType, typename ThisType, + typename... ArgTypes, typename... ActualArgTypes, + std::enable_if_t<sizeof...(Storages) == 0, int> = 0> +static RefPtr<PromiseType> InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr<PromiseType> (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + !detail::Any( + std::is_pointer_v<std::remove_reference_t<ActualArgTypes>>...), + "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<std::decay_t<ActualArgTypes>>...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward<ActualArgTypes>(aArgs)...); +} + +namespace detail { + +template <typename Function, typename PromiseType> +class ProxyFunctionRunnable : public CancelableRunnable { + using FunctionStorage = std::decay_t<Function>; + + public: + template <typename F> + ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, + F&& aFunction) + : CancelableRunnable("detail::ProxyFunctionRunnable"), + mProxyPromise(aProxyPromise), + mFunction(new FunctionStorage(std::forward<F>(aFunction))) {} + + NS_IMETHOD Run() override { + RefPtr<PromiseType> p = (*mFunction)(); + mFunction = nullptr; + p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>"); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr<typename PromiseType::Private> mProxyPromise; + UniquePtr<FunctionStorage> 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 <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + AllowInvokeAsyncFunctionLVRef, Function&& aFunction) + -> decltype(aFunction()) { + static_assert( + IsRefcountedSmartPointer<decltype(aFunction())>::value && + IsMozPromise< + typename RemoveSmartPointer<decltype(aFunction())>::Type>::value, + "Function object must return RefPtr<MozPromise>"); + MOZ_ASSERT(aTarget); + typedef typename RemoveSmartPointer<decltype(aFunction())>::Type PromiseType; + typedef detail::ProxyFunctionRunnable<Function, PromiseType> + ProxyRunnableType; + + auto p = MakeRefPtr<typename PromiseType::Private>(aCallerName); + auto r = MakeRefPtr<ProxyRunnableType>(p, std::forward<Function>(aFunction)); + aTarget->Dispatch(r.forget()); + return p; +} + +} // namespace detail + +// Invoke a function object (e.g., lambda) asynchronously. +// Return a promise that the function should eventually resolve or reject. +template <typename Function> +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + Function&& aFunction) -> decltype(aFunction()) { + static_assert(!std::is_lvalue_reference_v<Function>, + "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<Function>(aFunction)); +} + +# undef PROMISE_LOG +# undef PROMISE_ASSERT +# undef PROMISE_DEBUG + +} // namespace mozilla + +#endif |