Bug 1504464 - Part 2: Mark reader.[[closedPromise]] as handled in reader.releaseLock(). r=jwalden
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 14 Jan 2019 20:31:29 +0000
changeset 513798 d087b9c8c3891aac295ca973277ffd37e7fe2daa
parent 513797 72b109d30535050adb88689314d01bad4ac3aea6
child 513799 0030c59cdea424b2f7a4dd77a83b9ba5d4b71dd4
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [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 2: Mark reader.[[closedPromise]] as handled in reader.releaseLock(). r=jwalden In this case, it's likely the user doesn't see this as an error at all. Differential Revision: https://phabricator.services.mozilla.com/D14497
js/src/builtin/Stream.cpp
js/src/jit-test/tests/stream/reader-closedPromise-handled-2.js
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -2114,50 +2114,56 @@ static MOZ_MUST_USE bool ReadableStreamR
   if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
     // Uncatchable error. Die immediately without resolving
     // reader.[[closedPromise]].
     return false;
   }
 
   // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
   //         reader.[[closedPromise]] with a TypeError exception.
+  Rooted<PromiseObject*> unwrappedClosedPromise(cx);
   if (unwrappedStream->readable()) {
-    Rooted<PromiseObject*> closedPromise(
-        cx, UnwrapInternalSlot<PromiseObject>(
-                cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise));
-    if (!closedPromise) {
+    unwrappedClosedPromise = 
+      UnwrapInternalSlot<PromiseObject>(
+        cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
+    if (!unwrappedClosedPromise) {
       return false;
     }
 
-    AutoRealm ar(cx, closedPromise);
+    AutoRealm ar(cx, unwrappedClosedPromise);
     if (!cx->compartment()->wrap(cx, &exn)) {
       return false;
     }
-    if (!PromiseObject::reject(cx, closedPromise, exn)) {
+    if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
       return false;
     }
   } else {
     // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise
     //         rejected with a TypeError exception.
     RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
     if (!closedPromise) {
       return false;
     }
+    unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
 
     AutoRealm ar(cx, unwrappedReader);
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
       return false;
     }
     unwrappedReader->setClosedPromise(closedPromise);
   }
 
-  // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+  // Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+  unwrappedClosedPromise->setHandled();
+  cx->runtime()->removeUnhandledRejectedPromise(cx, unwrappedClosedPromise);
+
+  // Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
   unwrappedStream->clearReader();
 
-  // Step 6: Set reader.[[ownerReadableStream]] to undefined.
+  // Step 7: Set reader.[[ownerReadableStream]] to undefined.
   unwrappedReader->clearStream();
 
   return true;
 }
 
 static MOZ_MUST_USE JSObject* ReadableStreamControllerPullSteps(
     JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/stream/reader-closedPromise-handled-2.js
@@ -0,0 +1,33 @@
+// Releasing a reader should not result in a promise being tracked as
+// unhandled.
+
+function test(readable) {
+  // Create an errored stream.
+  let controller;
+  let stream = new ReadableStream({
+    start(c) {
+      controller = c;
+    }
+  });
+  drainJobQueue();
+
+  // Track promises.
+  let status = new Map;
+  setPromiseRejectionTrackerCallback((p, x) => { status.set(p, x); });
+
+  // Per Streams spec 3.7.5 step 5, this creates a rejected promise
+  // (reader.closed) but marks it as handled.
+  let reader = stream.getReader();
+  if (!readable) {
+    controller.close();
+  }
+  reader.releaseLock();
+
+  // 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);
+}
+
+test(true);
+test(false);