Bug 1504464 - Part 1: Mark reader.[[closedPromise]] as handled when creating a reader for an already-errored stream. r=jwalden
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 14 Jan 2019 20:30:56 +0000
changeset 510915 72b109d30535050adb88689314d01bad4ac3aea6
parent 510914 848d8e9a1f031827544f0c14c7c018fdcde6c774
child 510916 d087b9c8c3891aac295ca973277ffd37e7fe2daa
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1504464
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1504464 - Part 1: Mark reader.[[closedPromise]] as handled when creating a reader for an already-errored stream. r=jwalden I don't know why it's OK to drop this particular error; my guess is that the error was already reported previously, when the stream became errored, and there's no point reporting it again. Differential Revision: https://phabricator.services.mozilla.com/D14496
js/src/builtin/Promise.h
js/src/builtin/Stream.cpp
js/src/jit-test/tests/stream/reader-closedPromise-handled.js
--- a/js/src/builtin/Promise.h
+++ b/js/src/builtin/Promise.h
@@ -95,16 +95,19 @@ class PromiseObject : public NativeObjec
                                bool needsWrapping = false);
 
   static PromiseObject* createSkippingExecutor(JSContext* cx);
 
   static JSObject* unforgeableResolve(JSContext* cx, HandleValue value);
   static JSObject* unforgeableReject(JSContext* cx, HandleValue value);
 
   int32_t flags() { return getFixedSlot(PromiseSlot_Flags).toInt32(); }
+  void setHandled() {
+    setFixedSlot(PromiseSlot_Flags, Int32Value(flags() | PROMISE_FLAG_HANDLED));
+  }
   JS::PromiseState state() {
     int32_t flags = this->flags();
     if (!(flags & PROMISE_FLAG_RESOLVED)) {
       MOZ_ASSERT(!(flags & PROMISE_FLAG_FULFILLED));
       return JS::PromiseState::Pending;
     }
     if (flags & PROMISE_FLAG_FULFILLED) {
       return JS::PromiseState::Fulfilled;
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -2024,33 +2024,39 @@ static MOZ_MUST_USE bool ReadableStreamR
   // Step 2 is moved to the end.
 
   // Step 3: If stream.[[state]] is "readable",
   RootedObject promise(cx);
   if (unwrappedStream->readable()) {
     // Step a: Set reader.[[closedPromise]] to a new promise.
     promise = PromiseObject::createSkippingExecutor(cx);
   } else if (unwrappedStream->closed()) {
-    // Step 4: Otherwise
-    // Step a: If stream.[[state]] is "closed",
-    // Step i: Set reader.[[closedPromise]] to a new promise resolved with
+    // Step 4: Otherwise, if stream.[[state]] is "closed",
+    // Step a: Set reader.[[closedPromise]] to a new promise resolved with
     //         undefined.
     promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
   } else {
-    // Step b: Otherwise,
-    // Step i: Assert: stream.[[state]] is "errored".
+    // Step 5: Otherwise,
+    // Step a: Assert: stream.[[state]] is "errored".
     MOZ_ASSERT(unwrappedStream->errored());
 
-    // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
-    //          stream.[[storedError]].
+    // Step b: Set reader.[[closedPromise]] to a promise rejected with
+    //         stream.[[storedError]].
     RootedValue storedError(cx, unwrappedStream->storedError());
     if (!cx->compartment()->wrap(cx, &storedError)) {
       return false;
     }
     promise = PromiseObject::unforgeableReject(cx, storedError);
+    if (!promise) {
+      return false;
+    }
+
+    // Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+    promise->as<PromiseObject>().setHandled();
+    cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
   }
 
   if (!promise) {
     return false;
   }
 
   reader->setClosedPromise(promise);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/stream/reader-closedPromise-handled.js
@@ -0,0 +1,23 @@
+// Creating a reader from an errored stream should not result in a promise
+// being tracked as unhandled.
+
+// Create an errored stream.
+let stream = new ReadableStream({
+  start(controller) {
+    controller.error(new Error("splines insufficiently reticulated"));
+  }
+});
+drainJobQueue();
+
+// Track promises.
+let status = new Map;
+setPromiseRejectionTrackerCallback((p, x) => { status.set(p, x); });
+
+// Per Streams spec 3.7.4 step 5.c, this creates a rejected promise
+// (reader.closed) but marks it as handled.
+let reader = stream.getReader();
+
+// Check that the promise's status is not 0 (unhandled);
+// it may be either 1 (handled) or undefined (never tracked).
+let result = status.get(reader.closed);
+assertEq(result === 1 || result === undefined, true);