Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add async_hooks integration for gRPC Server #169

Closed
wants to merge 2 commits into from

Conversation

kjin
Copy link
Contributor

@kjin kjin commented Feb 2, 2018

This change adds async_hooks integration when it's available (Node 8+), and aims to make almost no impact otherwise. A short explanation of which AsyncResource objects are created is at the top of server.js.

As the async_hooks docs are presently a little hard to digest, I'll try to summarize it here (please correct me if I am mistaken):

  • Synchronous continuations in JavaScript are separated from each other by asynchronous tasks
  • We sometimes want a way to establish links between these continuations, as typically a continuation "kicks off" a task that leads to another continuation:
    /*continuation a*/
    fs.readFile('my-file', (err, contents) => { /*continuation b, caused by a*/ })
  • For example, to support tracing we want to know the ancestry of each continuation to determine the amount of time spent on a single request (group by top-level parents)
  • async_hooks gives us that information by allowing us to add hooks for:
    1. when an asynchronous task is started (init)
    2. when we enter the continuation that follows it (before)
    3. when we leave that continuation (after)
    4. when the asynchronous task is finished (destroy)
  • It automatically does this for (almost) all asynchronous tasks in Node core, but modules relying on native components for asynchronous work (such as gRPC) need to implement this on their own
  • It allows us to do so with the AsyncResource API (an AsyncResource is an object associated with an asynchronous task) which:
    • when constructed, emits an init event
    • exposes emitBefore, emitAfter, and emitDestroy which corresponds to the ordered list above

@kjin kjin changed the title Add async_hooks integration Add async_hooks integration for gRPC Server Feb 2, 2018
wrap: function(fn) {
return function() {
if (resource) {
resource.emitBefore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I am changing this to be safer here: nodejs/node#18513. But this is ought to be fine for now.

if (resource) {
resource.emitBefore();
try {
var result = fn.apply(this, arguments);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: declare var at the top so that it doesn't seem to escape from the scope (even though that's how var is actually implemented).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, changing this to let/const -- I think language-level parity with the rest of the code shouldn't be too important right now.

@@ -567,14 +576,15 @@ ServerDuplexStream.prototype.waitForCancel = waitForCancel;
* @param {grpc.Metadata} metadata Metadata from the client
*/
function handleUnary(call, handler, metadata) {
var asyncResource = createAsyncResourceWrapper('GrpcServerRequest');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As suggested by the official docs, it would be best to use the module name as a prefix. Something like 'grpc.ServerRequest'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if (trailer) {
err.metadata = trailer;
asyncResource.wrap(function() {
handler.func(stream, function(err, value, trailer, flags) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use isn't making sense to me. handler.func seems to be getting executed in the same continuation as handleClientStreaming – it is called synchronously.

It doesn't seem logical to create an AsyncResource to wrap a synchronously executed function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to mirror what the other handler types do. I don't think there's harm in having nesting here (from the perspective of an agent listening for events with only one type of async resource, there is no concept of nesting) -- and I can't think of a good alternative without changing the behavior of the code to move the invocation handler.func to the next turn of the event loop. At the minimum we will definitely want a per-request grpc.ServerRequest-type async resource. The grpc.Server async resource doesn't seem strictly required at the moment but it follows the precedent set by http.

stream.waitForCancel();
handler.func(stream);
asyncResource.wrap(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

likewise.

@@ -598,8 +608,9 @@ function handleUnary(call, handler, metadata) {
} else {
sendUnaryResponse(call, value, handler.serialize, trailer, flags);
}
asyncResource.destroy();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on why it is okay that the destroy doesn't get called on all paths?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not okay 🙃 I've added additional calls to destroy if the user function is never called.

@kjin
Copy link
Contributor Author

kjin commented Apr 10, 2018

Closing this for now since #234 was landed and released. I've made a note to self to check what needs to be done in the JS layer, if any.

@kjin kjin closed this Apr 10, 2018
@lock lock bot locked as resolved and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants