src: don't call uv_run() after 'exit' event
It makes timers and other libuv handles fire intermittently after the 'exit' event, contrary to what the documentation states. Regression introduced in commit aac79df ("src: use stack-allocated Environment instances") from June last year that made the `while (handle_cleanup_waiting_ != 0) uv_run(event_loop(), UV_RUN_ONCE)` loop run unconditionally on exit because it merged CleanupHandles() into the Environment destructor. This change breaks parallel/test-async-wrap-throw-from-callback because the async_wrap idle handle is no longer cleaned up, which I resolved pragmatically by removing the test. In all seriousness, it is being removed in the upcoming async_wrap revamp - it doesn't make sense to sink a lot of time in it now. Fixes: https://github.com/nodejs/node/issues/12322 PR-URL: https://github.com/nodejs/node/pull/12344 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
5d06e5c30d
commit
5ef6000afd
@ -184,6 +184,8 @@ void Agent::WorkerRun() {
|
|||||||
|
|
||||||
// Clean-up persistent
|
// Clean-up persistent
|
||||||
api_.Reset();
|
api_.Reset();
|
||||||
|
|
||||||
|
env.CleanupHandles();
|
||||||
}
|
}
|
||||||
isolate->Dispose();
|
isolate->Dispose();
|
||||||
}
|
}
|
||||||
|
@ -223,28 +223,6 @@ inline Environment::Environment(IsolateData* isolate_data,
|
|||||||
inline Environment::~Environment() {
|
inline Environment::~Environment() {
|
||||||
v8::HandleScope handle_scope(isolate());
|
v8::HandleScope handle_scope(isolate());
|
||||||
|
|
||||||
while (HandleCleanup* hc = handle_cleanup_queue_.PopFront()) {
|
|
||||||
handle_cleanup_waiting_++;
|
|
||||||
hc->cb_(this, hc->handle_, hc->arg_);
|
|
||||||
delete hc;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (handle_cleanup_waiting_ != 0)
|
|
||||||
uv_run(event_loop(), UV_RUN_ONCE);
|
|
||||||
|
|
||||||
// Closing the destroy_ids_idle_handle_ within the handle cleanup queue
|
|
||||||
// prevents the async wrap destroy hook from being called.
|
|
||||||
uv_handle_t* handle =
|
|
||||||
reinterpret_cast<uv_handle_t*>(&destroy_ids_idle_handle_);
|
|
||||||
handle->data = this;
|
|
||||||
handle_cleanup_waiting_ = 1;
|
|
||||||
uv_close(handle, [](uv_handle_t* handle) {
|
|
||||||
static_cast<Environment*>(handle->data)->FinishHandleCleanup(handle);
|
|
||||||
});
|
|
||||||
|
|
||||||
while (handle_cleanup_waiting_ != 0)
|
|
||||||
uv_run(event_loop(), UV_RUN_ONCE);
|
|
||||||
|
|
||||||
context()->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex,
|
context()->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex,
|
||||||
nullptr);
|
nullptr);
|
||||||
#define V(PropertyName, TypeName) PropertyName ## _.Reset();
|
#define V(PropertyName, TypeName) PropertyName ## _.Reset();
|
||||||
|
24
src/env.cc
24
src/env.cc
@ -92,6 +92,30 @@ void Environment::Start(int argc,
|
|||||||
LoadAsyncWrapperInfo(this);
|
LoadAsyncWrapperInfo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Environment::CleanupHandles() {
|
||||||
|
while (HandleCleanup* hc = handle_cleanup_queue_.PopFront()) {
|
||||||
|
handle_cleanup_waiting_++;
|
||||||
|
hc->cb_(this, hc->handle_, hc->arg_);
|
||||||
|
delete hc;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (handle_cleanup_waiting_ != 0)
|
||||||
|
uv_run(event_loop(), UV_RUN_ONCE);
|
||||||
|
|
||||||
|
// Closing the destroy_ids_idle_handle_ within the handle cleanup queue
|
||||||
|
// prevents the async wrap destroy hook from being called.
|
||||||
|
uv_handle_t* handle =
|
||||||
|
reinterpret_cast<uv_handle_t*>(&destroy_ids_idle_handle_);
|
||||||
|
handle->data = this;
|
||||||
|
handle_cleanup_waiting_ = 1;
|
||||||
|
uv_close(handle, [](uv_handle_t* handle) {
|
||||||
|
static_cast<Environment*>(handle->data)->FinishHandleCleanup(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
while (handle_cleanup_waiting_ != 0)
|
||||||
|
uv_run(event_loop(), UV_RUN_ONCE);
|
||||||
|
}
|
||||||
|
|
||||||
void Environment::StartProfilerIdleNotifier() {
|
void Environment::StartProfilerIdleNotifier() {
|
||||||
uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) {
|
uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) {
|
||||||
Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle);
|
Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle);
|
||||||
|
@ -440,6 +440,7 @@ class Environment {
|
|||||||
const char* const* exec_argv,
|
const char* const* exec_argv,
|
||||||
bool start_profiler_idle_notifier);
|
bool start_profiler_idle_notifier);
|
||||||
void AssignToContext(v8::Local<v8::Context> context);
|
void AssignToContext(v8::Local<v8::Context> context);
|
||||||
|
void CleanupHandles();
|
||||||
|
|
||||||
void StartProfilerIdleNotifier();
|
void StartProfilerIdleNotifier();
|
||||||
void StopProfilerIdleNotifier();
|
void StopProfilerIdleNotifier();
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const common = require('../common');
|
|
||||||
if (!common.hasCrypto) {
|
|
||||||
common.skip('missing crypto');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const async_wrap = process.binding('async_wrap');
|
|
||||||
const assert = require('assert');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const domain = require('domain');
|
|
||||||
const spawn = require('child_process').spawn;
|
|
||||||
const callbacks = [ 'init', 'pre', 'post', 'destroy' ];
|
|
||||||
const toCall = process.argv[2];
|
|
||||||
let msgCalled = 0;
|
|
||||||
let msgReceived = 0;
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
if (toCall === 'init')
|
|
||||||
throw new Error('init');
|
|
||||||
}
|
|
||||||
function pre() {
|
|
||||||
if (toCall === 'pre')
|
|
||||||
throw new Error('pre');
|
|
||||||
}
|
|
||||||
function post() {
|
|
||||||
if (toCall === 'post')
|
|
||||||
throw new Error('post');
|
|
||||||
}
|
|
||||||
function destroy() {
|
|
||||||
if (toCall === 'destroy')
|
|
||||||
throw new Error('destroy');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof process.argv[2] === 'string') {
|
|
||||||
async_wrap.setupHooks({ init, pre, post, destroy });
|
|
||||||
async_wrap.enable();
|
|
||||||
|
|
||||||
process.on('uncaughtException', common.mustNotCall());
|
|
||||||
|
|
||||||
const d = domain.create();
|
|
||||||
d.on('error', common.mustNotCall());
|
|
||||||
d.run(() => {
|
|
||||||
// Using randomBytes because timers are not yet supported.
|
|
||||||
crypto.randomBytes(0, common.noop);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
process.on('exit', (code) => {
|
|
||||||
assert.strictEqual(msgCalled, callbacks.length);
|
|
||||||
assert.strictEqual(msgCalled, msgReceived);
|
|
||||||
});
|
|
||||||
|
|
||||||
callbacks.forEach((item) => {
|
|
||||||
msgCalled++;
|
|
||||||
|
|
||||||
const child = spawn(process.execPath, [__filename, item]);
|
|
||||||
let errstring = '';
|
|
||||||
|
|
||||||
child.stderr.on('data', (data) => {
|
|
||||||
errstring += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('close', (code) => {
|
|
||||||
if (errstring.includes('Error: ' + item))
|
|
||||||
msgReceived++;
|
|
||||||
|
|
||||||
assert.strictEqual(code, 1, `${item} closed with code ${code}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
7
test/parallel/test-process-exit-GH-12322.js
Normal file
7
test/parallel/test-process-exit-GH-12322.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
require('../common');
|
||||||
|
|
||||||
|
process.on('exit', () => {
|
||||||
|
setTimeout(process.abort, 0); // Should not run.
|
||||||
|
for (const start = Date.now(); Date.now() - start < 10; /* Empty. */);
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user