You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Change of behaviour regarding micro tasks and Nan::AsyncProgressWorkerBase #1024
I have noticed a significant change of behaviour that seems to have occurred between Node.js 26.3.0 and Node.js 26.4.0 when using Nan::AsyncProgressWorkerBase that does not call JavaScript in HandleOKCallback.
I have a manual Promise resolution using raw V8 calls that goes something like this:
voidMyWorker::HandleOKCallback() {
Nan::HandleScope scope;
auto context = Nan::GetCurrentContext();
// This comes from a persistent reference
v8::Local<v8::Promise::Resolver> resolver = Nan::New(*resolver_handle);
resolver->Resolve(context, this->ProduceRVal()).FromJust();
}
Since at least Node.js 12 and until Node.js 26.4.0 this code would result in the micro tasks queue being called after this function running the JS handlers of the promise.
Node.js 26.4.0 and later do not always call it, leaving the promise hanging in the air until something else calls it.
A simple fix is to simply call it manually, adding:
I do not have a minimal repro and it might be difficult to do it since there are many other reasons why the micro tasks might get executed. In my case it happens only with mocha.
I am not very sure if a module should be calling the micro tasks directly and whether this change was intended or not.
Hmm, I am not sure either, but if something has changed it is likely not in NAN. Can you reproduce the error without NAN? If the problem is upstream and unintentional it should probably be dealt with there. If it is intentional, I guess we would have to add a workaround to get consistent behavior.
This does not appear to be a NAN problem, although I haven't been able to reproduce it without NAN.
So the answer to my question is, you do not need to call manually the micro tasks queue. Manually running the micro tasks queue from the native module simply masks the problem.
Something that changed between Node.js 26.3.0 and Node.js 26.4.0 allows to return to the event loop with both Promise handlers and process.nextTick callbacks scheduled without executing them, there must be somewhere a path that allows this to happen.
Simply adding a
setInterval(()=>console.log('.'),10)`
to the effected program immediately solves the problem as the timer periodically runs the microtask queue.
At the moment I haven't been able to reproduce it without mocha and a NAN module. I will reopen this in Node.js once I have a minimal repro.
Created nodejs/node#66158 to track it and see if anyone else has encountered something similar, still working on it.
I managed to find a case which does not pass through the native module but loading the native module is still a requirement and I also have a couple of other requirements (mocha and loading tsx even if using JavaScript) that are needed to trigger it.
Very long time ago, during the pre-industrial era of Node.js, native add-ons did not have to run the micro tasks themselves
Somewhere in 2016, with the introduction of CallbackScope and makeCallback, native modules had to either use makeCallback (as opposed to simply call()) when calling JavaScript in order to ensure that micro tasks were being run, if the native handler was not calling JavaScript, it had to do this on its own
however in practice, things continued to work as before, because Node.js still did clear the micro tasks queue
All this means that the change of behaviour is very real and it is in fact intended.
I will leave it to you to decide on the course of action, but I suggest that you consider making CallbackScope a public interface since this is the official API for doing this.
In fact the node::CallbackScope class is already declared external and accessible, so maybe there is no need for a NAN layer over it, hopefully it won't change too much across versions.
Ah, this is great to know. Thank you. At least the documentation needs
updating, possibly some examples. Might still need to expose a wrapper just
for backwards compatibility, since it did not exist before.
Ok, there is still a problem. The nan::AsyncWorker class uses another class called nan::AsyncResource that handles the async resource as a black box. The persistentHandle is a protected member and can be accessed by derived classes, however the node::async_context is private inside node::AsyncResource.
Maybe nan::AsyncResource should have a node::CallbackScope factory method in order to keep the current design.
Then that might be related to the error of the async worker example failing
debug builds. A new factory method seems like a good choice, but how will
it work with the ancient versions that do not have it? There should be a
way of performing no operation in such case. Would you care to submit a
draft pull request to help see what is needed to make this as smooth as
possible?
node::CallbackScope is there since 2016 or 2017, there can be an ifdef for even older versions.
However I am afraid that nothing can be done for existing NAN modules - or at least I don't think that it is a very good idea to call automatically something that was not there before. NAN modules will have to be updated for Node.js 26. This remains a somewhat rarely used concept however, it affects only modules that resolve a Promise without calling JavaScript. It may also affect not very correct modules which call JavaScript without using makeCallback but this is an issue with a module itself.
Yes, but having to update for new versions of Node is fine and sometimes
unavoidable. The promise has always been backwards compatibility, not
forwards.
@kkoopa Let's see what will be the course of action of the Node.js team, because
Many node-addon-api modules will be impacted too - node-addon-api does include a wrapper for node::CallbackScope but I am absolutely convinced that most people do not use it since it was not needed until now. node-addon-api also has built-in Promise helpers and these could be impacted
Everyone who failed to notice the difference between Call and makeCallback will be impacted
This relatively rare use case in NAN actually comes last.
IMO, this is the most correct way to solve the problem - it correctly restores the async context. As there are many aspects of async_hooks that are not complete when it comes to Promises, it is not such a big deal at the moment - but should these be implemented, this will continue to work.
As a temporary solution I will ship my module with Promiseasync_hooks broken on Node.js 26 because I have the feeling that this will spark a significant debate.
Alright, let us wait and see. You are probably right that this issue might
have a very significant impact since nobody seems to have noticed for years.
Activity
kkoopa commentedon Sep 19, 2026
Hmm, I am not sure either, but if something has changed it is likely not in NAN. Can you reproduce the error without NAN? If the problem is upstream and unintentional it should probably be dealt with there. If it is intentional, I guess we would have to add a workaround to get consistent behavior.
mmomtchev commentedon Sep 19, 2026
This does not appear to be a NAN problem, although I haven't been able to reproduce it without NAN.
So the answer to my question is, you do not need to call manually the micro tasks queue. Manually running the micro tasks queue from the native module simply masks the problem.
Something that changed between Node.js 26.3.0 and Node.js 26.4.0 allows to return to the event loop with both Promise handlers and
process.nextTickcallbacks scheduled without executing them, there must be somewhere a path that allows this to happen.Simply adding a
to the effected program immediately solves the problem as the timer periodically runs the microtask queue.
At the moment I haven't been able to reproduce it without
mochaand a NAN module. I will reopen this in Node.js once I have a minimal repro.kkoopa commentedon Sep 19, 2026
mmomtchev commentedon Sep 20, 2026
Created nodejs/node#66158 to track it and see if anyone else has encountered something similar, still working on it.
I managed to find a case which does not pass through the native module but loading the native module is still a requirement and I also have a couple of other requirements (
mochaand loadingtsxeven if using JavaScript) that are needed to trigger it.mmomtchev commentedon Sep 22, 2026
@kkoopa to recap the situation:
CallbackScopeandmakeCallback, native modules had to either usemakeCallback(as opposed to simplycall()) when calling JavaScript in order to ensure that micro tasks were being run, if the native handler was not calling JavaScript, it had to do this on its ownAll this means that the change of behaviour is very real and it is in fact intended.
I will leave it to you to decide on the course of action, but I suggest that you consider making
CallbackScopea public interface since this is the official API for doing this.mmomtchev commentedon Sep 22, 2026
In fact the
node::CallbackScopeclass is already declared external and accessible, so maybe there is no need for a NAN layer over it, hopefully it won't change too much across versions.kkoopa commentedon Sep 22, 2026
mmomtchev commentedon Sep 22, 2026
Ok, there is still a problem. The
nan::AsyncWorkerclass uses another class callednan::AsyncResourcethat handles the async resource as a black box. ThepersistentHandleis aprotectedmember and can be accessed by derived classes, however thenode::async_contextisprivateinsidenode::AsyncResource.Maybe
nan::AsyncResourceshould have anode::CallbackScopefactory method in order to keep the current design.kkoopa commentedon Sep 22, 2026
mmomtchev commentedon Sep 22, 2026
node::CallbackScopeis there since 2016 or 2017, there can be anifdeffor even older versions.However I am afraid that nothing can be done for existing NAN modules - or at least I don't think that it is a very good idea to call automatically something that was not there before. NAN modules will have to be updated for Node.js 26. This remains a somewhat rarely used concept however, it affects only modules that resolve a
Promisewithout calling JavaScript. It may also affect not very correct modules which call JavaScript without usingmakeCallbackbut this is an issue with a module itself.kkoopa commentedon Sep 22, 2026
mmomtchev commentedon Sep 23, 2026
@kkoopa Let's see what will be the course of action of the Node.js team, because
node-addon-apimodules will be impacted too -node-addon-apidoes include a wrapper fornode::CallbackScopebut I am absolutely convinced that most people do not use it since it was not needed until now.node-addon-apialso has built-inPromisehelpers and these could be impactedCallandmakeCallbackwill be impactedThis relatively rare use case in NAN actually comes last.
IMO, this is the most correct way to solve the problem - it correctly restores the async context. As there are many aspects of
async_hooksthat are not complete when it comes toPromises, it is not such a big deal at the moment - but should these be implemented, this will continue to work.As a temporary solution I will ship my module with
Promiseasync_hooksbroken on Node.js 26 because I have the feeling that this will spark a significant debate.kkoopa commentedon Sep 23, 2026