tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

job-queue-02.js (3171B)


      1 // |jit-test| error: "async tests completed successfully"
      2 // Test that the shell's job queue doesn't skip calls to JS::JobQueueMayNotBeEmpty.
      3 
      4 // For expressions like `await 1`, or `await P` for some already-resolved
      5 // promise P, there's no need to suspend the async call to determine the
      6 // expression's value. Suspension and resumption are expensive, so it would be
      7 // nice if we could avoid them.
      8 //
      9 // But of course, even when the value is known, the act of suspension itself is
     10 // visible: an async call's first suspension returns to its (synchronous)
     11 // caller; subsequent suspensions let other jobs run. So in general, we can't
     12 // short-circuit such `await` expressions.
     13 //
     14 // However, if an async call has been resumed from the job queue (that is, this
     15 // isn't the initial execution, with a synchronous caller expecting a promise of
     16 // the call's final return value), and there are no other jobs following that,
     17 // then the `await`'s reaction job would run immediately following this job ---
     18 // which *is* indistinguishable from skipping the suspension altogether.
     19 //
     20 // A JS::JobQueue implementation may call JS::JobQueueIsEmpty to indicate to the
     21 // engine that the currently running job is the last job in the queue, so this
     22 // optimization may be considered (there are further conditions that must be met
     23 // as well). If the JobQueue calls JobQueueIsEmpty, then it must also call
     24 // JS::JobQueueMayNotBeEmpty when jobs are enqueued, to indicate when the
     25 // opportunity has passed.
     26 
     27 var log = '';
     28 async function f(label, k) {
     29  log += label + '1';
     30  await 1;
     31  log += label + '2';
     32  await 1;
     33  log += label + '3';
     34 
     35  return k();
     36 }
     37 
     38 // Call `f` with `label` and `k`. If `skippable` is true, exercise the path that
     39 // skips the suspension and resumption; otherwise exercise the
     40 // non-short-circuited path.
     41 function test(skippable, label, k) {
     42  var resolve;
     43  (new Promise(r => { resolve = r; }))
     44    .then(v => { log += v + 't'; });
     45  assertEq(log, '');
     46  f(label, k);
     47  // job queue now: f(label)'s first await's continuation
     48  assertEq(log, label + '1');
     49 
     50  if (!skippable) {
     51    resolve('p');
     52    assertEq(log, label + '1');
     53    // job queue now: f(label)'s first await's continuation, explicit promise's reaction
     54  }
     55 
     56  // Resuming f(label) will reach the second await, which should skip suspension
     57  // or not, depending on whether we resolved that promise.
     58 }
     59 
     60 // SpiderMonkey's internal 'queue is empty' flag is initially false, even though
     61 // the queue is initially empty, because we don't yet know whether the embedding
     62 // is going to participate in the optimization by calling
     63 // JS::JobQueueMayNotBeEmpty and JS::JobQueueIsEmpty. But since the shell uses
     64 // SpiderMonkey's internal job queue implementation, this call to
     65 // `drainJobQueue` calls `JS::JobQueueIsEmpty`, and we are ready to play.
     66 Promise.resolve(42).then(v => assertEq(v, 42));
     67 drainJobQueue();
     68 
     69 log = '';
     70 test(true, 'b', continuation1);
     71 
     72 function continuation1() {
     73  assertEq(log, 'b1b2b3');
     74 
     75  log = '';
     76  test(false, 'c', continuation2);
     77 }
     78 
     79 function continuation2() {
     80  assertEq(log, 'c1c2ptc3');
     81  throw "async tests completed successfully"; // proof that we actually finished
     82 }