Project: v8 Issues People Development process History Sign in
New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.
Starred by 10 users
Status: Fixed
Closed: Jan 2017
HW: ----
OS: ----
Priority: ----
Type: FeatureRequest

Sign in to add a comment
MicrotaskQueue lacking introspection APIs
Reported by, Dec 23 2015 Back to list
Version: 4.9.99
OS: All
Architecture: All

Currently when running v8::Isolate::RunMicrotasks() it is impossible to distinguish one call from another. At times it may be necessary to work with an individual callback, such as create/remove application state for an individual callback.

It would be helpful to have an API like v8::Isolate::NextMicrotask() to allow users to manually run microtasks.
Comment 1 by, Jan 4 2016
Components: API
Labels: Type-FeatureRequest
Status: Available
In addition it is necessary that a public API be made available that notifies the user when an executor, resolve and reject callbacks be made. Then also necessary to replace said function with another, which would allow us to wrap the returned function and propagate information with the call.
Comment 3 by, Feb 2 2016
Regarding #2, would the Zones proposal (discussed at the most recent TC39 meeting) perhaps be related to this concern?
Comment 4 by, Mar 15 2016
Status: Assigned
Zones is related, but a far future idea. The underlying hooks to be able to have more granular control of the microtask queue, however, are needed in shorter term for the async_wrap functionality in node to properly be able to track *all* async exit and reentry.

We have most things already covered to an extent that async_wrap can be used to construct a nested context tree which allows async continuation to indirectly receive data from prior code (which is required for many things, like application performance monitoring). However, any time a native promise is used currently, that branch of the continuation tree is broken, losing the context. Thus, the need to wrap resolve/reject to bind the reentry to the context at the initial calling point.
We are looking into how to provide some kind of feature to meet requirements for tracking/controlling Promises. There are other use cases outside of Node.js for this feature. For example Angular currently patches Promises to track microtasks to figure out when to render, and would need some way to recover the ability when async/await is added. I hope a single API for both Node and the web, standardized at TC39, could provide a solution for these use cases. I don't see much room for a "short-term" fix--whatever we export may have to be maintained for a long time, so I think we should think carefully about what we're exposing.

We're trying to figure out exactly what the requirements are. For async_wrap, is the main use case application performance monitoring, or are there other additional important use cases? What sorts of callbacks would you expect to construct this context chain? Zones are intended specifically to track context across asynchronous callbacks; what would you need from them to do this? It would be very useful to understand the design space that would meet your needs.
There are 3 key events async_wrap needs to know about.
1) When it's created (i.e. then(), or maybe when the executor is called. still discussing this)
2) Just before the resolve/reject callback is called
3) Just after the resolve/reject callback is called

async_wrap also needs to be able to propagate at least minimal state. A single number as a unique id would do.

A fourth callback async_wrap currently supports is to be able to see when a resource is released. Many resources are manually free'd, and the rest are tracked with weak persistent handles.

The fundamental purpose of async_wrap is to allow users to analyze the asynchronous life cycle of the application at the level of system interaction. Not from that of the abstracted JS API. The majority of calls to (2) and (3) above will occur before entering and after leaving the JS stack.

async_wrap only uses a single unique id to track "ownership" of asynchronous resources, but this can be used very effectively by the user to do more advanced context tracking. This is where the fourth callback above is important. It's easy to keep a Map of values that you want to propagate, and delete those values once you're notified the associated resource has been cleaned up.

I've seen at least one use case where the fourth callback was used to create a resource tree. Something like so:

       ╱ ╲
      B   C
     ╱ ╲
    D   E

Now say that both B and C are free'd, the tree would be updated to:

       ╱ ╲
      D   E

So we can see that while B has been free'd, the resources it created (D and E) are still alive.

Performance tracking happens at a different level. For example, it will notify the user immediately when a TCP packet has been received, not when the JS APIs 'data' event fires. Or when a new TCP connection is constructed, not when the 'connection' event fires. It's important for users to see when the actual event occurs, and be able to measure the difference between that and when it can actually be processed. It also allows the user to easily see things like whether a write could be flushed to the kernel immediately.

