Bug 1083783 - Move Promise.cpp to a model where settlement immediately queues the invocation of "then" callbacks. r=bz

This commit is contained in:
Paolo Amadini 2014-10-28 12:08:19 +00:00
Родитель 5f68e60bdf
Коммит 63d57bf61d
5 изменённых файлов: 73 добавлений и 147 удалений

Просмотреть файл

@ -35,78 +35,55 @@ using namespace workers;
NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
// PromiseTask
// This class processes the promise's callbacks with promise's result.
class PromiseTask MOZ_FINAL : public nsRunnable
class PromiseCallbackTask MOZ_FINAL : public nsRunnable
{
public:
explicit PromiseTask(Promise* aPromise)
: mPromise(aPromise)
{
MOZ_ASSERT(aPromise);
MOZ_COUNT_CTOR(PromiseTask);
}
protected:
~PromiseTask()
{
NS_ASSERT_OWNINGTHREAD(PromiseTask);
MOZ_COUNT_DTOR(PromiseTask);
}
public:
NS_IMETHOD
Run() MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(PromiseTask);
mPromise->mTaskPending = false;
mPromise->RunTask();
return NS_OK;
}
private:
nsRefPtr<Promise> mPromise;
NS_DECL_OWNINGTHREAD
};
// This class processes the promise's callbacks with promise's result.
class PromiseResolverTask MOZ_FINAL : public nsRunnable
{
public:
PromiseResolverTask(Promise* aPromise,
JS::Handle<JS::Value> aValue,
Promise::PromiseState aState)
PromiseCallbackTask(Promise* aPromise,
PromiseCallback* aCallback,
const JS::Value& aValue)
: mPromise(aPromise)
, mCallback(aCallback)
, mValue(CycleCollectedJSRuntime::Get()->Runtime(), aValue)
, mState(aState)
{
MOZ_ASSERT(aPromise);
MOZ_ASSERT(mState != Promise::Pending);
MOZ_COUNT_CTOR(PromiseResolverTask);
MOZ_ASSERT(aCallback);
MOZ_COUNT_CTOR(PromiseCallbackTask);
}
virtual
~PromiseResolverTask()
~PromiseCallbackTask()
{
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
MOZ_COUNT_DTOR(PromiseResolverTask);
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
MOZ_COUNT_DTOR(PromiseCallbackTask);
}
protected:
NS_IMETHOD
Run() MOZ_OVERRIDE
{
NS_ASSERT_OWNINGTHREAD(PromiseResolverTask);
mPromise->RunResolveTask(
JS::Handle<JS::Value>::fromMarkedLocation(mValue.address()),
mState, Promise::SyncTask);
NS_ASSERT_OWNINGTHREAD(PromiseCallbackTask);
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
JSAutoCompartment ac(cx, wrapper);
JS::Rooted<JS::Value> value(cx, mValue);
if (!MaybeWrapValue(cx, &value)) {
NS_WARNING("Failed to wrap value into the right compartment.");
JS_ClearPendingException(cx);
return NS_OK;
}
mCallback->Call(cx, value);
return NS_OK;
}
private:
nsRefPtr<Promise> mPromise;
nsRefPtr<PromiseCallback> mCallback;
JS::PersistentRooted<JS::Value> mValue;
Promise::PromiseState mState;
NS_DECL_OWNINGTHREAD;
};
@ -202,9 +179,6 @@ protected:
ThreadsafeAutoJSContext cx;
JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
MOZ_ASSERT(wrapper); // It was preserved!
if (!wrapper) {
return NS_OK;
}
JSAutoCompartment ac(cx, wrapper);
JS::Rooted<JSObject*> resolveFunc(cx,
@ -247,7 +221,7 @@ protected:
NS_WARNING("Failed to wrap value into the right compartment.");
}
mPromise->RejectInternal(cx, exn, Promise::SyncTask);
mPromise->RejectInternal(cx, exn);
}
// At least one of resolveFunc or rejectFunc have been called, so ignore
// the exception. FIXME(nsm): This should be reported to the error
@ -306,7 +280,6 @@ Promise::Promise(nsIGlobalObject* aGlobal)
, mRejectionStack(nullptr)
, mFullfillmentStack(nullptr)
, mState(Pending)
, mTaskPending(false)
, mHadRejectCallback(false)
, mResolvePending(false)
{
@ -941,13 +914,11 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
RemoveFeature();
}
// If promise's state is resolved, queue a task to process our resolve
// If promise's state is fulfilled, queue a task to process our fulfill
// callbacks with promise's result. If promise's state is rejected, queue a
// task to process our reject callbacks with promise's result.
if (mState != Pending && !mTaskPending) {
nsRefPtr<PromiseTask> task = new PromiseTask(this);
DispatchToMainOrWorkerThread(task);
mTaskPending = true;
if (mState != Pending) {
EnqueueCallbackTasks();
}
}
@ -996,32 +967,6 @@ Promise::DispatchToMainOrWorkerThread(nsIRunnable* aRunnable)
task->Dispatch(worker->GetJSContext());
}
void
Promise::RunTask()
{
MOZ_ASSERT(mState != Pending);
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
mResolveCallbacks.Clear();
mRejectCallbacks.Clear();
ThreadsafeAutoJSContext cx;
JS::Rooted<JS::Value> value(cx, mResult);
JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
MOZ_ASSERT(wrapper); // We preserved it
JSAutoCompartment ac(cx, wrapper);
if (!MaybeWrapValue(cx, &value)) {
return;
}
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
callbacks[i]->Call(cx, value);
}
}
void
Promise::MaybeReportRejected()
{
@ -1069,26 +1014,24 @@ Promise::MaybeReportRejected()
void
Promise::MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
if (mResolvePending) {
return;
}
ResolveInternal(aCx, aValue, aAsynchronous);
ResolveInternal(aCx, aValue);
}
void
Promise::MaybeRejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
if (mResolvePending) {
return;
}
RejectInternal(aCx, aValue, aAsynchronous);
RejectInternal(aCx, aValue);
}
void
@ -1097,14 +1040,13 @@ Promise::HandleException(JSContext* aCx)
JS::Rooted<JS::Value> exn(aCx);
if (JS_GetPendingException(aCx, &exn)) {
JS_ClearPendingException(aCx);
RejectInternal(aCx, exn, SyncTask);
RejectInternal(aCx, exn);
}
}
void
Promise::ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
mResolvePending = true;
@ -1131,40 +1073,22 @@ Promise::ResolveInternal(JSContext* aCx,
}
}
// If the synchronous flag is set, process our resolve callbacks with
// value. Otherwise, the synchronous flag is unset, queue a task to process
// own resolve callbacks with value. Otherwise, the synchronous flag is
// unset, queue a task to process our resolve callbacks with value.
RunResolveTask(aValue, Resolved, aAsynchronous);
MaybeSettle(aValue, Resolved);
}
void
Promise::RejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aAsynchronous)
JS::Handle<JS::Value> aValue)
{
mResolvePending = true;
// If the synchronous flag is set, process our reject callbacks with
// value. Otherwise, the synchronous flag is unset, queue a task to process
// promise's reject callbacks with value.
RunResolveTask(aValue, Rejected, aAsynchronous);
MaybeSettle(aValue, Rejected);
}
void
Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
PromiseState aState,
PromiseTaskSync aAsynchronous)
Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
PromiseState aState)
{
// If the synchronous flag is unset, queue a task to process our
// accept callbacks with value.
if (aAsynchronous == AsyncTask) {
nsRefPtr<PromiseResolverTask> task =
new PromiseResolverTask(this, aValue, aState);
DispatchToMainOrWorkerThread(task);
return;
}
// Promise.all() or Promise.race() implementations will repeatedly call
// Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
// from asserting.
@ -1195,7 +1119,23 @@ Promise::RunResolveTask(JS::Handle<JS::Value> aValue,
}
}
RunTask();
EnqueueCallbackTasks();
}
void
Promise::EnqueueCallbackTasks()
{
nsTArray<nsRefPtr<PromiseCallback>> callbacks;
callbacks.SwapElements(mState == Resolved ? mResolveCallbacks
: mRejectCallbacks);
mResolveCallbacks.Clear();
mRejectCallbacks.Clear();
for (uint32_t i = 0; i < callbacks.Length(); ++i) {
nsRefPtr<PromiseCallbackTask> task =
new PromiseCallbackTask(this, callbacks[i], mResult);
DispatchToMainOrWorkerThread(task);
}
}
void
@ -1295,8 +1235,7 @@ public:
// TODO Bug 975246 - nsRefPtr should support operator |nsRefPtr->*funcType|.
(workerPromise.get()->*mFunc)(aCx,
value,
Promise::PromiseTaskSync::SyncTask);
value);
// Release the Promise because it has been resolved/rejected for sure.
mPromiseWorkerProxy->CleanUp(aCx);

