summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/debug/job-queue-02.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/debug/job-queue-02.js')
-rw-r--r--js/src/jit-test/tests/debug/job-queue-02.js82
1 files changed, 82 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/debug/job-queue-02.js b/js/src/jit-test/tests/debug/job-queue-02.js
new file mode 100644
index 0000000000..5f09a818af
--- /dev/null
+++ b/js/src/jit-test/tests/debug/job-queue-02.js
@@ -0,0 +1,82 @@
+// |jit-test| error: "async tests completed successfully"
+// Test that the shell's job queue doesn't skip calls to JS::JobQueueMayNotBeEmpty.
+
+// For expressions like `await 1`, or `await P` for some already-resolved
+// promise P, there's no need to suspend the async call to determine the
+// expression's value. Suspension and resumption are expensive, so it would be
+// nice if we could avoid them.
+//
+// But of course, even when the value is known, the act of suspension itself is
+// visible: an async call's first suspension returns to its (synchronous)
+// caller; subsequent suspensions let other jobs run. So in general, we can't
+// short-circuit such `await` expressions.
+//
+// However, if an async call has been resumed from the job queue (that is, this
+// isn't the initial execution, with a synchronous caller expecting a promise of
+// the call's final return value), and there are no other jobs following that,
+// then the `await`'s reaction job would run immediately following this job ---
+// which *is* indistinguishable from skipping the suspension altogether.
+//
+// A JS::JobQueue implementation may call JS::JobQueueIsEmpty to indicate to the
+// engine that the currently running job is the last job in the queue, so this
+// optimization may be considered (there are further conditions that must be met
+// as well). If the JobQueue calls JobQueueIsEmpty, then it must also call
+// JS::JobQueueMayNotBeEmpty when jobs are enqueued, to indicate when the
+// opportunity has passed.
+
+var log = '';
+async function f(label, k) {
+ log += label + '1';
+ await 1;
+ log += label + '2';
+ await 1;
+ log += label + '3';
+
+ return k();
+}
+
+// Call `f` with `label` and `k`. If `skippable` is true, exercise the path that
+// skips the suspension and resumption; otherwise exercise the
+// non-short-circuited path.
+function test(skippable, label, k) {
+ var resolve;
+ (new Promise(r => { resolve = r; }))
+ .then(v => { log += v + 't'; });
+ assertEq(log, '');
+ f(label, k);
+ // job queue now: f(label)'s first await's continuation
+ assertEq(log, label + '1');
+
+ if (!skippable) {
+ resolve('p');
+ assertEq(log, label + '1');
+ // job queue now: f(label)'s first await's continuation, explicit promise's reaction
+ }
+
+ // Resuming f(label) will reach the second await, which should skip suspension
+ // or not, depending on whether we resolved that promise.
+}
+
+// SpiderMonkey's internal 'queue is empty' flag is initially false, even though
+// the queue is initially empty, because we don't yet know whether the embedding
+// is going to participate in the optimization by calling
+// JS::JobQueueMayNotBeEmpty and JS::JobQueueIsEmpty. But since the shell uses
+// SpiderMonkey's internal job queue implementation, this call to
+// `drainJobQueue` calls `JS::JobQueueIsEmpty`, and we are ready to play.
+Promise.resolve(42).then(v => assertEq(v, 42));
+drainJobQueue();
+
+log = '';
+test(true, 'b', continuation1);
+
+function continuation1() {
+ assertEq(log, 'b1b2b3');
+
+ log = '';
+ test(false, 'c', continuation2);
+}
+
+function continuation2() {
+ assertEq(log, 'c1c2ptc3');
+ throw "async tests completed successfully"; // proof that we actually finished
+}