That covers the basics, but because of how general the API is users are coming up with new ways to analyze the state of their applications.
Thanks for these useful details, trev.norris. We're working to figure out the right solution here, and this information is a big help. Do you have a strawman idea for what sort of function signatures would be sufficient for Node's use case here, to hook into Promises?
Thanks for getting back with me.

The first things needed are to be notified when a callback is added to the MicrotaskQueue and to be able to propagate a value (i.e. Local<Value> or void*) with that callback. Possibly something like so:

typedef Local<Value> (*MicrotasksEnqueuedCallback)();

void Isolate::AddMicrotasksEnqueuedCallback(MicrotasksEnqueuedCallback callback);

So the return value of MicrotasksEnqueuedCallback propagates with the callback. Then instead of using RunMicrotasks() to automatically run them allow each callback to be retrieved individually:

Local<Function> Isolate::RetrieveMicrotask();

Local<Function> callback = isolate->RetrieveMicrotask();
Local<Value> data_v = callback.Data();
void* data = data_v.As<External>()->Value();

callback->Call(context, object, 0, nullptr).ToLocalChecked();

This does have the obvious flaw of not allowing two APIs to wrap the call of a microtask callback, but I think this demonstrates the conceptual need. There's definitely more than one way this API could go that would work for us.
Hi littledan, what are your latest thoughts on this and hooks into Promises?

Would you like any more information or use cases?

There is high priority work being done to improve Node's APM and context handling over async boundaries that would greatly benefit from this.

Thank you!
Thanks for the information trev.norris and jlewis for the ping. 

I'm curious about how the API specced in will be used by async_wrap. Specifically in relation to the four requirements mentioned in Can you tell me a bit more about how these microtaskQ hooks will work with 'init' and 'destroy' async_wrap callbacks?

For example, calling the executor would not trigger a microtaskQ callback at all so this API wouldn't give you the "init" callback hook needed for async_wrap. There could be cases where calling then() not hitting the microtaskQ immediately. Similarly, the 'delete' async_wrap callback would need hooks in Promises and not microtaskQ if I'm reading this correctly. Is node planning to patch Promises to provide these hooks? 

Also, I assume by "resource" in, you mean the promise and not the microtaskQ callback? Because you wouldn't be able to associate the resole/reject handlers and "then" handler to the same id, unless I'm missing something.
For APM tracing purpose, the expected behaviour is to trigger "init" when the "resolve" or "reject" function is called within the promise executor function given to the constructor. The "before" and "after" should simply occur before and after running the callback to a then() continuation. Not too sure what to expect from async/await. Lastly, the "destroy" callback should be triggered when the resource is GCed/freed.

I don't think the promise vs microtask distinction matters from Node.js perspective, but perhaps Trevor has further thoughts on that.
Sorry for the delayed response. I'm going to step back and walk through an example. Is easier for me to follow. Take the following:

