Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion fibers_async.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,24 @@ function asyncContextFrameInUse() {
return inUse;
}

// Code after an `await` that blocked on the fiber we gave it (Future.wait, Promise.await, a sync
// Meteor call). Without this dispatch it would have thrown "Can't wait without a fiber", so each one
// marks code that still depends on fibers after an `await`. Counted once per job, when it first
// parks; nothing is counted for the jobs that don't block. Kept on the native module, which every
// copy of this file shares (an app can load more than one).
_Fiber.__awaitContinuationBlocks = _Fiber.__awaitContinuationBlocks || 0;

function dispatchOnNewFiber(runDispatched, token) {
const fiber = Fiber(() => runDispatched(token));
let finished = false;
const fiber = Fiber(() => {
runDispatched(token);
finished = true;
});
fiber[kHidden] = true;
fiber.run();
if (!finished) {
_Fiber.__awaitContinuationBlocks++;
}
}

// Runs jobs on one long-lived hidden fiber instead of a new fiber per job. After each job it keeps
Expand Down Expand Up @@ -148,6 +162,7 @@ function reusingDispatcher(runNextDispatchable) {
if (fiber.run(token) !== kIdle && dispatchFiber === fiber) {
// A job blocked on this fiber; it belongs to that job now.
dispatchFiber = null;
_Fiber.__awaitContinuationBlocks++;
}
};
}
Expand All @@ -158,4 +173,10 @@ Object.defineProperty(Fiber, 'microtasksBatched', {
}
})

Object.defineProperty(Fiber, 'awaitContinuationBlocks', {
get() {
return _Fiber.__awaitContinuationBlocks || 0;
}
})

module.exports = _Fiber.Fiber = Fiber;
86 changes: 86 additions & 0 deletions test/await-continuation-blocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';
// Fiber.awaitContinuationBlocks counts code after an `await` that blocked on the fiber the
// dispatcher ran it on: once per blocking job, never for jobs that don't block or for awaits made
// outside a fiber. Needs a node with the microtask dispatch patch; elsewhere nothing is dispatched.
var Fiber = require('fibers');
var Future;
try {
Future = require('fibers/future');
} catch (err) {
Future = require('future');
}

var enabled = Fiber.awaitDispatchEnabled !== undefined
? Fiber.awaitDispatchEnabled
: typeof Fiber.microtasksDispatched === 'number' && process.env.FIBERS_AWAIT_DISPATCH !== '0';
if (!enabled) {
console.log('pass');
return;
}

function sleep(ms) {
var future = new Future();
setTimeout(function() {
future.return();
}, ms);
return future.wait();
}

function inFiber(fn) {
return new Promise(function(resolve, reject) {
Fiber(function() {
fn().then(resolve, reject);
}).run();
});
}

async function blocks(fn) {
var before = Fiber.awaitContinuationBlocks;
await fn();
return Fiber.awaitContinuationBlocks - before;
}

async function main() {
var cases = [
['awaits that never block', 0, function() {
return inFiber(async function() {
await null;
await new Promise(function(resolve) {
setTimeout(resolve, 1);
});
});
}],
['one continuation blocking twice', 1, function() {
return inFiber(async function() {
await null;
sleep(1);
sleep(1);
});
}],
['two continuations that block', 2, function() {
return inFiber(async function() {
await null;
sleep(1);
await null;
sleep(1);
});
}],
['awaits outside a fiber', 0, async function() {
await null;
await new Promise(function(resolve) {
setTimeout(resolve, 1);
});
}],
];
for (var ii = 0; ii < cases.length; ii++) {
var got = await blocks(cases[ii][2]);
if (got !== cases[ii][1]) {
throw new Error(cases[ii][0] + ': expected ' + cases[ii][1] + ' blocks, counted ' + got);
}
}
console.log('pass');
}

main().catch(function(err) {
console.log(err.stack);
});