mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
process,worker: ensure code after exit() effectless
Cope with the delay(to the next function call) of v8::Isolate::TerminateExecution() PR-URL: https://github.com/nodejs/node/pull/45620 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
parent
d5a08c7e11
commit
8438f3b73b
|
|
@ -100,6 +100,8 @@ function hrtimeBigInt() {
|
||||||
return hrBigintValues[0];
|
return hrBigintValues[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nop() {}
|
||||||
|
|
||||||
// The execution of this function itself should not cause any side effects.
|
// The execution of this function itself should not cause any side effects.
|
||||||
function wrapProcessMethods(binding) {
|
function wrapProcessMethods(binding) {
|
||||||
const {
|
const {
|
||||||
|
|
@ -194,6 +196,16 @@ function wrapProcessMethods(binding) {
|
||||||
// in the user land. Either document it, or deprecate it in favor of a
|
// in the user land. Either document it, or deprecate it in favor of a
|
||||||
// better public alternative.
|
// better public alternative.
|
||||||
process.reallyExit(process.exitCode || kNoFailure);
|
process.reallyExit(process.exitCode || kNoFailure);
|
||||||
|
|
||||||
|
// If this is a worker, v8::Isolate::TerminateExecution() is called above.
|
||||||
|
// That function spoofs the stack pointer to cause the stack guard
|
||||||
|
// check to throw the termination exception. Because v8 performs
|
||||||
|
// stack guard check upon every function call, we give it a chance.
|
||||||
|
//
|
||||||
|
// Without this, user code after `process.exit()` would take effect.
|
||||||
|
// test/parallel/test-worker-voluntarily-exit-followed-by-addition.js
|
||||||
|
// test/parallel/test-worker-voluntarily-exit-followed-by-throw.js
|
||||||
|
nop();
|
||||||
}
|
}
|
||||||
|
|
||||||
function kill(pid, sig) {
|
function kill(pid, sig) {
|
||||||
|
|
|
||||||
|
|
@ -314,6 +314,9 @@ class CallbackWrapperBase : public CallbackWrapper {
|
||||||
env->CallIntoModule([&](napi_env env) { result = cb(env, cbinfo_wrapper); },
|
env->CallIntoModule([&](napi_env env) { result = cb(env, cbinfo_wrapper); },
|
||||||
[&](napi_env env, v8::Local<v8::Value> value) {
|
[&](napi_env env, v8::Local<v8::Value> value) {
|
||||||
exceptionOccurred = true;
|
exceptionOccurred = true;
|
||||||
|
if (env->terminatedOrTerminating()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
env->isolate->ThrowException(value);
|
env->isolate->ThrowException(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,20 @@ struct napi_env__ {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void HandleThrow(napi_env env, v8::Local<v8::Value> value) {
|
static inline void HandleThrow(napi_env env, v8::Local<v8::Value> value) {
|
||||||
|
if (env->terminatedOrTerminating()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
env->isolate->ThrowException(value);
|
env->isolate->ThrowException(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// i.e. whether v8 exited or is about to exit
|
||||||
|
inline bool terminatedOrTerminating() {
|
||||||
|
return this->isolate->IsExecutionTerminating() || !can_call_into_js();
|
||||||
|
}
|
||||||
|
|
||||||
|
// v8 uses a special exception to indicate termination, the
|
||||||
|
// `handle_exception` callback should identify such case using
|
||||||
|
// terminatedOrTerminating() before actually handle the exception
|
||||||
template <typename T, typename U = decltype(HandleThrow)>
|
template <typename T, typename U = decltype(HandleThrow)>
|
||||||
inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) {
|
inline void CallIntoModule(T&& call, U&& handle_exception = HandleThrow) {
|
||||||
int open_handle_scopes_before = open_handle_scopes;
|
int open_handle_scopes_before = open_handle_scopes;
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ template <bool enforceUncaughtExceptionPolicy, typename T>
|
||||||
void node_napi_env__::CallbackIntoModule(T&& call) {
|
void node_napi_env__::CallbackIntoModule(T&& call) {
|
||||||
CallIntoModule(call, [](napi_env env_, v8::Local<v8::Value> local_err) {
|
CallIntoModule(call, [](napi_env env_, v8::Local<v8::Value> local_err) {
|
||||||
node_napi_env__* env = static_cast<node_napi_env__*>(env_);
|
node_napi_env__* env = static_cast<node_napi_env__*>(env_);
|
||||||
|
if (env->terminatedOrTerminating()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
node::Environment* node_env = env->node_env();
|
node::Environment* node_env = env->node_env();
|
||||||
if (!node_env->options()->force_node_api_uncaught_exceptions_policy &&
|
if (!node_env->options()->force_node_api_uncaught_exceptions_policy &&
|
||||||
!enforceUncaughtExceptionPolicy) {
|
!enforceUncaughtExceptionPolicy) {
|
||||||
|
|
|
||||||
|
|
@ -553,7 +553,9 @@ TEST_F(EnvironmentTest, ExitHandlerTest) {
|
||||||
callback_calls++;
|
callback_calls++;
|
||||||
node::Stop(*env);
|
node::Stop(*env);
|
||||||
});
|
});
|
||||||
node::LoadEnvironment(*env, "process.exit(42)").ToLocalChecked();
|
// When terminating, v8 throws makes the current embedder call bail out
|
||||||
|
// with MaybeLocal<>()
|
||||||
|
EXPECT_TRUE(node::LoadEnvironment(*env, "process.exit(42)").IsEmpty());
|
||||||
EXPECT_EQ(callback_calls, 1);
|
EXPECT_EQ(callback_calls, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ if (isMainThread) {
|
||||||
const { Test } = require(`./build/${common.buildType}/test_worker_terminate`);
|
const { Test } = require(`./build/${common.buildType}/test_worker_terminate`);
|
||||||
|
|
||||||
const { counter } = workerData;
|
const { counter } = workerData;
|
||||||
// Test() tries to call a function twice and asserts that the second call does
|
// Test() tries to call a function and asserts it fails because of a
|
||||||
// not work because of a pending exception.
|
// pending termination exception.
|
||||||
Test(() => {
|
Test(() => {
|
||||||
Atomics.add(counter, 0, 1);
|
Atomics.add(counter, 0, 1);
|
||||||
process.exit();
|
process.exit();
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ napi_value Test(napi_env env, napi_callback_info info) {
|
||||||
NODE_API_ASSERT(env, t == napi_function,
|
NODE_API_ASSERT(env, t == napi_function,
|
||||||
"Wrong first argument, function expected.");
|
"Wrong first argument, function expected.");
|
||||||
|
|
||||||
status = napi_call_function(env, recv, argv[0], 0, NULL, NULL);
|
|
||||||
assert(status == napi_ok);
|
|
||||||
status = napi_call_function(env, recv, argv[0], 0, NULL, NULL);
|
status = napi_call_function(env, recv, argv[0], 0, NULL, NULL);
|
||||||
assert(status == napi_pending_exception);
|
assert(status == napi_pending_exception);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const { Worker } = require('worker_threads');
|
||||||
const workerData = new Int32Array(new SharedArrayBuffer(4));
|
const workerData = new Int32Array(new SharedArrayBuffer(4));
|
||||||
const w = new Worker(`
|
const w = new Worker(`
|
||||||
const { createHook } = require('async_hooks');
|
const { createHook } = require('async_hooks');
|
||||||
|
const { workerData } = require('worker_threads');
|
||||||
|
|
||||||
setImmediate(async () => {
|
setImmediate(async () => {
|
||||||
createHook({ init() {} }).enable();
|
createHook({ init() {} }).enable();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { Worker, isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (isMainThread) {
|
||||||
|
const workerData = new Int32Array(new SharedArrayBuffer(4));
|
||||||
|
new Worker(__filename, {
|
||||||
|
workerData,
|
||||||
|
});
|
||||||
|
process.on('beforeExit', common.mustCall(() => {
|
||||||
|
assert.strictEqual(workerData[0], 0);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const { workerData } = require('worker_threads');
|
||||||
|
process.exit();
|
||||||
|
workerData[0] = 1;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const { Worker, isMainThread } = require('worker_threads');
|
||||||
|
|
||||||
|
if (isMainThread) {
|
||||||
|
const workerData = new Int32Array(new SharedArrayBuffer(4));
|
||||||
|
new Worker(__filename, {
|
||||||
|
workerData,
|
||||||
|
});
|
||||||
|
process.on('beforeExit', common.mustCall(() => {
|
||||||
|
assert.strictEqual(workerData[0], 0);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
const { workerData } = require('worker_threads');
|
||||||
|
try {
|
||||||
|
process.exit();
|
||||||
|
throw new Error('xxx');
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
} catch (err) {
|
||||||
|
workerData[0] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user