Skip to content

Commit

Permalink
src: make BuiltinLoader threadsafe and non-global
Browse files Browse the repository at this point in the history
As discussed in nodejs#45888, using a
global `BuiltinLoader` instance is probably undesirable in a world
in which embedders are able to create Node.js Environments with
different sources and therefore mutually incompatible code
caching properties.

This PR makes it so that `BuiltinLoader` is no longer a global
singleton and instead only shared between `Environment`s that
have a direct relation to each other, and addresses a few
thread safety issues along with that.
  • Loading branch information
addaleax committed Jan 10, 2023
1 parent 21fb98e commit 5da2608
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 131 deletions.
8 changes: 5 additions & 3 deletions src/api/environment.cc
Expand Up @@ -470,7 +470,7 @@ MaybeLocal<Value> LoadEnvironment(
return LoadEnvironment(
env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal<Value> {
std::string name = "embedder_main_" + std::to_string(env->thread_id());
builtins::BuiltinLoader::Add(name.c_str(), main_script_source_utf8);
env->builtin_loader()->Add(name.c_str(), main_script_source_utf8);
Realm* realm = env->principal_realm();

return realm->ExecuteBootstrapper(name.c_str());
Expand Down Expand Up @@ -711,10 +711,12 @@ Maybe<bool> InitializePrimordials(Local<Context> context) {
"internal/per_context/messageport",
nullptr};

auto builtin_loader = builtins::BuiltinLoader::Create();
for (const char** module = context_files; *module != nullptr; module++) {
Local<Value> arguments[] = {exports, primordials};
if (builtins::BuiltinLoader::CompileAndCall(
context, *module, arraysize(arguments), arguments, nullptr)
if (builtin_loader
->CompileAndCall(
context, *module, arraysize(arguments), arguments, nullptr)
.IsEmpty()) {
// Execution failed during context creation.
return Nothing<bool>();
Expand Down
10 changes: 10 additions & 0 deletions src/env-inl.h
Expand Up @@ -436,6 +436,16 @@ inline std::vector<double>* Environment::destroy_async_id_list() {
return &destroy_async_id_list_;
}

inline std::shared_ptr<builtins::BuiltinLoader> Environment::builtin_loader() {
DCHECK(builtin_loader_);
return builtin_loader_;
}

inline void Environment::set_builtin_loader(
std::shared_ptr<builtins::BuiltinLoader> loader) {
builtin_loader_ = loader;
}

inline double Environment::new_async_id() {
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];
Expand Down
2 changes: 2 additions & 0 deletions src/env.cc
Expand Up @@ -752,6 +752,8 @@ Environment::Environment(IsolateData* isolate_data,
env_info,
flags,
thread_id) {
// TODO(addaleax): Make this part of CreateEnvironment().
set_builtin_loader(builtins::BuiltinLoader::Create());
InitializeMainContext(context, env_info);
}

Expand Down
5 changes: 5 additions & 0 deletions src/env.h
Expand Up @@ -721,6 +721,9 @@ class Environment : public MemoryRetainer {
// List of id's that have been destroyed and need the destroy() cb called.
inline std::vector<double>* destroy_async_id_list();

std::shared_ptr<builtins::BuiltinLoader> builtin_loader();
void set_builtin_loader(std::shared_ptr<builtins::BuiltinLoader> loader);

std::unordered_multimap<int, loader::ModuleWrap*> hash_to_module_map;
std::unordered_map<uint32_t, loader::ModuleWrap*> id_to_module_map;
std::unordered_map<uint32_t, contextify::ContextifyScript*>
Expand Down Expand Up @@ -1143,6 +1146,8 @@ class Environment : public MemoryRetainer {

std::unique_ptr<Realm> principal_realm_ = nullptr;

std::shared_ptr<builtins::BuiltinLoader> builtin_loader_;

// Used by allocate_managed_buffer() and release_managed_buffer() to keep
// track of the BackingStore for a given pointer.
std::unordered_map<char*, std::unique_ptr<v8::BackingStore>>
Expand Down
3 changes: 0 additions & 3 deletions src/node.cc
Expand Up @@ -1183,9 +1183,6 @@ ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr,
}
}

if ((*snapshot_data_ptr) != nullptr) {
BuiltinLoader::RefreshCodeCache((*snapshot_data_ptr)->code_cache);
}
NodeMainInstance main_instance(*snapshot_data_ptr,
uv_default_loop(),
per_process::v8_platform.Platform(),
Expand Down
4 changes: 2 additions & 2 deletions src/node_binding.cc
Expand Up @@ -631,13 +631,13 @@ void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {
CHECK(exports->SetPrototype(context, Null(isolate)).FromJust());
DefineConstants(isolate, exports);
} else if (!strcmp(*module_v, "natives")) {
exports = builtins::BuiltinLoader::GetSourceObject(context);
exports = realm->env()->builtin_loader()->GetSourceObject(context);
// Legacy feature: process.binding('natives').config contains stringified
// config.gypi
CHECK(exports
->Set(context,
realm->isolate_data()->config_string(),
builtins::BuiltinLoader::GetConfigString(isolate))
realm->env()->builtin_loader()->GetConfigString(isolate))
.FromJust());
} else {
return THROW_ERR_INVALID_MODULE(isolate, "No such binding: %s", *module_v);
Expand Down

0 comments on commit 5da2608

Please sign in to comment.