I maintain a c++ module deasync (originally written by @vkurchatkin) which essentially exposes uv_run to jsLand in order to address some use cases that cannot be handled by other async-2-sync modules. A problem was reported recently that when deasync is involved in nested setTimeout calls with same timeout value, the process hung. It is illustrated by code
var deasync = require('deasync');
function async(cb) {
console.log('x');
setTimeout(function () {
console.log('y');
cb(null, 'value');
console.log('z');
}, 8);
}
setTimeout(function () {
console.log('A', deasync(async)());
}, 8); // changing 8 to, say 9, works
output
Changing one of the timeout values of setTimeout yields expected output
Problem is reproducible in latest Node version on at least Linux and Windows. I have run node in gdb session and found the problem is loop->active_handles has gone down to 0 after the inner setTimeout is called but before its handler is triggered, therefore the inner handler doesn't have a chance to run.
I think the cause is that timers of same timeout value are implemented using a linked list and only one handle is exposed to libuv. This handle is removed at the time the outer setTimeout handler is called.
By moving list.start(msecs, 0) in /lib/timers.js outside else block, I have verified the problem can be fixed. Will supply a PR later. What I am not clear is the impact of this change. Appreciate if someone can review the issue and my PR.
(P.S. I have read Node issue reporting guidelines and understand I am not supposed include dependencies. But deasync is simple, has no dependency by itself, and is easy to be re-factored into a dependency-less mixture of c++ and js code)