Hello,
I am trying to figure out how to maintain state associated with async functions across await boundaries. We currently have instrumentation for promises which allows us to maintain state in each step of the promise chain, unfortunately for us async/await is implemented using only internal functions with no exposure to JS-land. This makes it impossible for us to instrument it without requiring either user or V8 code modification. The app pasted below shows a simple use case where using promises directly maintains the correct state while using async/await loses it.
I was exploring this using Node 7.7.1 which uses V8 5.5.372 and found a naive solution (see attached await.patch). This shows the spirit of what we would like to do. We need async/await to use a JS-land function to add its continuation, or otherwise expose some kind of hook, so that we can get in there and maintain our state. I looked into upgrading my patch to the latest V8 before bringing it here to discuss, but it looks like async/await was completely refactored and the JS function AsyncFunctionAwait which my patch modified became the C++ function AsyncBuiltinsAssembler::Await (in builtins-async.cc) and I am not at all familiar with V8's assemblers.
I would like to know what is the best way forward to get a change into V8 making async/await instrumentable?
-Bryan
'use strict';
// Some environment variables to make newrelic keep quiet.
process.env.NEW_RELIC_NO_CONFIG_FILE = true;
process.env.NEW_RELIC_APP_NAME = 'async await example';
process.env.NEW_RELIC_LICENSE_KEY = 'this is not a license';
var newrelic = require('newrelic');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');
var pkgPath = path.resolve(__dirname, './package.json');
// A simple async function to show transaction state loss.
var asyncAwait = newrelic.createBackgroundTransaction('asdf', async function() {
console.log('async', !!newrelic.agent.tracer.segment);
await fs.readFileAsync(pkgPath, 'utf8'); // <-- State lost here.
console.log('async', !!newrelic.agent.tracer.segment);
// Just to show pure promises keeps state.
promises();
});
// Same thing but just with promises.
var promises = newrelic.createBackgroundTransaction('asdf', function() {
console.log('promises', !!newrelic.agent.tracer.segment);
return fs.readFileAsync(pkgPath, 'utf8').then(() => {
console.log('promises', !!newrelic.agent.tracer.segment);
process.exit(0);
});
});
asyncAwait();