1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #if !defined(MozPromise_h_)
8 #define MozPromise_h_
9
10 #include "mozilla/AbstractThread.h"
11 #include "mozilla/IndexSequence.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/Mutex.h"
15 #include "mozilla/Monitor.h"
16 #include "mozilla/Tuple.h"
17 #include "mozilla/TypeTraits.h"
18
19 #include "nsTArray.h"
20 #include "nsThreadUtils.h"
21
22 #if defined(DEBUG) || !defined(RELEASE_OR_BETA)
23 #define PROMISE_DEBUG
24 #endif
25
26 #ifdef PROMISE_DEBUG
27 #define PROMISE_ASSERT MOZ_RELEASE_ASSERT
28 #else
29 #define PROMISE_ASSERT(...) do { } while (0)
30 #endif
31
32 namespace mozilla {
33
34 extern LazyLogModule gMozPromiseLog;
35
36 #define PROMISE_LOG(x, ...) \
37 MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
38
39 namespace detail {
40 template<typename ThisType, typename Ret, typename ArgType>
41 static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType));
42 template<typename ThisType, typename Ret, typename ArgType>
43 static TrueType TakesArgumentHelper(Ret (ThisType::*)(ArgType) const);
44 template<typename ThisType, typename Ret>
45 static FalseType TakesArgumentHelper(Ret (ThisType::*)());
46 template<typename ThisType, typename Ret>
47 static FalseType TakesArgumentHelper(Ret (ThisType::*)() const);
48
49 template<typename ThisType, typename Ret, typename ArgType>
50 static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType));
51 template<typename ThisType, typename Ret, typename ArgType>
52 static Ret ReturnTypeHelper(Ret (ThisType::*)(ArgType) const);
53 template<typename ThisType, typename Ret>
54 static Ret ReturnTypeHelper(Ret (ThisType::*)());
55 template<typename ThisType, typename Ret>
56 static Ret ReturnTypeHelper(Ret (ThisType::*)() const);
57
58 template<typename MethodType>
59 struct ReturnType {
60 typedef decltype(detail::ReturnTypeHelper(DeclVal<MethodType>())) Type;
61 };
62
63 } // namespace detail
64
65 template<typename MethodType>
66 struct TakesArgument {
67 static const bool value = decltype(detail::TakesArgumentHelper(DeclVal<MethodType>()))::value;
68 };
69
70 template<typename MethodType, typename TargetType>
71 struct ReturnTypeIs {
72 static const bool value = IsConvertible<typename detail::ReturnType<MethodType>::Type, TargetType>::value;
73 };
74
75 /*
76 * A promise manages an asynchronous request that may or may not be able to be
77 * fulfilled immediately. When an API returns a promise, the consumer may attach
78 * callbacks to be invoked (asynchronously, on a specified thread) when the
79 * request is either completed (resolved) or cannot be completed (rejected).
80 * Whereas JS promise callbacks are dispatched from Microtask checkpoints,
81 * MozPromises resolution/rejection make a normal round-trip through the event
82 * loop, which simplifies their ordering semantics relative to other native code.
83 *
84 * MozPromises attempt to mirror the spirit of JS Promises to the extent that
85 * is possible (and desirable) in C++. While the intent is that MozPromises
86 * feel familiar to programmers who are accustomed to their JS-implemented cousin,
87 * we don't shy away from imposing restrictions and adding features that make
88 * sense for the use cases we encounter.
89 *
90 * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then()
91 * call accepts resolve and reject callbacks, and returns a MozPromise::Request.
92 * The Request object serves several purposes for the consumer.
93 *
94 * (1) It allows the caller to cancel the delivery of the resolve/reject value
95 * if it has not already occurred, via Disconnect() (this must be done on
96 * the target thread to avoid racing).
97 *
98 * (2) It provides access to a "Completion Promise", which is roughly analagous
99 * to the Promise returned directly by ->then() calls on JS promises. If
100 * the resolve/reject callback returns a new MozPromise, that promise is
101 * chained to the completion promise, such that its resolve/reject value
102 * will be forwarded along when it arrives. If the resolve/reject callback
103 * returns void, the completion promise is resolved/rejected with the same
104 * value that was passed to the callback.
105 *
106 * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs
107 * (rather than already_AddRefed) from various methods. This is done to allow elegant
108 * chaining of calls without cluttering up the code with intermediate variables, and
109 * without introducing separate API variants for callers that want a return value
110 * (from, say, ->Then()) from those that don't.
111 *
112 * When IsExclusive is true, the MozPromise does a release-mode assertion that
113 * there is at most one call to either Then(...) or ChainTo(...).
114 */
115
116 class MozPromiseRefcountable
117 {
118 public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)119 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable)
120 protected:
121 virtual ~MozPromiseRefcountable() {}
122 };
123
124 template<typename T> class MozPromiseHolder;
125 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
126 class MozPromise : public MozPromiseRefcountable
127 {
128 static const uint32_t sMagic = 0xcecace11;
129
130 public:
131 typedef ResolveValueT ResolveValueType;
132 typedef RejectValueT RejectValueType;
133 class ResolveOrRejectValue
134 {
135 public:
136 template<typename ResolveValueType_>
SetResolve(ResolveValueType_ && aResolveValue)137 void SetResolve(ResolveValueType_&& aResolveValue)
138 {
139 MOZ_ASSERT(IsNothing());
140 mResolveValue.emplace(Forward<ResolveValueType_>(aResolveValue));
141 }
142
143 template<typename RejectValueType_>
SetReject(RejectValueType_ && aRejectValue)144 void SetReject(RejectValueType_&& aRejectValue)
145 {
146 MOZ_ASSERT(IsNothing());
147 mRejectValue.emplace(Forward<RejectValueType_>(aRejectValue));
148 }
149
150 template<typename ResolveValueType_>
MakeResolve(ResolveValueType_ && aResolveValue)151 static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue)
152 {
153 ResolveOrRejectValue val;
154 val.SetResolve(Forward<ResolveValueType_>(aResolveValue));
155 return val;
156 }
157
158 template<typename RejectValueType_>
MakeReject(RejectValueType_ && aRejectValue)159 static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue)
160 {
161 ResolveOrRejectValue val;
162 val.SetReject(Forward<RejectValueType_>(aRejectValue));
163 return val;
164 }
165
IsResolve()166 bool IsResolve() const { return mResolveValue.isSome(); }
IsReject()167 bool IsReject() const { return mRejectValue.isSome(); }
IsNothing()168 bool IsNothing() const { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
169
ResolveValue()170 const ResolveValueType& ResolveValue() const { return mResolveValue.ref(); }
RejectValue()171 const RejectValueType& RejectValue() const { return mRejectValue.ref(); }
172
173 private:
174 Maybe<ResolveValueType> mResolveValue;
175 Maybe<RejectValueType> mRejectValue;
176 };
177
178 protected:
179 // MozPromise is the public type, and never constructed directly. Construct
180 // a MozPromise::Private, defined below.
MozPromise(const char * aCreationSite,bool aIsCompletionPromise)181 MozPromise(const char* aCreationSite, bool aIsCompletionPromise)
182 : mCreationSite(aCreationSite)
183 , mMutex("MozPromise Mutex")
184 , mHaveRequest(false)
185 , mIsCompletionPromise(aIsCompletionPromise)
186 #ifdef PROMISE_DEBUG
187 , mMagic4(mMutex.mLock)
188 #endif
189 {
190 PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this);
191 }
192
193 public:
194 // MozPromise::Private allows us to separate the public interface (upon which
195 // consumers of the promise may invoke methods like Then()) from the private
196 // interface (upon which the creator of the promise may invoke Resolve() or
197 // Reject()). APIs should create and store a MozPromise::Private (usually
198 // via a MozPromiseHolder), and return a MozPromise to consumers.
199 //
200 // NB: We can include the definition of this class inline once B2G ICS is gone.
201 class Private;
202
203 template<typename ResolveValueType_>
204 static RefPtr<MozPromise>
CreateAndResolve(ResolveValueType_ && aResolveValue,const char * aResolveSite)205 CreateAndResolve(ResolveValueType_&& aResolveValue, const char* aResolveSite)
206 {
207 RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aResolveSite);
208 p->Resolve(Forward<ResolveValueType_>(aResolveValue), aResolveSite);
209 return p.forget();
210 }
211
212 template<typename RejectValueType_>
213 static RefPtr<MozPromise>
CreateAndReject(RejectValueType_ && aRejectValue,const char * aRejectSite)214 CreateAndReject(RejectValueType_&& aRejectValue, const char* aRejectSite)
215 {
216 RefPtr<typename MozPromise::Private> p = new MozPromise::Private(aRejectSite);
217 p->Reject(Forward<RejectValueType_>(aRejectValue), aRejectSite);
218 return p.forget();
219 }
220
221 typedef MozPromise<nsTArray<ResolveValueType>, RejectValueType, IsExclusive> AllPromiseType;
222 private:
223 class AllPromiseHolder : public MozPromiseRefcountable
224 {
225 public:
AllPromiseHolder(size_t aDependentPromises)226 explicit AllPromiseHolder(size_t aDependentPromises)
227 : mPromise(new typename AllPromiseType::Private(__func__))
228 , mOutstandingPromises(aDependentPromises)
229 {
230 mResolveValues.SetLength(aDependentPromises);
231 }
232
Resolve(size_t aIndex,const ResolveValueType & aResolveValue)233 void Resolve(size_t aIndex, const ResolveValueType& aResolveValue)
234 {
235 if (!mPromise) {
236 // Already rejected.
237 return;
238 }
239
240 mResolveValues[aIndex].emplace(aResolveValue);
241 if (--mOutstandingPromises == 0) {
242 nsTArray<ResolveValueType> resolveValues;
243 resolveValues.SetCapacity(mResolveValues.Length());
244 for (size_t i = 0; i < mResolveValues.Length(); ++i) {
245 resolveValues.AppendElement(mResolveValues[i].ref());
246 }
247
248 mPromise->Resolve(resolveValues, __func__);
249 mPromise = nullptr;
250 mResolveValues.Clear();
251 }
252 }
253
Reject(const RejectValueType & aRejectValue)254 void Reject(const RejectValueType& aRejectValue)
255 {
256 if (!mPromise) {
257 // Already rejected.
258 return;
259 }
260
261 mPromise->Reject(aRejectValue, __func__);
262 mPromise = nullptr;
263 mResolveValues.Clear();
264 }
265
Promise()266 AllPromiseType* Promise() { return mPromise; }
267
268 private:
269 nsTArray<Maybe<ResolveValueType>> mResolveValues;
270 RefPtr<typename AllPromiseType::Private> mPromise;
271 size_t mOutstandingPromises;
272 };
273 public:
274
All(AbstractThread * aProcessingThread,nsTArray<RefPtr<MozPromise>> & aPromises)275 static RefPtr<AllPromiseType> All(AbstractThread* aProcessingThread, nsTArray<RefPtr<MozPromise>>& aPromises)
276 {
277 RefPtr<AllPromiseHolder> holder = new AllPromiseHolder(aPromises.Length());
278 for (size_t i = 0; i < aPromises.Length(); ++i) {
279 aPromises[i]->Then(aProcessingThread, __func__,
280 [holder, i] (ResolveValueType aResolveValue) -> void { holder->Resolve(i, aResolveValue); },
281 [holder] (RejectValueType aRejectValue) -> void { holder->Reject(aRejectValue); }
282 );
283 }
284 return holder->Promise();
285 }
286
287 class Request : public MozPromiseRefcountable
288 {
289 public:
290 virtual void Disconnect() = 0;
291
292 virtual MozPromise* CompletionPromise() = 0;
293
294 virtual void AssertIsDead() = 0;
295
296 protected:
Request()297 Request() : mComplete(false), mDisconnected(false) {}
~Request()298 virtual ~Request() {}
299
300 bool mComplete;
301 bool mDisconnected;
302 };
303
304 protected:
305
306 /*
307 * A ThenValue tracks a single consumer waiting on the promise. When a consumer
308 * invokes promise->Then(...), a ThenValue is created. Once the Promise is
309 * resolved or rejected, a {Resolve,Reject}Runnable is dispatched, which
310 * invokes the resolve/reject method and then deletes the ThenValue.
311 */
312 class ThenValueBase : public Request
313 {
314 static const uint32_t sMagic = 0xfadece11;
315
316 public:
317 class ResolveOrRejectRunnable : public Runnable
318 {
319 public:
ResolveOrRejectRunnable(ThenValueBase * aThenValue,MozPromise * aPromise)320 ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise)
321 : mThenValue(aThenValue)
322 , mPromise(aPromise)
323 {
324 MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending());
325 }
326
~ResolveOrRejectRunnable()327 ~ResolveOrRejectRunnable()
328 {
329 if (mThenValue) {
330 mThenValue->AssertIsDead();
331 }
332 }
333
Run()334 NS_IMETHOD Run() override
335 {
336 PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this);
337 mThenValue->DoResolveOrReject(mPromise->Value());
338 mThenValue = nullptr;
339 mPromise = nullptr;
340 return NS_OK;
341 }
342
343 private:
344 RefPtr<ThenValueBase> mThenValue;
345 RefPtr<MozPromise> mPromise;
346 };
347
ThenValueBase(AbstractThread * aResponseTarget,const char * aCallSite)348 explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite)
349 : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {}
350
351 #ifdef PROMISE_DEBUG
~ThenValueBase()352 ~ThenValueBase()
353 {
354 mMagic1 = 0;
355 mMagic2 = 0;
356 }
357 #endif
358
CompletionPromise()359 MozPromise* CompletionPromise() override
360 {
361 MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
362 MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
363 if (!mCompletionPromise) {
364 mCompletionPromise = new MozPromise::Private(
365 "<completion promise>", true /* aIsCompletionPromise */);
366 }
367 return mCompletionPromise;
368 }
369
AssertIsDead()370 void AssertIsDead() override
371 {
372 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
373 // We want to assert that this ThenValues is dead - that is to say, that
374 // there are no consumers waiting for the result. In the case of a normal
375 // ThenValue, we check that it has been disconnected, which is the way
376 // that the consumer signals that it no longer wishes to hear about the
377 // result. If this ThenValue has a completion promise (which is mutually
378 // exclusive with being disconnectable), we recursively assert that every
379 // ThenValue associated with the completion promise is dead.
380 if (mCompletionPromise) {
381 mCompletionPromise->AssertIsDead();
382 } else {
383 MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected);
384 }
385 }
386
Dispatch(MozPromise * aPromise)387 void Dispatch(MozPromise *aPromise)
388 {
389 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
390 aPromise->mMutex.AssertCurrentThreadOwns();
391 MOZ_ASSERT(!aPromise->IsPending());
392
393 RefPtr<Runnable> runnable =
394 static_cast<Runnable*>(new (typename ThenValueBase::ResolveOrRejectRunnable)(this, aPromise));
395 PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]",
396 aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", ThenValueBase::mCallSite,
397 runnable.get(), aPromise, this);
398
399 // Promise consumers are allowed to disconnect the Request object and
400 // then shut down the thread or task queue that the promise result would
401 // be dispatched on. So we unfortunately can't assert that promise
402 // dispatch succeeds. :-(
403 mResponseTarget->Dispatch(runnable.forget(), AbstractThread::DontAssertDispatchSuccess);
404 }
405
Disconnect()406 virtual void Disconnect() override
407 {
408 MOZ_DIAGNOSTIC_ASSERT(ThenValueBase::mResponseTarget->IsCurrentThreadIn());
409 MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
410 Request::mDisconnected = true;
411
412 // We could support rejecting the completion promise on disconnection, but
413 // then we'd need to have some sort of default reject value. The use cases
414 // of disconnection and completion promise chaining seem pretty orthogonal,
415 // so let's use assert against it.
416 MOZ_DIAGNOSTIC_ASSERT(!mCompletionPromise);
417 }
418
419 protected:
420 virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) = 0;
421
DoResolveOrReject(const ResolveOrRejectValue & aValue)422 void DoResolveOrReject(const ResolveOrRejectValue& aValue)
423 {
424 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic);
425 MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
426 Request::mComplete = true;
427 if (Request::mDisconnected) {
428 PROMISE_LOG("ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this);
429 return;
430 }
431
432 // Invoke the resolve or reject method.
433 RefPtr<MozPromise> p = DoResolveOrRejectInternal(aValue);
434
435 // If there's a completion promise, resolve it appropriately with the
436 // result of the method.
437 //
438 // We jump through some hoops to cast to MozPromise::Private here. This
439 // can go away when we can just declare mCompletionPromise as
440 // MozPromise::Private. See the declaration below.
441 RefPtr<MozPromise::Private> completionPromise =
442 dont_AddRef(static_cast<MozPromise::Private*>(mCompletionPromise.forget().take()));
443 if (completionPromise) {
444 if (p) {
445 p->ChainTo(completionPromise.forget(), "<chained completion promise>");
446 } else {
447 completionPromise->ResolveOrReject(aValue, "<completion of non-promise-returning method>");
448 }
449 }
450 }
451
452 RefPtr<AbstractThread> mResponseTarget; // May be released on any thread.
453 #ifdef PROMISE_DEBUG
454 uint32_t mMagic1 = sMagic;
455 #endif
456 // Declaring RefPtr<MozPromise::Private> here causes build failures
457 // on MSVC because MozPromise::Private is only forward-declared at this
458 // point. This hack can go away when we inline-declare MozPromise::Private,
459 // which is blocked on the B2G ICS compiler being too old.
460 RefPtr<MozPromise> mCompletionPromise;
461 #ifdef PROMISE_DEBUG
462 uint32_t mMagic2 = sMagic;
463 #endif
464 const char* mCallSite;
465 };
466
467 /*
468 * We create two overloads for invoking Resolve/Reject Methods so as to
469 * make the resolve/reject value argument "optional".
470 */
471
472 template<typename ThisType, typename MethodType, typename ValueType>
473 static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
474 TakesArgument<MethodType>::value,
475 already_AddRefed<MozPromise>>::Type
InvokeCallbackMethod(ThisType * aThisVal,MethodType aMethod,ValueType && aValue)476 InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
477 {
478 return ((*aThisVal).*aMethod)(Forward<ValueType>(aValue)).forget();
479 }
480
481 template<typename ThisType, typename MethodType, typename ValueType>
482 static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
483 TakesArgument<MethodType>::value,
484 already_AddRefed<MozPromise>>::Type
InvokeCallbackMethod(ThisType * aThisVal,MethodType aMethod,ValueType && aValue)485 InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
486 {
487 ((*aThisVal).*aMethod)(Forward<ValueType>(aValue));
488 return nullptr;
489 }
490
491 template<typename ThisType, typename MethodType, typename ValueType>
492 static typename EnableIf<ReturnTypeIs<MethodType, RefPtr<MozPromise>>::value &&
493 !TakesArgument<MethodType>::value,
494 already_AddRefed<MozPromise>>::Type
InvokeCallbackMethod(ThisType * aThisVal,MethodType aMethod,ValueType && aValue)495 InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
496 {
497 return ((*aThisVal).*aMethod)().forget();
498 }
499
500 template<typename ThisType, typename MethodType, typename ValueType>
501 static typename EnableIf<ReturnTypeIs<MethodType, void>::value &&
502 !TakesArgument<MethodType>::value,
503 already_AddRefed<MozPromise>>::Type
InvokeCallbackMethod(ThisType * aThisVal,MethodType aMethod,ValueType && aValue)504 InvokeCallbackMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue)
505 {
506 ((*aThisVal).*aMethod)();
507 return nullptr;
508 }
509
510 template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
511 class MethodThenValue : public ThenValueBase
512 {
513 public:
MethodThenValue(AbstractThread * aResponseTarget,ThisType * aThisVal,ResolveMethodType aResolveMethod,RejectMethodType aRejectMethod,const char * aCallSite)514 MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
515 ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
516 const char* aCallSite)
517 : ThenValueBase(aResponseTarget, aCallSite)
518 , mThisVal(aThisVal)
519 , mResolveMethod(aResolveMethod)
520 , mRejectMethod(aRejectMethod) {}
521
Disconnect()522 virtual void Disconnect() override
523 {
524 ThenValueBase::Disconnect();
525
526 // If a Request has been disconnected, we don't guarantee that the
527 // resolve/reject runnable will be dispatched. Null out our refcounted
528 // this-value now so that it's released predictably on the dispatch thread.
529 mThisVal = nullptr;
530 }
531
532 protected:
DoResolveOrRejectInternal(const ResolveOrRejectValue & aValue)533 virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) override
534 {
535 RefPtr<MozPromise> completion;
536 if (aValue.IsResolve()) {
537 completion = InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aValue.ResolveValue());
538 } else {
539 completion = InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aValue.RejectValue());
540 }
541
542 // Null out mThisVal after invoking the callback so that any references are
543 // released predictably on the dispatch thread. Otherwise, it would be
544 // released on whatever thread last drops its reference to the ThenValue,
545 // which may or may not be ok.
546 mThisVal = nullptr;
547
548 return completion.forget();
549 }
550
551 private:
552 RefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread.
553 ResolveMethodType mResolveMethod;
554 RejectMethodType mRejectMethod;
555 };
556
557 // NB: We could use std::function here instead of a template if it were supported. :-(
558 template<typename ResolveFunction, typename RejectFunction>
559 class FunctionThenValue : public ThenValueBase
560 {
561 public:
FunctionThenValue(AbstractThread * aResponseTarget,ResolveFunction && aResolveFunction,RejectFunction && aRejectFunction,const char * aCallSite)562 FunctionThenValue(AbstractThread* aResponseTarget,
563 ResolveFunction&& aResolveFunction,
564 RejectFunction&& aRejectFunction,
565 const char* aCallSite)
566 : ThenValueBase(aResponseTarget, aCallSite)
567 {
568 mResolveFunction.emplace(Move(aResolveFunction));
569 mRejectFunction.emplace(Move(aRejectFunction));
570 }
571
Disconnect()572 virtual void Disconnect() override
573 {
574 ThenValueBase::Disconnect();
575
576 // If a Request has been disconnected, we don't guarantee that the
577 // resolve/reject runnable will be dispatched. Destroy our callbacks
578 // now so that any references in closures are released predictable on
579 // the dispatch thread.
580 mResolveFunction.reset();
581 mRejectFunction.reset();
582 }
583
584 protected:
DoResolveOrRejectInternal(const ResolveOrRejectValue & aValue)585 virtual already_AddRefed<MozPromise> DoResolveOrRejectInternal(const ResolveOrRejectValue& aValue) override
586 {
587 // Note: The usage of InvokeCallbackMethod here requires that
588 // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous
589 // classes with ::operator()), since it allows us to share code more easily.
590 // We could fix this if need be, though it's quite easy to work around by
591 // just capturing something.
592 RefPtr<MozPromise> completion;
593 if (aValue.IsResolve()) {
594 completion = InvokeCallbackMethod(mResolveFunction.ptr(), &ResolveFunction::operator(), aValue.ResolveValue());
595 } else {
596 completion = InvokeCallbackMethod(mRejectFunction.ptr(), &RejectFunction::operator(), aValue.RejectValue());
597 }
598
599 // Destroy callbacks after invocation so that any references in closures are
600 // released predictably on the dispatch thread. Otherwise, they would be
601 // released on whatever thread last drops its reference to the ThenValue,
602 // which may or may not be ok.
603 mResolveFunction.reset();
604 mRejectFunction.reset();
605
606 return completion.forget();
607 }
608
609 private:
610 Maybe<ResolveFunction> mResolveFunction; // Only accessed and deleted on dispatch thread.
611 Maybe<RejectFunction> mRejectFunction; // Only accessed and deleted on dispatch thread.
612 };
613
614 public:
ThenInternal(AbstractThread * aResponseThread,ThenValueBase * aThenValue,const char * aCallSite)615 void ThenInternal(AbstractThread* aResponseThread, ThenValueBase* aThenValue,
616 const char* aCallSite)
617 {
618 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
619 MutexAutoLock lock(mMutex);
620 MOZ_ASSERT(aResponseThread->IsDispatchReliable());
621 MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
622 mHaveRequest = true;
623 PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
624 aCallSite, this, aThenValue, (int) IsPending());
625 if (!IsPending()) {
626 aThenValue->Dispatch(this);
627 } else {
628 mThenValues.AppendElement(aThenValue);
629 }
630 }
631
632 public:
633
634 template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
Then(AbstractThread * aResponseThread,const char * aCallSite,ThisType * aThisVal,ResolveMethodType aResolveMethod,RejectMethodType aRejectMethod)635 RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
636 ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
637 {
638 RefPtr<ThenValueBase> thenValue = new MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>(
639 aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite);
640 ThenInternal(aResponseThread, thenValue, aCallSite);
641 return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
642 }
643
644 template<typename ResolveFunction, typename RejectFunction>
Then(AbstractThread * aResponseThread,const char * aCallSite,ResolveFunction && aResolveFunction,RejectFunction && aRejectFunction)645 RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite,
646 ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
647 {
648 RefPtr<ThenValueBase> thenValue = new FunctionThenValue<ResolveFunction, RejectFunction>(aResponseThread,
649 Move(aResolveFunction), Move(aRejectFunction), aCallSite);
650 ThenInternal(aResponseThread, thenValue, aCallSite);
651 return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
652 }
653
ChainTo(already_AddRefed<Private> aChainedPromise,const char * aCallSite)654 void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
655 {
656 MutexAutoLock lock(mMutex);
657 MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
658 mHaveRequest = true;
659 RefPtr<Private> chainedPromise = aChainedPromise;
660 PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
661 aCallSite, this, chainedPromise.get(), (int) IsPending());
662 if (!IsPending()) {
663 ForwardTo(chainedPromise);
664 } else {
665 mChainedPromises.AppendElement(chainedPromise);
666 }
667 }
668
669 // Note we expose the function AssertIsDead() instead of IsDead() since
670 // checking IsDead() is a data race in the situation where the request is not
671 // dead. Therefore we enforce the form |Assert(IsDead())| by exposing
672 // AssertIsDead() only.
AssertIsDead()673 void AssertIsDead()
674 {
675 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
676 MutexAutoLock lock(mMutex);
677 for (auto&& then : mThenValues) {
678 then->AssertIsDead();
679 }
680 for (auto&& chained : mChainedPromises) {
681 chained->AssertIsDead();
682 }
683 }
684
685 protected:
IsPending()686 bool IsPending() const { return mValue.IsNothing(); }
Value()687 const ResolveOrRejectValue& Value() const
688 {
689 // This method should only be called once the value has stabilized. As
690 // such, we don't need to acquire the lock here.
691 MOZ_DIAGNOSTIC_ASSERT(!IsPending());
692 return mValue;
693 }
694
DispatchAll()695 void DispatchAll()
696 {
697 mMutex.AssertCurrentThreadOwns();
698 for (size_t i = 0; i < mThenValues.Length(); ++i) {
699 mThenValues[i]->Dispatch(this);
700 }
701 mThenValues.Clear();
702
703 for (size_t i = 0; i < mChainedPromises.Length(); ++i) {
704 ForwardTo(mChainedPromises[i]);
705 }
706 mChainedPromises.Clear();
707 }
708
ForwardTo(Private * aOther)709 void ForwardTo(Private* aOther)
710 {
711 MOZ_ASSERT(!IsPending());
712 if (mValue.IsResolve()) {
713 aOther->Resolve(mValue.ResolveValue(), "<chained promise>");
714 } else {
715 aOther->Reject(mValue.RejectValue(), "<chained promise>");
716 }
717 }
718
~MozPromise()719 virtual ~MozPromise()
720 {
721 PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this);
722 AssertIsDead();
723 // We can't guarantee a completion promise will always be revolved or
724 // rejected since ResolveOrRejectRunnable might not run when dispatch fails.
725 if (!mIsCompletionPromise) {
726 MOZ_ASSERT(!IsPending());
727 MOZ_ASSERT(mThenValues.IsEmpty());
728 MOZ_ASSERT(mChainedPromises.IsEmpty());
729 }
730 #ifdef PROMISE_DEBUG
731 mMagic1 = 0;
732 mMagic2 = 0;
733 mMagic3 = 0;
734 mMagic4 = nullptr;
735 #endif
736 };
737
738 const char* mCreationSite; // For logging
739 Mutex mMutex;
740 ResolveOrRejectValue mValue;
741 #ifdef PROMISE_DEBUG
742 uint32_t mMagic1 = sMagic;
743 #endif
744 nsTArray<RefPtr<ThenValueBase>> mThenValues;
745 #ifdef PROMISE_DEBUG
746 uint32_t mMagic2 = sMagic;
747 #endif
748 nsTArray<RefPtr<Private>> mChainedPromises;
749 #ifdef PROMISE_DEBUG
750 uint32_t mMagic3 = sMagic;
751 #endif
752 bool mHaveRequest;
753 const bool mIsCompletionPromise;
754 #ifdef PROMISE_DEBUG
755 void* mMagic4;
756 #endif
757 };
758
759 template<typename ResolveValueT, typename RejectValueT, bool IsExclusive>
760 class MozPromise<ResolveValueT, RejectValueT, IsExclusive>::Private
761 : public MozPromise<ResolveValueT, RejectValueT, IsExclusive>
762 {
763 public:
764 explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false)
MozPromise(aCreationSite,aIsCompletionPromise)765 : MozPromise(aCreationSite, aIsCompletionPromise) {}
766
767 template<typename ResolveValueT_>
Resolve(ResolveValueT_ && aResolveValue,const char * aResolveSite)768 void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite)
769 {
770 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
771 MutexAutoLock lock(mMutex);
772 MOZ_ASSERT(IsPending());
773 PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite);
774 mValue.SetResolve(Forward<ResolveValueT_>(aResolveValue));
775 DispatchAll();
776 }
777
778 template<typename RejectValueT_>
Reject(RejectValueT_ && aRejectValue,const char * aRejectSite)779 void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite)
780 {
781 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
782 MutexAutoLock lock(mMutex);
783 MOZ_ASSERT(IsPending());
784 PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite);
785 mValue.SetReject(Forward<RejectValueT_>(aRejectValue));
786 DispatchAll();
787 }
788
789 template<typename ResolveOrRejectValue_>
ResolveOrReject(ResolveOrRejectValue_ && aValue,const char * aSite)790 void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite)
791 {
792 PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == mMutex.mLock);
793 MutexAutoLock lock(mMutex);
794 MOZ_ASSERT(IsPending());
795 PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite);
796 mValue = Forward<ResolveOrRejectValue_>(aValue);
797 DispatchAll();
798 }
799 };
800
801 // A generic promise type that does the trick for simple use cases.
802 typedef MozPromise<bool, nsresult, /* IsExclusive = */ false> GenericPromise;
803
804 /*
805 * Class to encapsulate a promise for a particular role. Use this as the member
806 * variable for a class whose method returns a promise.
807 */
808 template<typename PromiseType>
809 class MozPromiseHolder
810 {
811 public:
MozPromiseHolder()812 MozPromiseHolder()
813 : mMonitor(nullptr) {}
814
815 // Move semantics.
816 MozPromiseHolder& operator=(MozPromiseHolder&& aOther)
817 {
818 MOZ_ASSERT(!mMonitor && !aOther.mMonitor);
819 MOZ_DIAGNOSTIC_ASSERT(!mPromise);
820 mPromise = aOther.mPromise;
821 aOther.mPromise = nullptr;
822 return *this;
823 }
824
~MozPromiseHolder()825 ~MozPromiseHolder() { MOZ_ASSERT(!mPromise); }
826
Ensure(const char * aMethodName)827 already_AddRefed<PromiseType> Ensure(const char* aMethodName) {
828 if (mMonitor) {
829 mMonitor->AssertCurrentThreadOwns();
830 }
831 if (!mPromise) {
832 mPromise = new (typename PromiseType::Private)(aMethodName);
833 }
834 RefPtr<PromiseType> p = mPromise.get();
835 return p.forget();
836 }
837
838 // Provide a Monitor that should always be held when accessing this instance.
SetMonitor(Monitor * aMonitor)839 void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; }
840
IsEmpty()841 bool IsEmpty() const
842 {
843 if (mMonitor) {
844 mMonitor->AssertCurrentThreadOwns();
845 }
846 return !mPromise;
847 }
848
Steal()849 already_AddRefed<typename PromiseType::Private> Steal()
850 {
851 if (mMonitor) {
852 mMonitor->AssertCurrentThreadOwns();
853 }
854
855 RefPtr<typename PromiseType::Private> p = mPromise;
856 mPromise = nullptr;
857 return p.forget();
858 }
859
Resolve(typename PromiseType::ResolveValueType aResolveValue,const char * aMethodName)860 void Resolve(typename PromiseType::ResolveValueType aResolveValue,
861 const char* aMethodName)
862 {
863 if (mMonitor) {
864 mMonitor->AssertCurrentThreadOwns();
865 }
866 MOZ_ASSERT(mPromise);
867 mPromise->Resolve(aResolveValue, aMethodName);
868 mPromise = nullptr;
869 }
870
871
ResolveIfExists(typename PromiseType::ResolveValueType aResolveValue,const char * aMethodName)872 void ResolveIfExists(typename PromiseType::ResolveValueType aResolveValue,
873 const char* aMethodName)
874 {
875 if (!IsEmpty()) {
876 Resolve(aResolveValue, aMethodName);
877 }
878 }
879
Reject(typename PromiseType::RejectValueType aRejectValue,const char * aMethodName)880 void Reject(typename PromiseType::RejectValueType aRejectValue,
881 const char* aMethodName)
882 {
883 if (mMonitor) {
884 mMonitor->AssertCurrentThreadOwns();
885 }
886 MOZ_ASSERT(mPromise);
887 mPromise->Reject(aRejectValue, aMethodName);
888 mPromise = nullptr;
889 }
890
891
RejectIfExists(typename PromiseType::RejectValueType aRejectValue,const char * aMethodName)892 void RejectIfExists(typename PromiseType::RejectValueType aRejectValue,
893 const char* aMethodName)
894 {
895 if (!IsEmpty()) {
896 Reject(aRejectValue, aMethodName);
897 }
898 }
899
900 private:
901 Monitor* mMonitor;
902 RefPtr<typename PromiseType::Private> mPromise;
903 };
904
905 /*
906 * Class to encapsulate a MozPromise::Request reference. Use this as the member
907 * variable for a class waiting on a MozPromise.
908 */
909 template<typename PromiseType>
910 class MozPromiseRequestHolder
911 {
912 public:
MozPromiseRequestHolder()913 MozPromiseRequestHolder() {}
~MozPromiseRequestHolder()914 ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); }
915
Begin(RefPtr<typename PromiseType::Request> && aRequest)916 void Begin(RefPtr<typename PromiseType::Request>&& aRequest)
917 {
918 MOZ_DIAGNOSTIC_ASSERT(!Exists());
919 mRequest = Move(aRequest);
920 }
921
Begin(typename PromiseType::Request * aRequest)922 void Begin(typename PromiseType::Request* aRequest)
923 {
924 MOZ_DIAGNOSTIC_ASSERT(!Exists());
925 mRequest = aRequest;
926 }
927
Complete()928 void Complete()
929 {
930 MOZ_DIAGNOSTIC_ASSERT(Exists());
931 mRequest = nullptr;
932 }
933
934 // Disconnects and forgets an outstanding promise. The resolve/reject methods
935 // will never be called.
Disconnect()936 void Disconnect() {
937 MOZ_ASSERT(Exists());
938 mRequest->Disconnect();
939 mRequest = nullptr;
940 }
941
DisconnectIfExists()942 void DisconnectIfExists() {
943 if (Exists()) {
944 Disconnect();
945 }
946 }
947
Exists()948 bool Exists() const { return !!mRequest; }
949
950 private:
951 RefPtr<typename PromiseType::Request> mRequest;
952 };
953
954 // Asynchronous Potentially-Cross-Thread Method Calls.
955 //
956 // This machinery allows callers to schedule a promise-returning method to be
957 // invoked asynchronously on a given thread, while at the same time receiving
958 // a promise upon which to invoke Then() immediately. InvokeAsync dispatches
959 // a task to invoke the method on the proper thread and also chain the resulting
960 // promise to the one that the caller received, so that resolve/reject values
961 // are forwarded through.
962
963 namespace detail {
964
965 template<typename ReturnType, typename ThisType, typename... ArgTypes, size_t... Indices>
966 ReturnType
MethodCallInvokeHelper(ReturnType (ThisType::* aMethod)(ArgTypes...),ThisType * aThisVal,Tuple<ArgTypes...> & aArgs,IndexSequence<Indices...>)967 MethodCallInvokeHelper(ReturnType(ThisType::*aMethod)(ArgTypes...), ThisType* aThisVal,
968 Tuple<ArgTypes...>& aArgs, IndexSequence<Indices...>)
969 {
970 return ((*aThisVal).*aMethod)(Get<Indices>(aArgs)...);
971 }
972
973 // Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause
974 // assertions when used on templated types.
975 class MethodCallBase
976 {
977 public:
MethodCallBase()978 MethodCallBase() { MOZ_COUNT_CTOR(MethodCallBase); }
~MethodCallBase()979 virtual ~MethodCallBase() { MOZ_COUNT_DTOR(MethodCallBase); }
980 };
981
982 template<typename PromiseType, typename ThisType, typename... ArgTypes>
983 class MethodCall : public MethodCallBase
984 {
985 public:
986 typedef RefPtr<PromiseType>(ThisType::*MethodType)(ArgTypes...);
MethodCall(MethodType aMethod,ThisType * aThisVal,ArgTypes...aArgs)987 MethodCall(MethodType aMethod, ThisType* aThisVal, ArgTypes... aArgs)
988 : mMethod(aMethod)
989 , mThisVal(aThisVal)
990 , mArgs(Forward<ArgTypes>(aArgs)...)
991 {}
992
Invoke()993 RefPtr<PromiseType> Invoke()
994 {
995 return MethodCallInvokeHelper(mMethod, mThisVal.get(), mArgs, typename IndexSequenceFor<ArgTypes...>::Type());
996 }
997
998 private:
999 MethodType mMethod;
1000 RefPtr<ThisType> mThisVal;
1001 Tuple<ArgTypes...> mArgs;
1002 };
1003
1004 template<typename PromiseType, typename ThisType, typename ...ArgTypes>
1005 class ProxyRunnable : public Runnable
1006 {
1007 public:
ProxyRunnable(typename PromiseType::Private * aProxyPromise,MethodCall<PromiseType,ThisType,ArgTypes...> * aMethodCall)1008 ProxyRunnable(typename PromiseType::Private* aProxyPromise, MethodCall<PromiseType, ThisType, ArgTypes...>* aMethodCall)
1009 : mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {}
1010
Run()1011 NS_IMETHOD Run() override
1012 {
1013 RefPtr<PromiseType> p = mMethodCall->Invoke();
1014 mMethodCall = nullptr;
1015 p->ChainTo(mProxyPromise.forget(), "<Proxy Promise>");
1016 return NS_OK;
1017 }
1018
1019 private:
1020 RefPtr<typename PromiseType::Private> mProxyPromise;
1021 nsAutoPtr<MethodCall<PromiseType, ThisType, ArgTypes...>> mMethodCall;
1022 };
1023
Any()1024 constexpr bool Any()
1025 {
1026 return false;
1027 }
1028
1029 template <typename T1>
Any(T1 a)1030 constexpr bool Any(T1 a)
1031 {
1032 return static_cast<bool>(a);
1033 }
1034
1035 template <typename T1, typename... Ts>
Any(T1 a,Ts...aOthers)1036 constexpr bool Any(T1 a, Ts... aOthers)
1037 {
1038 return a || Any(aOthers...);
1039 }
1040
1041 } // namespace detail
1042
1043 template<typename PromiseType, typename ThisType, typename ...ArgTypes, typename ...ActualArgTypes>
1044 static RefPtr<PromiseType>
InvokeAsync(AbstractThread * aTarget,ThisType * aThisVal,const char * aCallerName,RefPtr<PromiseType> (ThisType::* aMethod)(ArgTypes...),ActualArgTypes &&...aArgs)1045 InvokeAsync(AbstractThread* aTarget, ThisType* aThisVal, const char* aCallerName,
1046 RefPtr<PromiseType>(ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs)
1047 {
1048 static_assert(!detail::Any(IsReference<ArgTypes>::value...),
1049 "Cannot pass reference types through InvokeAsync, see bug 1313497 if you require it");
1050 typedef detail::MethodCall<PromiseType, ThisType, ArgTypes...> MethodCallType;
1051 typedef detail::ProxyRunnable<PromiseType, ThisType, ArgTypes...> ProxyRunnableType;
1052
1053 MethodCallType* methodCall = new MethodCallType(aMethod, aThisVal, Forward<ActualArgTypes>(aArgs)...);
1054 RefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName);
1055 RefPtr<ProxyRunnableType> r = new ProxyRunnableType(p, methodCall);
1056 MOZ_ASSERT(aTarget->IsDispatchReliable());
1057 aTarget->Dispatch(r.forget());
1058 return p.forget();
1059 }
1060
1061 #undef PROMISE_LOG
1062 #undef PROMISE_ASSERT
1063 #undef PROMISE_DEBUG
1064
1065 } // namespace mozilla
1066
1067 #endif
1068