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 }