fune/xpcom/threads/MozPromise.h
Ray Kraesig e2ee2f11df Bug 1891541 - [2/2] enforce that MozPromise only accepts static strings r=xpcom-reviewers,media-playback-reviewers,padenot,emilio
All present uses of the call-site arguments to MozPromise's methods
supply static strings. However, this is nowhere enforced. Do so.

Additionally, since this is the third or fourth time the present author
alone has personally implemented such an enforcement mechanism, create a
helper class to simplify doing so.

No functional changes.

Differential Revision: https://phabricator.services.mozilla.com/D207462
2024-04-24 17:02:49 +00:00

1861 lines
68 KiB
C++

/* -*- 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/. */
#ifndef XPCOM_THREADS_MOZPROMISE_H_
#define XPCOM_THREADS_MOZPROMISE_H_
#include <type_traits>
#include <utility>
#include "mozilla/Attributes.h"
#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/StaticString.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 T>
using MethodReturnType = typename detail::MethodTrait<T>::ReturnType;
template <typename MethodType>
constexpr bool TakesAnyArguments =
detail::MethodTrait<MethodType>::ArgSize != 0;
template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
class MozPromise;
template <typename T>
constexpr bool IsMozPromise = false;
template <typename ResolveValueT, typename RejectValueT, bool IsExclusive>
constexpr bool
IsMozPromise<MozPromise<ResolveValueT, RejectValueT, IsExclusive>> = true;
/*
* 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:
using ResolveValueType = ResolveValueT;
using RejectValueType = RejectValueT;
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(StaticString 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.get(), 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, StaticString 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, StaticString 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, StaticString aSite) {
RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aSite);
p->ResolveOrReject(std::forward<ResolveOrRejectValueType_>(aValue), aSite);
return p;
}
using AllPromiseType = MozPromise<CopyableTArray<ResolveValueType>,
RejectValueType, IsExclusive>;
using AllSettledPromiseType =
MozPromise<CopyableTArray<ResolveOrRejectValue>, bool, IsExclusive>;
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.
using ResolveOrRejectValueParam =
std::conditional_t<IsExclusive, ResolveOrRejectValue&&,
const ResolveOrRejectValue&>;
using ResolveValueTypeParam =
std::conditional_t<IsExclusive, ResolveValueType&&,
const ResolveValueType&>;
using RejectValueTypeParam =
std::conditional_t<IsExclusive, RejectValueType&&,
const RejectValueType&>;
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, StaticString 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.get(),
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.get(), 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
StaticString 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<TakesAnyArguments<MethodType>,
MethodReturnType<MethodType>>
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<!TakesAnyArguments<MethodType>,
MethodReturnType<MethodType>>
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 = RemoveSmartPointer<MethodReturnType<ResolveMethodType>>;
using R2 = RemoveSmartPointer<MethodReturnType<RejectMethodType>>;
constexpr static bool SupportChaining =
IsMozPromise<R1> && 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, R1, MozPromise>;
public:
ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal,
ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
StaticString 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>(mThisVal.get(), mResolveMethod,
MaybeMove(aValue.ResolveValue()),
std::move(mCompletionPromise));
} else {
InvokeCallbackMethod<SupportChaining>(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 = RemoveSmartPointer<MethodReturnType<ResolveRejectMethodType>>;
constexpr static bool SupportChaining = IsMozPromise<R1>;
// Fall back to MozPromise when promise chaining is not supported to make
// code compile.
using PromiseType = std::conditional_t<SupportChaining, R1, MozPromise>;
public:
ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal,
ResolveRejectMethodType aResolveRejectMethod,
StaticString 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>(
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 = RemoveSmartPointer<MethodReturnType<ResolveFunction>>;
using R2 = RemoveSmartPointer<MethodReturnType<RejectFunction>>;
constexpr static bool SupportChaining =
IsMozPromise<R1> && 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, R1, MozPromise>;
public:
ThenValue(nsISerialEventTarget* aResponseTarget,
ResolveFunction&& aResolveFunction,
RejectFunction&& aRejectFunction, StaticString 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>(
mResolveFunction.ptr(), &ResolveFunction::operator(),
MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise));
} else {
InvokeCallbackMethod<SupportChaining>(
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 = RemoveSmartPointer<MethodReturnType<ResolveRejectFunction>>;
constexpr static bool SupportChaining = IsMozPromise<R1>;
// Fall back to MozPromise when promise chaining is not supported to make
// code compile.
using PromiseType = std::conditional_t<SupportChaining, R1, MozPromise>;
public:
ThenValue(nsISerialEventTarget* aResponseTarget,
ResolveRejectFunction&& aResolveRejectFunction,
StaticString 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>(
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;
};
template <typename ResolveFunction>
class MapValue final : public ThenValueBase {
friend class ThenCommand<MapValue>;
constexpr static const bool SupportChaining = true;
using ResolveValueT_ = std::invoke_result_t<ResolveFunction, ResolveValueT>;
using PromiseType = MozPromise<ResolveValueT_, RejectValueT, IsExclusive>;
public:
explicit MapValue(nsISerialEventTarget* aResponseTarget,
ResolveFunction&& f, StaticString aCallSite)
: ThenValueBase(aResponseTarget, aCallSite),
mResolveFunction(Some(std::forward<ResolveFunction>(f))) {}
protected:
void Disconnect() override {
ThenValueBase::Disconnect();
mResolveFunction.reset();
}
MozPromiseBase* CompletionPromise() const override {
return mCompletionPromise;
}
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
// Note that promise-chaining is always supported here; this function can
// only transform from MozPromise<A, B, k> to MozPromise<A2, B, k>.
auto value = MaybeMove(aValue);
typename PromiseType::ResolveOrRejectValue output;
if (value.IsResolve()) {
output.SetResolve((*mResolveFunction)(std::move(value.ResolveValue())));
} else {
output.SetReject(std::move(value.RejectValue()));
}
if (mCompletionPromise) {
mCompletionPromise->ResolveOrReject(std::move(output),
ThenValueBase::mCallSite);
}
}
private:
Maybe<ResolveFunction> mResolveFunction;
RefPtr<typename PromiseType::Private> mCompletionPromise;
};
template <typename RejectFunction>
class MapErrValue final : public ThenValueBase {
friend class ThenCommand<MapErrValue>;
constexpr static const bool SupportChaining = true;
using RejectValueT_ = std::invoke_result_t<RejectFunction, RejectValueT>;
using PromiseType = MozPromise<ResolveValueT, RejectValueT_, IsExclusive>;
public:
explicit MapErrValue(nsISerialEventTarget* aResponseTarget,
RejectFunction&& f, StaticString aCallSite)
: ThenValueBase(aResponseTarget, aCallSite),
mRejectFunction(Some(std::forward<RejectFunction>(f))) {}
protected:
void Disconnect() override {
ThenValueBase::Disconnect();
mRejectFunction.reset();
}
MozPromiseBase* CompletionPromise() const override {
return mCompletionPromise;
}
void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override {
// Note that promise-chaining is always supported here; this function can
// only transform from MozPromise<A, B, k> to MozPromise<A, B2, k>.
auto value = MaybeMove(aValue);
typename PromiseType::ResolveOrRejectValue output;
if (value.IsResolve()) {
output.SetResolve(std::move(value.ResolveValue()));
} else {
output.SetReject((*mRejectFunction)(std::move(value.RejectValue())));
}
if (mCompletionPromise) {
mCompletionPromise->ResolveOrReject(std::move(output),
ThenValueBase::mCallSite);
}
}
private:
Maybe<RejectFunction> mRejectFunction;
RefPtr<typename PromiseType::Private> mCompletionPromise;
};
public:
void ThenInternal(already_AddRefed<ThenValueBase> aThenValue,
StaticString 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.get(), 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.
*
* Note that a ThenCommand is always exclusive, even if its source or result
* promises are not. To attach multiple continuations, explicitly convert it
* to a promise first.
*/
template <typename ThenValueType>
class MOZ_TEMPORARY_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(StaticString aCallSite,
already_AddRefed<ThenValueType> aThenValue,
MozPromise* aReceiver)
: mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {}
ThenCommand(ThenCommand&& aOther) noexcept = 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,
"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)...);
}
template <typename... Ts>
auto Map(Ts&&... aArgs) -> decltype(std::declval<PromiseType>().Map(
std::forward<Ts>(aArgs)...)) {
return static_cast<RefPtr<PromiseType>>(*this)->Map(
std::forward<Ts>(aArgs)...);
}
template <typename... Ts>
auto MapErr(Ts&&... aArgs) -> decltype(std::declval<PromiseType>().MapErr(
std::forward<Ts>(aArgs)...)) {
return static_cast<RefPtr<PromiseType>>(*this)->MapErr(
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:
StaticString 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, StaticString 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, StaticString aCallSite,
Functions&&... aFunctions) {
RefPtr<ThenValueType> thenValue =
new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite);
return ReturnType(aCallSite, thenValue.forget(), this);
}
// Shorthand for a `Then` which simply forwards the reject-value, but performs
// some additional work with the resolve-value.
template <typename Function>
auto Map(nsISerialEventTarget* aResponseTarget, StaticString aCallSite,
Function&& function) {
RefPtr<MapValue<Function>> thenValue = new MapValue<Function>(
aResponseTarget, std::forward<Function>(function), aCallSite);
return ThenCommand<MapValue<Function>>(aCallSite, thenValue.forget(), this);
}
// Shorthand for a `Then` which simply forwards the resolve-value, but
// performs some additional work with the reject-value.
template <typename Function>
auto MapErr(nsISerialEventTarget* aResponseTarget, StaticString aCallSite,
Function&& function) {
RefPtr<MapErrValue<Function>> thenValue = new MapErrValue<Function>(
aResponseTarget, std::forward<Function>(function), aCallSite);
return ThenCommand<MapErrValue<Function>>(aCallSite, thenValue.forget(),
this);
}
void ChainTo(already_AddRefed<Private> aChainedPromise,
StaticString 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.get(), 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
};
StaticString 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(StaticString aCreationSite,
bool aIsCompletionPromise = false)
: MozPromise(aCreationSite, aIsCompletionPromise) {}
template <typename ResolveValueT_>
void Resolve(ResolveValueT_&& aResolveValue, StaticString aResolveSite) {
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
mMagic3 == sMagic && mMagic4 == &mMutex);
MutexAutoLock lock(mMutex);
PROMISE_LOG("%s resolving MozPromise (%p created at %s)",
aResolveSite.get(), this, mCreationSite.get());
if (!IsPending()) {
PROMISE_LOG(
"%s ignored already resolved or rejected MozPromise (%p created at "
"%s)",
aResolveSite.get(), this, mCreationSite.get());
return;
}
mValue.SetResolve(std::forward<ResolveValueT_>(aResolveValue));
DispatchAll();
}
template <typename RejectValueT_>
void Reject(RejectValueT_&& aRejectValue, StaticString aRejectSite) {
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
mMagic3 == sMagic && mMagic4 == &mMutex);
MutexAutoLock lock(mMutex);
PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite.get(),
this, mCreationSite.get());
if (!IsPending()) {
PROMISE_LOG(
"%s ignored already resolved or rejected MozPromise (%p created at "
"%s)",
aRejectSite.get(), this, mCreationSite.get());
return;
}
mValue.SetReject(std::forward<RejectValueT_>(aRejectValue));
DispatchAll();
}
template <typename ResolveOrRejectValue_>
void ResolveOrReject(ResolveOrRejectValue_&& aValue, StaticString aSite) {
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic &&
mMagic3 == sMagic && mMagic4 == &mMutex);
MutexAutoLock lock(mMutex);
PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)",
aSite.get(), this, mCreationSite.get());
if (!IsPending()) {
PROMISE_LOG(
"%s ignored already resolved or rejected MozPromise (%p created at "
"%s)",
aSite.get(), this, mCreationSite.get());
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.get());
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.get());
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.get());
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.
//
// Vaguely deprecated: prefer explicitly naming the resolve- and reject-type.
// Additionally, prefer `mozilla::Ok` as the resolve-type if the boolean's value
// is irrelevant.
using GenericPromise = MozPromise<bool, nsresult, /* IsExclusive = */ true>;
// A generic, non-exclusive promise type that does the trick for simple use
// cases.
//
// Vaguely deprecated, as above.
using GenericNonExclusivePromise =
MozPromise<bool, nsresult, /* IsExclusive = */ false>;
/*
* 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) noexcept = default;
MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) noexcept =
default;
~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); }
already_AddRefed<PromiseType> Ensure(StaticString 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, StaticString 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,
StaticString aMethodName) {
if (!IsEmpty()) {
Resolve(std::forward<ResolveValueType_>(aResolveValue), aMethodName);
}
}
template <typename RejectValueType_>
void Reject(RejectValueType_&& aRejectValue, StaticString 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,
StaticString aMethodName) {
if (!IsEmpty()) {
Reject(std::forward<RejectValueType_>(aRejectValue), aMethodName);
}
}
template <typename ResolveOrRejectValueType_>
void ResolveOrReject(ResolveOrRejectValueType_&& aValue,
StaticString 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,
StaticString 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());
RefPtr request = std::move(mRequest);
request->Disconnect();
}
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, StaticString 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, StaticString 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, StaticString 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;
};
template <typename T>
constexpr static bool IsRefPtrMozPromise = false;
template <typename T, typename U, bool B>
constexpr static bool IsRefPtrMozPromise<RefPtr<MozPromise<T, U, B>>> = true;
} // 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, StaticString 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.");
static_assert(detail::IsRefPtrMozPromise<decltype(aFunction())>,
"Function object must return RefPtr<MozPromise>");
MOZ_ASSERT(aTarget);
typedef RemoveSmartPointer<decltype(aFunction())> 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;
}
#undef PROMISE_LOG
#undef PROMISE_ASSERT
#undef PROMISE_DEBUG
} // namespace mozilla
#endif