--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -129,20 +129,26 @@ using ReturnTypeIs =
class MozPromiseRefcountable
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)
protected:
virtual ~MozPromiseRefcountable() {}
};
+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 MozPromiseRefcountable
+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 = typename Conditional<IsExclusive, T&&, const T&>::Type>
static R MaybeMove(T& aX)
@@ -422,18 +428,18 @@ protected:
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
// We want to assert that this ThenValues is dead - that is to say, that
// there are no consumers waiting for the result. In the case of a normal
// ThenValue, we check that it has been disconnected, which is the way
// that the consumer signals that it no longer wishes to hear about the
// result. If this ThenValue has a completion promise (which is mutually
// exclusive with being disconnectable), we recursively assert that every
// ThenValue associated with the completion promise is dead.
- if (mCompletionPromise) {
- mCompletionPromise->AssertIsDead();
+ if (MozPromiseBase* p = CompletionPromise()) {
+ p->AssertIsDead();
} else {
MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
}
}
void Dispatch(MozPromise *aPromise)
{
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
@@ -457,52 +463,45 @@ protected:
MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
Request::mDisconnected = true;
// We could support rejecting the completion promise on disconnection, but
// then we'd need to have some sort of default reject value. The use cases
// of disconnection and completion promise chaining seem pretty orthogonal,
// so let's use assert against it.
- MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise);
+ MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise());
}
protected:
- virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0;
+ 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->IsCurrentThreadIn());
Request::mComplete = true;
if (Request::mDisconnected) {
PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this);
return;
}
// Invoke the resolve or reject method.
- RefPtr<MozPromise> result = DoResolveOrRejectInternal(aValue);
-
- MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise || result,
- "Can't do promise chaining for a non-promise-returning method.");
-
- if (mCompletionPromise && result) {
- result->ChainTo(mCompletionPromise.forget(), "<chained completion promise>");
- }
+ DoResolveOrRejectInternal(aValue);
}
RefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
#ifdef PROMISE_DEBUG
uint32_t mMagic1 = sMagic;
#endif
- RefPtr<Private> mCompletionPromise;
+ const char* mCallSite;
#ifdef PROMISE_DEBUG
uint32_t mMagic2 = sMagic;
#endif
- const char* mCallSite;
};
/*
* We create two overloads for invoking Resolve/Reject Methods so as to
* make the resolve/reject value argument "optional".
*/
template<typename ThisType, typename MethodType, typename ValueType>
@@ -580,40 +579,53 @@ protected:
// 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:
- already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ MozPromiseBase* CompletionPromise() const override
{
- RefPtr<MozPromise> completion;
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ {
+ RefPtr<MozPromise> result;
if (aValue.IsResolve()) {
- completion = InvokeCallbackMethod(
+ result = InvokeCallbackMethod(
mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()));
} else {
- completion = InvokeCallbackMethod(
+ result = InvokeCallbackMethod(
mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()));
}
// Null out mThisVal after invoking the callback so that any references are
// released predictably on the dispatch thread. Otherwise, it would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mThisVal = nullptr;
- return completion.forget();
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
ResolveMethodType mResolveMethod;
RejectMethodType mRejectMethod;
+ RefPtr<Private> mCompletionPromise;
};
template<typename ThisType, typename ResolveRejectMethodType>
class ThenValue<ThisType*, ResolveRejectMethodType> : public ThenValueBase
{
friend class ThenCommand<ThenValue>;
using SupportChaining =
ReturnTypeIs<ResolveRejectMethodType, RefPtr<MozPromise>>;
@@ -634,33 +646,46 @@ protected:
// 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:
- already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ MozPromiseBase* CompletionPromise() const override
{
- RefPtr<MozPromise> completion = InvokeCallbackMethod(
+ return mCompletionPromise;
+ }
+
+ void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ {
+ RefPtr<MozPromise> result = InvokeCallbackMethod(
mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue));
// Null out mThisVal after invoking the callback so that any references are
// released predictably on the dispatch thread. Otherwise, it would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mThisVal = nullptr;
- return completion.forget();
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
ResolveRejectMethodType mResolveRejectMethod;
+ RefPtr<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 SupportChaining = IntegralConstant<
@@ -687,45 +712,60 @@ protected:
// 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:
- already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ 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.
- RefPtr<MozPromise> completion;
+ RefPtr<MozPromise> result;
if (aValue.IsResolve()) {
- completion = InvokeCallbackMethod(mResolveFunction.ptr(),
- &ResolveFunction::operator(), MaybeMove(aValue.ResolveValue()));
+ result = InvokeCallbackMethod(mResolveFunction.ptr(),
+ &ResolveFunction::operator(),
+ MaybeMove(aValue.ResolveValue()));
} else {
- completion = InvokeCallbackMethod(mRejectFunction.ptr(),
- &RejectFunction::operator(), MaybeMove(aValue.RejectValue()));
+ result = InvokeCallbackMethod(mRejectFunction.ptr(),
+ &RejectFunction::operator(),
+ MaybeMove(aValue.RejectValue()));
}
// Destroy callbacks after invocation so that any references in closures are
// released predictably on the dispatch thread. Otherwise, they would be
// released on whatever thread last drops its reference to the ThenValue,
// which may or may not be ok.
mResolveFunction.reset();
mRejectFunction.reset();
- return completion.forget();
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
+ RefPtr<Private> mCompletionPromise;
};
template<typename ResolveRejectFunction>
class ThenValue<ResolveRejectFunction> : public ThenValueBase
{
friend class ThenCommand<ThenValue>;
using SupportChaining =
ReturnTypeIs<ResolveRejectFunction, RefPtr<MozPromise>>;
@@ -746,39 +786,52 @@ protected:
// 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:
- already_AddRefed<MozPromise> DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override
+ 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.
- RefPtr<MozPromise> completion =
+ RefPtr<MozPromise> result =
InvokeCallbackMethod(mResolveRejectFunction.ptr(),
&ResolveRejectFunction::operator(),
MaybeMove(aValue));
// 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();
- return completion.forget();
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mCompletionPromise || result,
+ "Can't do promise chaining for a non-promise-returning method.");
+
+ if (mCompletionPromise && result) {
+ result->ChainTo(mCompletionPromise.forget(),
+ "<chained completion promise>");
+ }
}
private:
Maybe<ResolveRejectFunction> mResolveRejectFunction; // Only accessed and deleted on dispatch thread.
+ RefPtr<Private> mCompletionPromise;
};
public:
void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
const char* aCallSite)
{
PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex);
MOZ_ASSERT(aResponseThread);
@@ -923,17 +976,17 @@ public:
mChainedPromises.AppendElement(chainedPromise);
}
}
// Note we expose the function AssertIsDead() instead of IsDead() since
// checking IsDead() is a data race in the situation where the request is not
// dead. Therefore we enforce the form |Assert(IsDead())| by exposing
// AssertIsDead() only.
- void AssertIsDead()
+ 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();