Description
The original issue is at #1264
The synopsis is that because of how uv_run
works, unrefed handles show up as active still for the first run, and don't appear as no-longer active until the second run.
In the past, running an unrefed timer in beforeExit
would infinitely loop, since the second run would always be in the next beforeExit
, consequentially calling 'beforeExit'
again and scheduling another time, looping infinitely. This was fixed in #3407 by making unrefed timers of the same timeout use the previous handle, which is then properly unreferenced.
As described, one would expect the event loop / beforeExit code to look something like so:
bool more;
uv_run_mode run_mode = UV_RUN_ONCE;
do {
more = uv_run(env->event_loop(), run_mode);
if (more == false) {
EmitBeforeExit(env);
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env->event_loop());
run_mode = UV_RUN_NOWAIT;
} else {
run_mode = UV_RUN_ONCE;
}
} while (more == true);
However in reality it looks more like this:
(
Lines 4063 to 4078 in 471aa5a
bool more;
do {
more = uv_run(env->event_loop(), UV_RUN_ONCE);
if (more == false) {
EmitBeforeExit(env);
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env->event_loop());
if (uv_run(env->event_loop(), UV_RUN_NOWAIT) != 0)
more = true;
}
} while (more == true);
If you look closely at the actual version, you'll notice that uv_run
ends up actually being called at least 2 times on a beforeExit
re-entry anyways, which logically register the timer and unref. However it does not seem to work like that.
Sniff test says it may be some discrepancy within uv_run
modes?
cc @indutny, @trevnorris, @saghul