setTimeout(function timeout01() {
  cont p = new Promise(function promise01(res) {
    setTimeout(function timeout02() {

A Promise executor runs immediately, so when the Timer handle created by setTimeout() is called, thus calling the init() callback, the correct stack trace can be read. The call to promise01() is just another call in the call stack. i.e. a Promise executor is nothing special.

The callback timeout02() is executed between the Timer's before()/after() AsyncWrap hooks. At which time the resolver is called. Can I assume that the microtaskQ is always called when either the resolve/reject callbacks are called?

Continuing the example:

p.then(function then01(val) {
  return 'bar';

p.then(function then02(val) {
  return 'baz';

Thus far I've mentioned we need to be notified when then() is called. Though while writing this I realized it's not actually the case. The mock async stacks below demonstrate this.

Here's the breakdown of how I'd expect events to fire using the AsyncWrap API. (line breaks indicate a continuation in event loop)

- timeout01 - before
- timeout02 - init
- timeout01 - after

- timeout02 - before
- res       - init
- timeout02 - after

- res       - before
- (call then01)
- res       - after
- res       - before
- (call then02)
- res       - after

This makes the assumption that the call to resolve/reject immediately calls the microtaskQ. If this is the case then the necessary state can be collected, stored and finally restored before the then() callbacks are called.

So to revise the native API. Possibly something like so:

// Making this a v8::Value b/c not sure if it would be an actual v8::Promise.
typedef void* (*ResolveOrRejectCallback)(v8::Local<v8::Value> value);

v8::Isolate::OnResolveOrReject(ResolveOrRejectCallback cb);

Point of the above is to allow us to attach a pointer to the value placed in the microtaskQ to be retrieved later. Then maybe something like so to use that pointer.

// fr_cb is the onFullfill or onRejected callback that are passed to the then().
typedef v8::Local<v8::Value> (*RunMicrotaskCallback)(v8::Local<v8::Function> fr_cb, v8::Local<v8::Value> arg, void* data);

v8::Isolate::ProcessMicrotaskQueue(RunMicrotaskCallback cb);

All of this could be used similar to the following:

void* PropagateState(v8::Local<v8::Value> value) {
  // Have the constructor automatically call init().
  node::AsyncState* state = new node::AsyncState(node::NewAsyncUid());
  // Here we can use a weak Persistent to manage the destroy() callback.
  return state;


// Return the same value that's returned by the callback. Though possibly that
// can be automatically.
v8::Local<v8::Value> RunMQCallback(v8::Local<v8::Function> cb, v8::Local<v8::Value> arg, void* data) {
  node::AsyncState* state = static_cast<node::AsyncState*>(data);
  v8::HandleScope scope(state->isolate());
  v8::Local<v8::Value> ret = cb->Call(state->context(), &arg, 1).ToLocalChecked();
  return ret;

// The callback is called for every item in the microtaskQ

This still has the issue of not allowing the microtaskQ to be wrapped multiple times. The above shows a simplified version of how AsyncWrap would use the API.

My apologies for the long post, but I believe this one covers what we'd need and explanations as to why.
Clarification to make with the following API:

typedef void* (*ResolveOrRejectCallback)(v8::Local<v8::Value> value);

I intended the v8::Local<v8::Value> argument to represent the Promise whose that called the resolve/reject callback. Not the fullfilled/rejected value.
Trevor, are you implying that the then() callbacks with statically resolvable return values should not produce an init for further nested promise chaining? Or did you just omit those details in the (call thenX) bits?

I think I would expect the return of any then() callback to always produce an init from the return value and just potentially not reach before/after events if no further then() chaining occurs. Not sure when destroy should occur there; GC time?
That case is covered by my explanation. Though it is painful and I was hoping there would be feedback about how we could make it less so.

Take the following:

Promise.resolve(42).then(function then01(val) {
  return val * 2;
}).then(function then02(val) {
  console.log(val);  // Should be 84

I assume that internally the return statement of the onFulfilled/onRejected is functionally equivalent to an executor's resolve callback being called. So technically this is the point where the init() should be triggered, but if there are any V8 optimizations that would automatically suppress calling init() by detecting that there is nothing that could attach itself to the Promise in the future that would be excellent and I personally don't believe it would affect functionality.

There's also the case, when returning a Promise, where calling init() for the .then()'s return isn't helpful, and would be useful if we could automatically not call the init() for that case. For example:

  .then(function then01(val) {
    return new Promise(function p01(res) { setTimeout(res, 100, val) });
  .then(function then02(val) {
    return Promise.resolve(val * 2);
  .then(function then03(val) {

Here are the points where init() would be called, and the order in which they'd be called:

- resolve(1)
- setTimeout()
- then01() return
- resolve(val * 2)
- then02() return
- then03() return

It would be helpful to the user if we could somehow omit the .then()'s automatic resolve call when a Promise is being returned. Because we can know that some other call will be make from the new Promise's executor that will propagate the necessary state.

There is another note I forgot to mention. That is in the case of a thrown exception. Where it's the equivalent of calling reject. Meaning it would  be necessary to call init() after a throw. I only bring this up because this case also triggers another V8 API that node uses to determine whether a rejected Promise has gone unhandled, which also fires immediately when reject is either called, or an exception is thrown.
Thanks trevor that helps a lot!

I agree with the various hooks you've mentioned, and I like the
idea of setting up a callback to notify Node about the various

A couple of problems with the mentioned API --

-- I don't see a way to associate the parent promise with a newly
   created promise during PromiseThen.

-- This API would involve instrumenting both native promises (by
   adding resolve/reject hooks) and microtask queue(by exposing it)
   instead of being contained within just promises.

-- The microtask queue could contain non promise related
   callbacks, so you'd have to do additional processing/checking
   before running the asyncwrap callback.

-- Exposing the microtask queue could prevent future performance
   optimizations (like rewriting it in lower level compiler code

Here's a much simpler API that modifies your suggestion to
account for the above mentioned problems --

I'd appreciate your thoughts on this API and see if there's
something missing.
Allow me to clarify by example. Below there will be a few examples. Each one to highlight the order in which the init()/before()/after() callbacks would be executed.

1  const p = new Promise(res => {
2    res(42);
3  });
5  p.then(val => {
6    console.log(val);
7  });

init   - promise-01 (line 2)
before - promise-01 (line 6)
init   - promise-02 (line 7)
after  - promise-01 (line 7)

The above highlights that the call to new Promise() on line 1 does not trigger any callback because a Promise executor is executed synchronously. The user should be able to call Error.captureStackTrace() from every init() call and generate the full long stack trace without any stack overlap. Triggering init() from new Promise() would cause such overlap. Additionally any stack between new Promise() and calling the resolver would be lost.

The next example lead me to find a conflict in my previous explanation of execution timing for before()/after() calls, and I'd like some discussion on the best way to resolve this.

 1  const p = new Promise(function promise_01(resolve) {
 2    setTimeout(function timer_01() {
 3      resolve(42);
 4    }, 200);
 5  });
 7  setTimeout(function timer_02() {
 8    p.then(function promise_02(val) {
 9      console.log(val);
10    });
11  }, 100);

init   - timer_01   (line 2)
init   - timer_02   (line 7)

before - timer_02   (line 7)
init   - promise_02 (line 8) (treating like nextTick())
after  - timer_02   (line 11)

before - timer_01   (line 2)
init   - promise_01 (line 3)
after  - timer_01   (line 4)

... here's the conflict of when promise_01 and promise_02's
    before()/after() callbacks run ...

While I'd prefer to treat the call to .then() similar to that of an event emitter's .on(), it does in fact flag an asynchronous event. Unfortunately it's not as simple as treating it like a call to process.nextTick() because of the necessary init() call when resolve() executed in order to collect the full stack trace. This leaves us in a position where when the callback promise_02() is called there are now two sets of before()/after() calls to be made.

I find the following to be technically correct, but it is also ugly.

before - promise_01 (line 8)
before - promise_02 (line 8)
... would then() implicitly need to call init()?
after  - promise_02 (line 10)
after  - promise_01 (line 10)

Thoughts on a better solution?

Side note: For purely cosmetic purposes I was convinced to change the callbacks pre()/post() to before()/after(). The argument was so that the name was more indicative of when they're called.
I'd like to share info about API that DevTools is using for async stacks.

In inspector (part of DevTools backend in V8) we use Async Task API [1]:
* asyncTaskScheduled(id, recurring) - where task was scheduled (call to setTimeout or call to promise resolver).
* asyncTaskStarted(id) - when microtask for this task was started.
* asyncTaskFinished(id) - when microtask was finished.
* asyncTaskCanceled(id) - when microtask was canceled.
We call this methods on event received from V8 in V8DebugEventListener [2]. Async events have v8::AsyncTaskEvent type and contains object with type, name and id fields. "enqueue" event is fired on promise resolve, "enqueueRecurring" is fired for async functions, "willHandle" - before microtask, "didHandle" - after, "cancel" - on exit from async function. DevTools event processing is located in [3].

Thanks Trevor, and Alexey!

Trevor --
1) I wonder if the asyncTaskScheduled hook can be used for long stacktrace tracking and keep the current semantics for the init hook? +Matt had some additional thoughts on the init hook semantics.

2) The before()/after() are only called /once/ per promise. The current design doesn't call before/after for promises that are not created with a PromiseThen. I've updated the doc to be more explicit about this with an example and rationale. So in your example, only the before/after of the promise created by p.then() will be called. 

I'm ok with changing the callbacks from pre/post to before/after.

Alexey, thanks for the information. I'd like to clarify the stack traces that DevTools is able to pull from this. Here's an example (done for the browser b/c node doesn't properly hook for async stack traces):

 1  let p = null;
 3  setTimeout(function s1() {
 4    p = new Promise(function p1(res) {
 5      setTimeout(function s3() {
 6        res(1);
 7      }, 10);
 8    });
 9  }, 10);
11  setTimeout(function s2() {
12    p.then(function r1(val) {
13      console.log(val);
14    });
15  }, 100);

DevTools shows me the async stack of:

r1 (:12)
s2 (:11)

One key utility of async hooks is that two IDs are given on every init() call. First is the ID of the execution context in which the operation is taking place (referred to as the currentId). Second is the ID that caused the asynchronous operation to exist (referred to as the parentId). This parentId is needed because many operations are performed outside of a JavaScript stack. Leaving a zero length stack from init().

The parentId can also be used to continue an async stack trace and reach the code that is the reason for the init() callback being triggered. Combining both the parentId with the currentId, the above example would give us the following stack trace:

r1  (:12)
-- break and use stack from parentId --
res (:6)
s3  (:5)
p1  (:4)
s1  (:3)

Problem here is that calling .then() doesn't trigger any event. So no information can be collected. It presents a unique scenario from everything else that exists in node. It's like EventEmitter, except it fires asynchronously. It's like process.nextTick(), except it doesn't trigger init().

For any other API in node I'd say it should be possible to transverse from the point of the callback back to the point that caused the callback to be called. In the case of the example above, the call to res() is what triggered r1() to fire, but there's no way to figure that out.

Am I missing something or looking at this from the wrong perspective?


> I wonder if the asyncTaskScheduled hook can be used for long stacktrace tracking and keep the current semantics for the init hook?

I think mostly, yes. Though I do have the one question above.

> The before()/after() are only called /once/ per promise.

I'm probably misunderstanding what you're saying, but my understanding was that the before()/after() callbacks would fire for every resolver. e.g.

 1  const p = Promise.resolve(1);
 2  p.then(val => console.log('first .then()');
 3  p.then(val => console.log('second .then()');

Is my assumption incorrect?

> The current design doesn't call before/after for promises that are not created with a PromiseThen.

Sounds good. Thanks for your work on that doc.
I believe the above code will result in the following set of calls:

init, 1 (setTimeout s1)
init, 2 (setTimeout s2)
before, 1 (setTimeout s1)
init, 3 (promise p)
init, 4 (setTimeout s3)
after, 1 (setTimeout s1)
before, 4 (setTimeout s3)
after, 4 (setTimeout s3)
before, 2 (setTimeout s2)
init, 5, 3 (promise p.then)
after, 2 (setTimeout s2)
before 5 (promise p.then)
console.log(val) // => 1
after 5 (promise p.then)

For the async stack traces problem above, it should be possible to connect the execution of r1 to the stack of s1. The line `init, 5, 3 (promise p.then)` provides the parent relationship between the async chain in s1 and the async chain in r1. As long as the current context is maintained on the async-hooks side, both current and parent contexts should be available for the .then call.

> my understanding was that the before()/after() callbacks would fire for every resolver.

  Each resolver will result in a new set of calls to init/before/after. In your example, there would never be a before/after call for p since no asynchronous work is done until the first .then call.
Had a hangout with Matt and Sathya to discuss whether the current API was sufficient. After clarifying the specific needs of async hooks it was realized the existing V8 debug API would provide the additional information necessary, and that simply monitoring calls via the debug API won't have noticeable overhead. So through the combination of the two we can retrieve all the information we need.

Example to walk through my understanding of the current proposal.

 1  function foo() {
 2    const p = new Promise(function promise1(res) {
 3      setTimeout(function st1() {
 4        res();
 5      });
 6    });
 7    setTimeout(function st2() {
 8      p.then(function then1() {
 9        /* gather async information here */
10      });
11    }, 10);
12  }

From the document "Promise lifecycle hooks in V8" [1] the enum PromiseHookType specifies that the callback PromiseHook will be called for the init/before/after events async hooks is expecting. Thus we would know when both `new Promise` and its executor `promise1` execute to the effect:

`new Promise` (line 2) would trigger the PromiseHook with PromiseHookType::kInit. The executor would be called between calls of PromiseHookType::kBefore and PromiseHookType::kAfter (at the time of this writing they're kPre/kPost, but may change :). This would allow us to track the promise's id as the currentId of setTimeout.

The same would occur with `p.then()` (line 8) with the addition of `parentPromise` being passed as the third argument. Which would point to the promise returned by `new Promise` (line 2). Giving us the async graph's inflection point between the `.then()` and the executor. Which will be used to link additional relationships together (explained below).

If we want to write out a long stack trace between `res()` (line 4) and `then1()` (line 8) we would need to further use the V8 debug API. Which would be called when `res()` is executed and at which time a stack trace and the currentId/triggerId can be gathered. Keeping a tree of id relationships, we would transverse up from `then1()` to `new Promise`. From that point there should only be one branch of the tree that executed `res()`, and will contain the stack trace we're looking for. So not as straight forward as linking long stack traces using AsyncWrap, but the same result can be achieved.

LGTM, as far as my understanding is correct.

Project Member Comment 25 by, Dec 9 2016
The following revision refers to this bug:

commit d778b36f0c98934f6dd1046ee20e04d60dfac212
Author: gsathya <>
Date: Fri Dec 09 06:56:43 2016

[promisehook] Add is_promisehook_enabled

This will be used in CSA to check if any promisehook is set.

-- Adds a is_promisehook_enabled_ field to the isolate and helper methods.
-- Adds this field to the ExternalReference table.
-- Adds a helper method to access this from CSA

Note -- this patch doesn't actually add the ability to attach the hook

BUG= v8:4643 

Cr-Commit-Position: refs/heads/master@{#41607}


Project Member Comment 26 by, Dec 15 2016
The following revision refers to this bug:

commit b4aadaec1e46a98f2c2ee273c99aa808e19c770f
Author: gsathya <>
Date: Thu Dec 15 15:50:54 2016

[promisehook] Store promise in PromiseReactionJob

This will be used in PromiseHook.

BUG= v8:4643 

Cr-Commit-Position: refs/heads/master@{#41730}


Project Member Comment 27 by, Dec 16 2016
The following revision refers to this bug:

commit b1c148b91f84d5825c9a52688e71b08fe2be2958
Author: gsathya <>
Date: Fri Dec 16 19:17:16 2016

[promisehook] Implement PromiseHook

This adds kInit, kResolve, kBefore and kAfter lifecycle hooks to promises.

This also exposes an API to set the PromiseHook.

BUG= v8:4643 

Cr-Commit-Position: refs/heads/master@{#41775}


Project Member Comment 28 by, Dec 28 2016
The following revision refers to this bug:

commit df179704ffe34d6c49cffbec24fa7ba43090bfc1
Author: gsathya <>
Date: Wed Dec 28 19:31:24 2016

[promisehook] Fire init hook for promise subclass

Add test as well.
Add regression test for passing uninitialized promises to init hook

BUG= v8:4643 

Cr-Commit-Position: refs/heads/master@{#41982}


Status: Fixed
Project Member Comment 30 by, Aug 10
The following revision refers to this bug:

commit c19547f690ea024000a99a4c52c6afc2d437ee4d
Author: Sathya Gunasekaran <>
Date: Thu Aug 10 23:01:24 2017

[api] Remove experimental tag for PromiseHooks

Bug:  v8:4643 
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_chromium_rel_ng
Change-Id: Id82365d20830e5efd33a8d066f5aab4b999807d7
Reviewed-by: Adam Klein <>
Commit-Queue: Sathya Gunasekaran <>
Cr-Commit-Position: refs/heads/master@{#47297}

Sign in to add a comment