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();
--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
---
You received this message because you are subscribed to the Google Groups
"v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.
diff --git a/deps/v8/src/js/async-await.js b/deps/v8/src/js/async-await.js
index b733f3d9fa..88883157e4 100644
--- a/deps/v8/src/js/async-await.js
+++ b/deps/v8/src/js/async-await.js
@@ -91,27 +91,15 @@ function AsyncFunctionAwait(generator, awaited, outerPromise) {
return;
}
- // Just forwarding the exception, so no debugEvent for throwawayCapability
- var throwawayCapability = NewPromiseCapability(GlobalPromise, false);
-
- // The Promise will be thrown away and not handled, but it shouldn't trigger
- // unhandled reject events as its work is done
- SET_PRIVATE(throwawayCapability.promise, promiseHasHandlerSymbol, true);
-
if (DEBUG_IS_ACTIVE) {
if (IsPromise(awaited)) {
// Mark the reject handler callback to be a forwarding edge, rather
// than a meaningful catch handler
SET_PRIVATE(onRejected, promiseForwardingHandlerSymbol, true);
}
-
- // Mark the dependency to outerPromise in case the throwaway Promise is
- // found on the Promise stack
- SET_PRIVATE(throwawayCapability.promise, promiseHandledBySymbol,
- outerPromise);
}
- PerformPromiseThen(promise, onFulfilled, onRejected, throwawayCapability);
+ promise.then(onFulfilled, onRejected);
}
// Called by the parser from the desugaring of 'await' when catch