diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 571888ebc8d6cb..f98699dc0363f5 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -80,8 +80,11 @@ const { validateInteger, } = require('internal/validators'); const { - exiting_aliased_Uint32Array, + exit_info_private_symbol, getHiddenValue, + kExitCode, + kExiting, + kHasExitCode, } = internalBinding('util'); setupProcessObject(); @@ -90,26 +93,24 @@ setupGlobalProxy(); setupBuffer(); process.domain = null; + +// process._exiting and process.exitCode { - const exitingAliasedUint32Array = - getHiddenValue(process, exiting_aliased_Uint32Array); + const fields = getHiddenValue(process, exit_info_private_symbol); + ObjectDefineProperty(process, '_exiting', { __proto__: null, get() { - return exitingAliasedUint32Array[0] === 1; + return fields[kExiting] === 1; }, set(value) { - exitingAliasedUint32Array[0] = value ? 1 : 0; + fields[kExiting] = value ? 1 : 0; }, enumerable: true, configurable: true, }); -} -process._exiting = false; -{ let exitCode; - ObjectDefineProperty(process, 'exitCode', { __proto__: null, get() { @@ -123,6 +124,10 @@ process._exiting = false; value = code; } validateInteger(value, 'code'); + fields[kExitCode] = value; + fields[kHasExitCode] = 1; + } else { + fields[kHasExitCode] = 0; } exitCode = code; }, @@ -130,6 +135,7 @@ process._exiting = false; configurable: false, }); } +process._exiting = false; // process.config is serialized config.gypi const nativeModule = internalBinding('builtins'); diff --git a/src/api/hooks.cc b/src/api/hooks.cc index 04fc00c394404b..65d39e6b9ff921 100644 --- a/src/api/hooks.cc +++ b/src/api/hooks.cc @@ -16,7 +16,6 @@ using v8::NewStringType; using v8::Nothing; using v8::Object; using v8::String; -using v8::Value; void RunAtExit(Environment* env) { env->RunAtExitCallbacks(); @@ -36,19 +35,17 @@ Maybe EmitProcessBeforeExit(Environment* env) { if (!env->destroy_async_id_list()->empty()) AsyncWrap::DestroyAsyncIdsCallback(env); - HandleScope handle_scope(env->isolate()); - Local context = env->context(); - Context::Scope context_scope(context); - - Local exit_code_v; - if (!env->process_object()->Get(context, env->exit_code_string()) - .ToLocal(&exit_code_v)) return Nothing(); + Isolate* isolate = env->isolate(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(env->context()); - Local exit_code; - if (!exit_code_v->ToInteger(context).ToLocal(&exit_code)) { + if (!env->can_call_into_js()) { return Nothing(); } + Local exit_code = Integer::New( + isolate, static_cast(env->exit_code(ExitCode::kNoFailure))); + return ProcessEmit(env, "beforeExit", exit_code).IsEmpty() ? Nothing() : Just(true); } @@ -65,29 +62,22 @@ Maybe EmitProcessExitInternal(Environment* env) { // process.emit('exit') Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); - Local context = env->context(); - Context::Scope context_scope(context); - Local process_object = env->process_object(); - - // TODO(addaleax): It might be nice to share process.exitCode via - // getter/setter pairs that pass data directly to the native side, so that we - // don't manually have to read and write JS properties here. These getters - // could use e.g. a typed array for performance. + Context::Scope context_scope(env->context()); + env->set_exiting(true); - Local exit_code = env->exit_code_string(); - Local code_v; - int code; - if (!process_object->Get(context, exit_code).ToLocal(&code_v) || - !code_v->Int32Value(context).To(&code) || - ProcessEmit(env, "exit", Integer::New(isolate, code)).IsEmpty() || - // Reload exit code, it may be changed by `emit('exit')` - !process_object->Get(context, exit_code).ToLocal(&code_v) || - !code_v->Int32Value(context).To(&code)) { + if (!env->can_call_into_js()) { return Nothing(); } - return Just(static_cast(code)); + Local exit_code = Integer::New( + isolate, static_cast(env->exit_code(ExitCode::kNoFailure))); + + if (ProcessEmit(env, "exit", exit_code).IsEmpty()) { + return Nothing(); + } + // Reload exit code, it may be changed by `emit('exit')` + return Just(env->exit_code(ExitCode::kNoFailure)); } Maybe EmitProcessExit(Environment* env) { diff --git a/src/env-inl.h b/src/env-inl.h index 4446b6057d5977..a512d175a29cfe 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -364,11 +364,17 @@ inline bool Environment::force_context_aware() const { } inline void Environment::set_exiting(bool value) { - exiting_[0] = value ? 1 : 0; + exit_info_[kExiting] = value ? 1 : 0; } -inline AliasedUint32Array& Environment::exiting() { - return exiting_; +inline ExitCode Environment::exit_code(const ExitCode default_code) const { + return exit_info_[kHasExitCode] == 0 + ? default_code + : static_cast(exit_info_[kExitCode]); +} + +inline AliasedInt32Array& Environment::exit_info() { + return exit_info_; } inline void Environment::set_abort_on_uncaught_exception(bool value) { diff --git a/src/env.cc b/src/env.cc index be5d4b0723c578..2f0720d4027010 100644 --- a/src/env.cc +++ b/src/env.cc @@ -655,7 +655,8 @@ Environment::Environment(IsolateData* isolate_data, exec_argv_(exec_args), argv_(args), exec_path_(GetExecPath(args)), - exiting_(isolate_, 1, MAYBE_FIELD_PTR(env_info, exiting)), + exit_info_( + isolate_, kExitInfoFieldCount, MAYBE_FIELD_PTR(env_info, exit_info)), should_abort_on_uncaught_toggle_( isolate_, 1, @@ -1608,7 +1609,7 @@ EnvSerializeInfo Environment::Serialize(SnapshotCreator* creator) { info.immediate_info = immediate_info_.Serialize(ctx, creator); info.tick_info = tick_info_.Serialize(ctx, creator); info.performance_state = performance_state_->Serialize(ctx, creator); - info.exiting = exiting_.Serialize(ctx, creator); + info.exit_info = exit_info_.Serialize(ctx, creator); info.stream_base_state = stream_base_state_.Serialize(ctx, creator); info.should_abort_on_uncaught_toggle = should_abort_on_uncaught_toggle_.Serialize(ctx, creator); @@ -1654,7 +1655,7 @@ void Environment::DeserializeProperties(const EnvSerializeInfo* info) { immediate_info_.Deserialize(ctx); tick_info_.Deserialize(ctx); performance_state_->Deserialize(ctx); - exiting_.Deserialize(ctx); + exit_info_.Deserialize(ctx); stream_base_state_.Deserialize(ctx); should_abort_on_uncaught_toggle_.Deserialize(ctx); @@ -1844,7 +1845,7 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("builtins_without_cache", builtins_without_cache); tracker->TrackField("destroy_async_id_list", destroy_async_id_list_); tracker->TrackField("exec_argv", exec_argv_); - tracker->TrackField("exiting", exiting_); + tracker->TrackField("exit_info", exit_info_); tracker->TrackField("should_abort_on_uncaught_toggle", should_abort_on_uncaught_toggle_); tracker->TrackField("stream_base_state", stream_base_state_); diff --git a/src/env.h b/src/env.h index 542d1cf6e5697b..3d44e0acbd86a2 100644 --- a/src/env.h +++ b/src/env.h @@ -510,7 +510,7 @@ struct EnvSerializeInfo { TickInfo::SerializeInfo tick_info; ImmediateInfo::SerializeInfo immediate_info; performance::PerformanceState::SerializeInfo performance_state; - AliasedBufferIndex exiting; + AliasedBufferIndex exit_info; AliasedBufferIndex stream_base_state; AliasedBufferIndex should_abort_on_uncaught_toggle; @@ -743,10 +743,12 @@ class Environment : public MemoryRetainer { inline void set_force_context_aware(bool value); inline bool force_context_aware() const; - // This is a pseudo-boolean that keeps track of whether the process is - // exiting. + // This contains fields that are a pseudo-boolean that keeps track of whether + // the process is exiting, an integer representing the process exit code, and + // a pseudo-boolean to indicate whether the exit code is undefined. + inline AliasedInt32Array& exit_info(); inline void set_exiting(bool value); - inline AliasedUint32Array& exiting(); + inline ExitCode exit_code(const ExitCode default_code) const; // This stores whether the --abort-on-uncaught-exception flag was passed // to Node. @@ -1038,6 +1040,14 @@ class Environment : public MemoryRetainer { inline void RemoveHeapSnapshotNearHeapLimitCallback(size_t heap_limit); + // Field identifiers for exit_info_ + enum ExitInfoField { + kExiting = 0, + kExitCode, + kHasExitCode, + kExitInfoFieldCount + }; + private: inline void ThrowError(v8::Local (*fun)(v8::Local), const char* errmsg); @@ -1103,7 +1113,7 @@ class Environment : public MemoryRetainer { uint32_t script_id_counter_ = 0; uint32_t function_id_counter_ = 0; - AliasedUint32Array exiting_; + AliasedInt32Array exit_info_; AliasedUint32Array should_abort_on_uncaught_toggle_; int should_not_abort_scope_counter_ = 0; diff --git a/src/env_properties.h b/src/env_properties.h index 7282d1b4e50946..89d2b11af83cca 100644 --- a/src/env_properties.h +++ b/src/env_properties.h @@ -24,7 +24,7 @@ V(napi_type_tag, "node:napi:type_tag") \ V(napi_wrapper, "node:napi:wrapper") \ V(untransferable_object_private_symbol, "node:untransferableObject") \ - V(exiting_aliased_Uint32Array, "node:exiting_aliased_Uint32Array") + V(exit_info_private_symbol, "node:exit_info_private_symbol") // Symbols are per-isolate primitives but Environment proxies them // for the sake of convenience. @@ -114,7 +114,6 @@ V(errno_string, "errno") \ V(error_string, "error") \ V(exchange_string, "exchange") \ - V(exit_code_string, "exitCode") \ V(expire_string, "expire") \ V(exponent_string, "exponent") \ V(exports_string, "exports") \ diff --git a/src/node_errors.cc b/src/node_errors.cc index c400d74d23bbbd..5408c0166b0e3b 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -1151,15 +1151,8 @@ void TriggerUncaughtException(Isolate* isolate, RunAtExit(env); // If the global uncaught exception handler sets process.exitCode, - // exit with that code. Otherwise, exit with 1. - Local exit_code = env->exit_code_string(); - Local code; - if (process_object->Get(env->context(), exit_code).ToLocal(&code) && - code->IsInt32()) { - env->Exit(static_cast(code.As()->Value())); - } else { - env->Exit(ExitCode::kGenericUserError); - } + // exit with that code. Otherwise, exit with `ExitCode::kGenericUserError`. + env->Exit(env->exit_code(ExitCode::kGenericUserError)); } void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) { diff --git a/src/node_process_object.cc b/src/node_process_object.cc index 6822601a47d0be..decb41f82c35b1 100644 --- a/src/node_process_object.cc +++ b/src/node_process_object.cc @@ -92,11 +92,11 @@ MaybeLocal CreateProcessObject(Realm* realm) { return MaybeLocal(); } - // process[exiting_aliased_Uint32Array] + // process[exit_info_private_symbol] if (process ->SetPrivate(context, - realm->env()->exiting_aliased_Uint32Array(), - realm->env()->exiting().GetJSArray()) + realm->env()->exit_info_private_symbol(), + realm->env()->exit_info().GetJSArray()) .IsNothing()) { return {}; } diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index a5fda9aa5a3833..4f56d7cca1c42a 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -123,7 +123,7 @@ std::ostream& operator<<(std::ostream& output, const EnvSerializeInfo& i) { << "// -- performance_state begins --\n" << i.performance_state << ",\n" << "// -- performance_state ends --\n" - << i.exiting << ", // exiting\n" + << i.exit_info << ", // exit_info\n" << i.stream_base_state << ", // stream_base_state\n" << i.should_abort_on_uncaught_toggle << ", // should_abort_on_uncaught_toggle\n" @@ -736,7 +736,7 @@ EnvSerializeInfo FileReader::Read() { result.immediate_info = Read(); result.performance_state = Read(); - result.exiting = Read(); + result.exit_info = Read(); result.stream_base_state = Read(); result.should_abort_on_uncaught_toggle = Read(); result.principal_realm = Read(); @@ -757,7 +757,7 @@ size_t FileWriter::Write(const EnvSerializeInfo& data) { written_total += Write(data.immediate_info); written_total += Write( data.performance_state); - written_total += Write(data.exiting); + written_total += Write(data.exit_info); written_total += Write(data.stream_base_state); written_total += Write(data.should_abort_on_uncaught_toggle); diff --git a/src/node_util.cc b/src/node_util.cc index 58f58478e59944..5d2b4f2155d3d2 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -424,6 +424,17 @@ void Initialize(Local target, V(kRejected); #undef V +#define V(name) \ + target \ + ->Set(context, \ + FIXED_ONE_BYTE_STRING(env->isolate(), #name), \ + Integer::New(env->isolate(), Environment::ExitInfoField::name)) \ + .FromJust() + V(kExiting); + V(kExitCode); + V(kHasExitCode); +#undef V + SetMethodNoSideEffect(context, target, "getHiddenValue", GetHiddenValue); SetMethod(context, target, "setHiddenValue", SetHiddenValue); SetMethodNoSideEffect(