summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/debug/job-queue-02.js
blob: 5f09a818afcfabf9337a76177cdb5f32c294ce13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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
}