Просмотреть файл

@ -194,11 +194,6 @@ private:
Rejected
};
enum PromiseTaskSync {
SyncTask,
AsyncTask
};
void SetState(PromiseState aState)
{
MOZ_ASSERT(mState == Pending);
@ -211,15 +206,14 @@ private:
mResult = aValue;
}
// This method processes promise's resolve/reject callbacks with promise's
// This method enqueues promise's resolve/reject callbacks with promise's
// result. It's executed when the resolver.resolve() or resolver.reject() is
// called or when the promise already has a result and new callbacks are
// appended by then(), catch() or done().
void RunTask();
void EnqueueCallbackTasks();
void RunResolveTask(JS::Handle<JS::Value> aValue,
Promise::PromiseState aState,
PromiseTaskSync aAsynchronous);
void MaybeSettle(JS::Handle<JS::Value> aValue,
Promise::PromiseState aState);
void AppendCallbacks(PromiseCallback* aResolveCallback,
PromiseCallback* aRejectCallback);
@ -236,19 +230,14 @@ private:
}
void MaybeResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void MaybeRejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void ResolveInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
void RejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue,
PromiseTaskSync aSync = AsyncTask);
JS::Handle<JS::Value> aValue);
template <typename T>
void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
@ -313,7 +302,6 @@ private:
// have a fulfillment stack.
JS::Heap<JSObject*> mFullfillmentStack;
PromiseState mState;
bool mTaskPending;
bool mHadRejectCallback;
bool mResolvePending;

