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

[v18.x] backport various patches of internal refactorings for snapshot and realms #45007

Closed
Closed
1 change: 1 addition & 0 deletions .eslintrc.js
Expand Up @@ -338,6 +338,7 @@ module.exports = {
TextEncoderStream: 'readable',
TransformStream: 'readable',
TransformStreamDefaultController: 'readable',
ShadowRealm: 'readable',
SubtleCrypto: 'readable',
WritableStream: 'readable',
WritableStreamDefaultWriter: 'readable',
Expand Down
21 changes: 20 additions & 1 deletion doc/api/cli.md
Expand Up @@ -411,10 +411,18 @@ Disable experimental support for the [Fetch API][].

<!-- YAML
added: v16.6.0
-->
-->

Use this flag to disable top-level await in REPL.

### `--experimental-shadow-realm`

<!-- YAML
added: REPLACEME
-->

Use this flag to enable [ShadowRealm][] support.

### `--experimental-specifier-resolution=mode`

<!-- YAML
Expand Down Expand Up @@ -1177,6 +1185,15 @@ in the current working directory.
When used without `--build-snapshot`, `--snapshot-blob` specifies the
path to the blob that will be used to restore the application state.

When loading a snapshot, Node.js checks that:

1. The version, architecture and platform of the running Node.js binary
are exactly the same as that of the binary that generates the snapshot.
2. The V8 flags and CPU features are compatible with that of the binary
that generates the snapshot.

If they don't match, Node.js would refuse to load the snapshot and exit with 1.

### `--test`

<!-- YAML
Expand Down Expand Up @@ -1807,6 +1824,7 @@ Node.js options that are allowed are:
* `--experimental-modules`
* `--experimental-network-imports`
* `--experimental-policy`
* `--experimental-shadow-realm`
* `--experimental-specifier-resolution`
* `--experimental-top-level-await`
* `--experimental-vm-modules`
Expand Down Expand Up @@ -2236,6 +2254,7 @@ done
[OSSL_PROVIDER-legacy]: https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html
[REPL]: repl.md
[ScriptCoverage]: https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage
[ShadowRealm]: https://github.com/tc39/proposal-shadowrealm
[Source Map]: https://sourcemaps.info/spec.html
[Subresource Integrity]: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
[V8 JavaScript code coverage]: https://v8project.blogspot.com/2017/12/javascript-code-coverage.html
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Expand Up @@ -159,6 +159,9 @@ Enable experimental support for loading modules using `import` over `https:`.
.It Fl -experimental-policy
Use the specified file as a security policy.
.
.It Fl -experimental-shadow-realm
Use this flag to enable ShadowRealm support.
.
.It Fl -no-experimental-fetch
Disable experimental support for the Fetch API.
.
Expand Down
4 changes: 4 additions & 0 deletions lib/.eslintrc.yaml
Expand Up @@ -89,6 +89,10 @@ rules:
message: Use `const { Request } = require('internal/deps/undici/undici');` instead of the global.
- name: Response
message: Use `const { Response } = require('internal/deps/undici/undici');` instead of the global.
# ShadowRealm is not available in primordials because it can be
# disabled with --no-harmony-shadow-realm CLI flag.
- name: ShadowRealm
message: Use `const { ShadowRealm } = globalThis;` instead of the global.
# SharedArrayBuffer is not available in primordials because it can be
# disabled with --no-harmony-sharedarraybuffer CLI flag.
- name: SharedArrayBuffer
Expand Down
53 changes: 40 additions & 13 deletions lib/internal/perf/event_loop_utilization.js
@@ -1,13 +1,26 @@
'use strict';

const nodeTiming = require('internal/perf/nodetiming');

const { now } = require('internal/perf/utils');
const {
constants: {
NODE_PERFORMANCE_MILESTONE_LOOP_START,
},
loopIdleTime,
milestones,
} = internalBinding('performance');

function eventLoopUtilization(util1, util2) {
const ls = nodeTiming.loopStart;
// Get the original milestone timestamps that calculated from the beginning
// of the process.
return internalEventLoopUtilization(
milestones[NODE_PERFORMANCE_MILESTONE_LOOP_START] / 1e6,
loopIdleTime(),
util1,
util2
);
}

if (ls <= 0) {
function internalEventLoopUtilization(loopStart, loopIdleTime, util1, util2) {
if (loopStart <= 0) {
return { idle: 0, active: 0, utilization: 0 };
}

Expand All @@ -17,17 +30,31 @@ function eventLoopUtilization(util1, util2) {
return { idle, active, utilization: active / (idle + active) };
}

const idle = nodeTiming.idleTime;
const active = now() - ls - idle;
// Using process.hrtime() to get the time from the beginning of the process,
// and offset it by the loopStart time (which is also calculated from the
// beginning of the process).
const now = process.hrtime();
const active = now[0] * 1e3 + now[1] / 1e6 - loopStart - loopIdleTime;

if (!util1) {
return { idle, active, utilization: active / (idle + active) };
return {
idle: loopIdleTime,
active,
utilization: active / (loopIdleTime + active),
};
}

const idle_delta = idle - util1.idle;
const active_delta = active - util1.active;
const utilization = active_delta / (idle_delta + active_delta);
return { idle: idle_delta, active: active_delta, utilization };
const idleDelta = loopIdleTime - util1.idle;
const activeDelta = active - util1.active;
const utilization = activeDelta / (idleDelta + activeDelta);
return {
idle: idleDelta,
active: activeDelta,
utilization,
};
}

module.exports = eventLoopUtilization;
module.exports = {
internalEventLoopUtilization,
eventLoopUtilization,
};
2 changes: 1 addition & 1 deletion lib/internal/perf/performance.js
Expand Up @@ -35,7 +35,7 @@ const {
setDispatchBufferFull,
} = require('internal/perf/observe');

const eventLoopUtilization = require('internal/perf/event_loop_utilization');
const { eventLoopUtilization } = require('internal/perf/event_loop_utilization');
const nodeTiming = require('internal/perf/nodetiming');
const timerify = require('internal/perf/timerify');
const { customInspectSymbol: kInspect } = require('internal/util');
Expand Down
32 changes: 9 additions & 23 deletions lib/internal/worker.js
Expand Up @@ -27,7 +27,9 @@ const {
const EventEmitter = require('events');
const assert = require('internal/assert');
const path = require('path');
const { now } = require('internal/perf/utils');
const {
internalEventLoopUtilization
} = require('internal/perf/event_loop_utilization');

const errorCodes = require('internal/errors').codes;
const {
Expand Down Expand Up @@ -472,28 +474,12 @@ function eventLoopUtilization(util1, util2) {
return { idle: 0, active: 0, utilization: 0 };
}

if (util2) {
const idle = util1.idle - util2.idle;
const active = util1.active - util2.active;
return { idle, active, utilization: active / (idle + active) };
}

const idle = this[kHandle].loopIdleTime();

// Using performance.now() here is fine since it's always the time from
// the beginning of the process, and is why it needs to be offset by the
// loopStart time (which is also calculated from the beginning of the
// process).
const active = now() - this[kLoopStartTime] - idle;

if (!util1) {
return { idle, active, utilization: active / (idle + active) };
}

const idle_delta = idle - util1.idle;
const active_delta = active - util1.active;
const utilization = active_delta / (idle_delta + active_delta);
return { idle: idle_delta, active: active_delta, utilization };
return internalEventLoopUtilization(
this[kLoopStartTime],
this[kHandle].loopIdleTime(),
util1,
util2
);
}

module.exports = {
Expand Down
8 changes: 8 additions & 0 deletions node.gyp
Expand Up @@ -477,6 +477,7 @@
'src/api/hooks.cc',
'src/api/utils.cc',
'src/async_wrap.cc',
'src/base_object.cc',
'src/cares_wrap.cc',
'src/cleanup_queue.cc',
'src/connect_wrap.cc',
Expand Down Expand Up @@ -525,10 +526,12 @@
'src/node_process_events.cc',
'src/node_process_methods.cc',
'src/node_process_object.cc',
'src/node_realm.cc',
'src/node_report.cc',
'src/node_report_module.cc',
'src/node_report_utils.cc',
'src/node_serdes.cc',
'src/node_shadow_realm.cc',
'src/node_snapshotable.cc',
'src/node_sockaddr.cc',
'src/node_stat_watcher.cc',
Expand Down Expand Up @@ -584,6 +587,7 @@
'src/connection_wrap.h',
'src/debug_utils.h',
'src/debug_utils-inl.h',
'src/env_properties.h',
'src/env.h',
'src/env-inl.h',
'src/handle_wrap.h',
Expand Down Expand Up @@ -631,16 +635,20 @@
'src/node_platform.h',
'src/node_process.h',
'src/node_process-inl.h',
'src/node_realm.h',
'src/node_realm-inl.h',
'src/node_report.h',
'src/node_revert.h',
'src/node_root_certs.h',
'src/node_shadow_realm.h',
'src/node_snapshotable.h',
'src/node_snapshot_builder.h',
'src/node_sockaddr.h',
'src/node_sockaddr-inl.h',
'src/node_stat_watcher.h',
'src/node_union_bytes.h',
'src/node_url.h',
'src/node_util.h',
'src/node_version.h',
'src/node_v8.h',
'src/node_v8_platform-inl.h',
Expand Down
2 changes: 1 addition & 1 deletion src/api/embed_helpers.cc
Expand Up @@ -68,7 +68,7 @@ Maybe<int> SpinEventLoop(Environment* env) {
env->set_snapshot_serialize_callback(Local<Function>());

env->PrintInfoForSnapshotIfDebug();
env->VerifyNoStrongBaseObjects();
env->ForEachRealm([](Realm* realm) { realm->VerifyNoStrongBaseObjects(); });
return EmitProcessExit(env);
}

Expand Down
18 changes: 14 additions & 4 deletions src/api/environment.cc
Expand Up @@ -5,6 +5,8 @@
#include "node_internals.h"
#include "node_options-inl.h"
#include "node_platform.h"
#include "node_realm-inl.h"
#include "node_shadow_realm.h"
#include "node_v8_platform-inl.h"
#include "node_wasm_web_api.h"
#include "uv.h"
Expand Down Expand Up @@ -264,6 +266,12 @@ void SetIsolateMiscHandlers(v8::Isolate* isolate, const IsolateSettings& s) {
isolate->SetWasmStreamingCallback(wasm_web_api::StartStreamingCompilation);
}

if (per_process::cli_options->get_per_isolate_options()
->experimental_shadow_realm) {
isolate->SetHostCreateShadowRealmContextCallback(
shadow_realm::HostCreateShadowRealmContextCallback);
}

if ((s.flags & SHOULD_NOT_SET_PROMISE_REJECTION_CALLBACK) == 0) {
auto* promise_reject_cb = s.promise_reject_callback ?
s.promise_reject_callback : PromiseRejectCallback;
Expand Down Expand Up @@ -371,7 +379,7 @@ Environment* CreateEnvironment(
}
#endif

if (env->RunBootstrapping().IsEmpty()) {
if (env->principal_realm()->RunBootstrapping().IsEmpty()) {
FreeEnvironment(env);
return nullptr;
}
Expand Down Expand Up @@ -446,11 +454,13 @@ MaybeLocal<Value> LoadEnvironment(
builtins::BuiltinLoader::Add(
name.c_str(), UnionBytes(**main_utf16, main_utf16->length()));
env->set_main_utf16(std::move(main_utf16));
Realm* realm = env->principal_realm();

// Arguments must match the parameters specified in
// BuiltinLoader::LookupAndCompile().
std::vector<Local<Value>> args = {env->process_object(),
env->builtin_module_require()};
return ExecuteBootstrapper(env, name.c_str(), &args);
std::vector<Local<Value>> args = {realm->process_object(),
realm->builtin_module_require()};
return realm->ExecuteBootstrapper(name.c_str(), &args);
});
}

Expand Down
29 changes: 26 additions & 3 deletions src/base_object-inl.h
Expand Up @@ -32,6 +32,15 @@

namespace node {

BaseObject::BaseObject(Environment* env, v8::Local<v8::Object> object)
: BaseObject(env->principal_realm(), object) {}

// static
v8::Local<v8::FunctionTemplate> BaseObject::GetConstructorTemplate(
Environment* env) {
return BaseObject::GetConstructorTemplate(env->isolate_data());
}

void BaseObject::Detach() {
CHECK_GT(pointer_data()->strong_ptr_count, 0);
pointer_data()->is_detached = true;
Expand All @@ -57,17 +66,31 @@ v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) const {
}

Environment* BaseObject::env() const {
return env_;
return realm_->env();
}

Realm* BaseObject::realm() const {
return realm_;
}

void BaseObject::TagNodeObject(v8::Local<v8::Object> object) {
DCHECK_GE(object->InternalFieldCount(), BaseObject::kInternalFieldCount);
object->SetAlignedPointerInInternalField(BaseObject::kEmbedderType,
&kNodeEmbedderId);
}

void BaseObject::SetInternalFields(v8::Local<v8::Object> object, void* slot) {
TagNodeObject(object);
object->SetAlignedPointerInInternalField(BaseObject::kSlot, slot);
}

BaseObject* BaseObject::FromJSObject(v8::Local<v8::Value> value) {
v8::Local<v8::Object> obj = value.As<v8::Object>();
DCHECK_GE(obj->InternalFieldCount(), BaseObject::kSlot);
DCHECK_GE(obj->InternalFieldCount(), BaseObject::kInternalFieldCount);
return static_cast<BaseObject*>(
obj->GetAlignedPointerFromInternalField(BaseObject::kSlot));
}


template <typename T>
T* BaseObject::FromJSObject(v8::Local<v8::Value> object) {
return static_cast<T*>(FromJSObject(object));
Expand Down