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

Questions: async_hooks destroy callback vs setImmediate #13262

Closed
JiaLiPassion opened this issue May 28, 2017 · 6 comments · Fixed by #13369
Closed

Questions: async_hooks destroy callback vs setImmediate #13262

JiaLiPassion opened this issue May 28, 2017 · 6 comments · Fixed by #13369
Labels
async_hooks Issues and PRs related to the async hooks subsystem. confirmed-bug Issues with confirmed bugs. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.

Comments

@JiaLiPassion
Copy link
Contributor

  • Version:
    v8.0.0-nightly20170527f84666f923

  • Platform:
    Mac OS

  • Subsystem:

I just tried the new async_hooks callback API, I want to confirm the behaviors below is correct or not?

const async_hooks = require('async_hooks');

function init(id, provider, parentId, parentHandle) {
    process._rawDebug('init ', id, provider);
}

function before(id) { 
    process._rawDebug('before', id);
 }
function after(id) { 
    process._rawDebug('after', id);
 }
function destroy(id) {
    process._rawDebug('destroy', id);
}

async_hooks.createHook({init, before, after, destroy}).enable();

const timerId1 = setImmediate(() => {
    process._rawDebug('setImmediate');
});
clearImmediate(timerId1);

It is just a simple setImmediate and it was clear by clearImmediate.
I expect the result will be

 init  2 Immediate
destroy 2

but the result is

 init  2 Immediate

only.

If I add some other setTimeout logic in the test program, the destroy will be called.

const async_hooks = require('async_hooks');

function init(id, provider, parentId, parentHandle) {
    process._rawDebug('init ', id, provider, parentId, parentHandle);
}

function before(id) { 
    process._rawDebug('before', id);
 }
function after(id) { 
    process._rawDebug('after', id);
 }
function destroy(id) {
    process._rawDebug('destroy', id);
}

async_hooks.createHook({init, before, after, destroy}).enable();

const timerId1 = setImmediate(() => {
    process._rawDebug('setImmediate');
});
clearImmediate(timerId1);

const timerId = setTimeout(() => {
    process._rawDebug('settimeout');
}, 100);
clearTimeout(timerId);

the result will look like

init  2 Immediate
init  3 Timeout
init  4 TIMERWRAP
destroy 2
destroy 3

Is this the correct behaviors? And how to ensure the destroy callback is called ?

Thanks a lot!

@addaleax addaleax added async_hooks Issues and PRs related to the async hooks subsystem. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. confirmed-bug Issues with confirmed bugs. labels May 28, 2017
@addaleax
Copy link
Member

Thanks for this report! I suspect this has something to do with the destroy callback being called asynchronously (we have to do that, unfortunately), and the loop exiting too early for that callback to be run.

@JiaLiPassion
Copy link
Contributor Author

@addaleax , Thank you for reply, got it! I will continue to test other async APIs.

@AndreasMadsen
Copy link
Member

AndreasMadsen commented May 28, 2017

I suspect this has something to do with the destroy callback being called asynchronously (we have to do that, unfortunately), and the loop exiting too early for that callback to be run.

Indeed, we can't always guarantee the destroy event to be emitted during process termination, but since clearImmediate directly indicates a destroy it should be possible in this case.

The emit is queued using uv_idle_start here https://github.com/nodejs/node/blob/master/src/async-wrap.cc#L177

I guess we can check the env->destroy_ids_list() list when validating that there is no more to do.

@AndreasMadsen
Copy link
Member

Looking at how setImmediate works (basically we need setImmediate(drainDestroyQueue)) I think we should use uv_check_start instead of uv_idle_start.

@Fishrock123
Copy link
Contributor

Fishrock123 commented May 29, 2017

This seems like a simple bug: destroy() should be emitted from clearImmediate(). Similar to timers, although it is in unenroll() for timers.

@AndreasMadsen
Copy link
Member

AndreasMadsen commented May 29, 2017

@Fishrock123 it already is "emitted" in clearImmediate using emitDestroy similarly to unenroll. But emitDestroy doesn't emit until the next event loop iteration because it uses uv_idle_start. Since the event queue is empty the next event loop iteration never happens.

addaleax added a commit to addaleax/node that referenced this issue May 29, 2017
Run destroy callbacks before a normal application exit happens.

Fixes: nodejs#13262
trevnorris added a commit to trevnorris/node that referenced this issue Jun 2, 2017
Calling the destroy callbacks in a uv_idle_t causes a timing issue where
if a handle or request is closed then the class isn't deleted until
uv_close() callbacks are called (which happens after the poll phase).
This results in some destroy callbacks not being called just before the
application exits. So instead switch the destroy callbacks to be called
in a uv_timer_t with the timeout set to zero.

When uv_run() is called with UV_RUN_ONCE the final operation of the
event loop is to process all remaining timers. By setting the timeout to
zero it results in the destroy callbacks being processed after
uv_close() but before uv_run() returned. Processing the destroyed ids
that were previously missed.

Also, process the destroy_ids_list() in a do {} while() loop that makes
sure the vector is empty before returning. Which also makes running
clear() unnecessary.

Fixes: nodejs#13262
PR-URL: nodejs#13369
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
trevnorris pushed a commit to trevnorris/node that referenced this issue Jun 2, 2017
Verify that the destroy callback for a TCP server is called before exit
if it is closed in another destroy callback.

Fixes: nodejs#13262
PR-URL: nodejs#13369
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
jasnell pushed a commit that referenced this issue Jun 5, 2017
Calling the destroy callbacks in a uv_idle_t causes a timing issue where
if a handle or request is closed then the class isn't deleted until
uv_close() callbacks are called (which happens after the poll phase).
This results in some destroy callbacks not being called just before the
application exits. So instead switch the destroy callbacks to be called
in a uv_timer_t with the timeout set to zero.

When uv_run() is called with UV_RUN_ONCE the final operation of the
event loop is to process all remaining timers. By setting the timeout to
zero it results in the destroy callbacks being processed after
uv_close() but before uv_run() returned. Processing the destroyed ids
that were previously missed.

Also, process the destroy_ids_list() in a do {} while() loop that makes
sure the vector is empty before returning. Which also makes running
clear() unnecessary.

Fixes: #13262
PR-URL: #13369
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
jasnell pushed a commit that referenced this issue Jun 5, 2017
Verify that the destroy callback for a TCP server is called before exit
if it is closed in another destroy callback.

Fixes: #13262
PR-URL: #13369
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Andreas Madsen <amwebdk@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
async_hooks Issues and PRs related to the async hooks subsystem. confirmed-bug Issues with confirmed bugs. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
4 participants