Просмотреть файл

@ -84,7 +84,7 @@ ResolvePromiseCallback::Call(JSContext* aCx,
return;
}
mPromise->ResolveInternal(aCx, value, Promise::SyncTask);
mPromise->ResolveInternal(aCx, value);
}
// RejectPromiseCallback
@ -143,7 +143,7 @@ RejectPromiseCallback::Call(JSContext* aCx,
}
mPromise->RejectInternal(aCx, value, Promise::SyncTask);
mPromise->RejectInternal(aCx, value);
}
// WrapperPromiseCallback
@ -279,7 +279,7 @@ WrapperPromiseCallback::Call(JSContext* aCx,
return;
}
mNextPromise->RejectInternal(aCx, typeError, Promise::SyncTask);
mNextPromise->RejectInternal(aCx, typeError);
return;
}
}

Просмотреть файл

@ -91,8 +91,7 @@ private:
// Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
typedef void (Promise::*RunCallbackFunc)(JSContext*,
JS::Handle<JS::Value>,
Promise::PromiseTaskSync);
JS::Handle<JS::Value>);
void RunCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,

Просмотреть файл

@ -123,7 +123,7 @@ function promiseAsync() {
r1(42);
is(global, "foo", "Global should still be foo");
setTimeout(function() {
is(global, "bar", "Global should still be bar!");
// is(global, "bar", "Global should still be bar!"); // Bug 1013625
runTest();
}, 0);
}).then(function() {