mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
src: make BuiltinLoader threadsafe and non-global
As discussed in https://github.com/nodejs/node/pull/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. PR-URL: https://github.com/nodejs/node/pull/45942 Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
parent
5d560978ff
commit
d896f5befd
@ -473,7 +473,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());
|
||||
@ -714,10 +714,17 @@ Maybe<bool> InitializePrimordials(Local<Context> context) {
|
||||
"internal/per_context/messageport",
|
||||
nullptr};
|
||||
|
||||
// We do not have access to a per-Environment BuiltinLoader instance
|
||||
// at this point, because this code runs before an Environment exists
|
||||
// in the first place. However, creating BuiltinLoader instances is
|
||||
// relatively cheap and all the scripts that we may want to run at
|
||||
// startup are always present in it.
|
||||
thread_local builtins::BuiltinLoader builtin_loader;
|
||||
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>();
|
||||
|
@ -436,6 +436,10 @@ inline std::vector<double>* Environment::destroy_async_id_list() {
|
||||
return &destroy_async_id_list_;
|
||||
}
|
||||
|
||||
inline builtins::BuiltinLoader* Environment::builtin_loader() {
|
||||
return &builtin_loader_;
|
||||
}
|
||||
|
||||
inline double Environment::new_async_id() {
|
||||
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
|
||||
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];
|
||||
|
@ -675,6 +675,15 @@ Environment::Environment(IsolateData* isolate_data,
|
||||
thread_id_(thread_id.id == static_cast<uint64_t>(-1)
|
||||
? AllocateEnvironmentThreadId().id
|
||||
: thread_id.id) {
|
||||
#ifdef NODE_V8_SHARED_RO_HEAP
|
||||
if (!is_main_thread()) {
|
||||
CHECK_NOT_NULL(isolate_data->worker_context());
|
||||
// TODO(addaleax): Adjust for the embedder API snapshot support changes
|
||||
builtin_loader()->CopySourceAndCodeCacheReferenceFrom(
|
||||
isolate_data->worker_context()->env()->builtin_loader());
|
||||
}
|
||||
#endif
|
||||
|
||||
// We'll be creating new objects so make sure we've entered the context.
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
|
@ -721,6 +721,8 @@ 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();
|
||||
|
||||
builtins::BuiltinLoader* builtin_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*>
|
||||
@ -1144,6 +1146,8 @@ class Environment : public MemoryRetainer {
|
||||
|
||||
std::unique_ptr<Realm> principal_realm_ = nullptr;
|
||||
|
||||
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>>
|
||||
|
@ -126,8 +126,6 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
using builtins::BuiltinLoader;
|
||||
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
@ -1183,9 +1181,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(),
|
||||
|
@ -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);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "env-inl.h"
|
||||
#include "node_external_reference.h"
|
||||
#include "node_internals.h"
|
||||
#include "node_threadsafe_cow-inl.h"
|
||||
#include "simdutf.h"
|
||||
#include "util-inl.h"
|
||||
|
||||
@ -32,9 +33,8 @@ using v8::String;
|
||||
using v8::Undefined;
|
||||
using v8::Value;
|
||||
|
||||
BuiltinLoader BuiltinLoader::instance_;
|
||||
|
||||
BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) {
|
||||
BuiltinLoader::BuiltinLoader()
|
||||
: config_(GetConfig()), code_cache_(std::make_shared<BuiltinCodeCache>()) {
|
||||
LoadJavaScriptSource();
|
||||
#ifdef NODE_SHARED_BUILTIN_CJS_MODULE_LEXER_LEXER_PATH
|
||||
AddExternalizedBuiltin(
|
||||
@ -54,25 +54,21 @@ BuiltinLoader::BuiltinLoader() : config_(GetConfig()), has_code_cache_(false) {
|
||||
#endif // NODE_SHARED_BUILTIN_UNDICI_UNDICI_PATH
|
||||
}
|
||||
|
||||
BuiltinLoader* BuiltinLoader::GetInstance() {
|
||||
return &instance_;
|
||||
}
|
||||
|
||||
bool BuiltinLoader::Exists(const char* id) {
|
||||
auto& source = GetInstance()->source_;
|
||||
return source.find(id) != source.end();
|
||||
auto source = source_.read();
|
||||
return source->find(id) != source->end();
|
||||
}
|
||||
|
||||
bool BuiltinLoader::Add(const char* id, const UnionBytes& source) {
|
||||
auto result = GetInstance()->source_.emplace(id, source);
|
||||
auto result = source_.write()->emplace(id, source);
|
||||
return result.second;
|
||||
}
|
||||
|
||||
Local<Object> BuiltinLoader::GetSourceObject(Local<Context> context) {
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Local<Object> out = Object::New(isolate);
|
||||
auto& source = GetInstance()->source_;
|
||||
for (auto const& x : source) {
|
||||
auto source = source_.read();
|
||||
for (auto const& x : *source) {
|
||||
Local<String> key = OneByteString(isolate, x.first.c_str(), x.first.size());
|
||||
out->Set(context, key, x.second.ToStringChecked(isolate)).FromJust();
|
||||
}
|
||||
@ -80,23 +76,21 @@ Local<Object> BuiltinLoader::GetSourceObject(Local<Context> context) {
|
||||
}
|
||||
|
||||
Local<String> BuiltinLoader::GetConfigString(Isolate* isolate) {
|
||||
return GetInstance()->config_.ToStringChecked(isolate);
|
||||
return config_.ToStringChecked(isolate);
|
||||
}
|
||||
|
||||
std::vector<std::string> BuiltinLoader::GetBuiltinIds() {
|
||||
std::vector<std::string> BuiltinLoader::GetBuiltinIds() const {
|
||||
std::vector<std::string> ids;
|
||||
ids.reserve(source_.size());
|
||||
for (auto const& x : source_) {
|
||||
auto source = source_.read();
|
||||
ids.reserve(source->size());
|
||||
for (auto const& x : *source) {
|
||||
ids.emplace_back(x.first);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void BuiltinLoader::InitializeBuiltinCategories() {
|
||||
if (builtin_categories_.is_initialized) {
|
||||
DCHECK(!builtin_categories_.can_be_required.empty());
|
||||
return;
|
||||
}
|
||||
BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
|
||||
BuiltinCategories builtin_categories;
|
||||
|
||||
std::vector<std::string> prefixes = {
|
||||
#if !HAVE_OPENSSL
|
||||
@ -110,10 +104,10 @@ void BuiltinLoader::InitializeBuiltinCategories() {
|
||||
"internal/main/"
|
||||
};
|
||||
|
||||
builtin_categories_.can_be_required.emplace(
|
||||
builtin_categories.can_be_required.emplace(
|
||||
"internal/deps/cjs-module-lexer/lexer");
|
||||
|
||||
builtin_categories_.cannot_be_required = std::set<std::string> {
|
||||
builtin_categories.cannot_be_required = std::set<std::string> {
|
||||
#if !HAVE_INSPECTOR
|
||||
"inspector", "inspector/promises", "internal/util/inspector",
|
||||
#endif // !HAVE_INSPECTOR
|
||||
@ -136,55 +130,35 @@ void BuiltinLoader::InitializeBuiltinCategories() {
|
||||
"internal/v8_prof_processor",
|
||||
};
|
||||
|
||||
for (auto const& x : source_) {
|
||||
auto source = source_.read();
|
||||
for (auto const& x : *source) {
|
||||
const std::string& id = x.first;
|
||||
for (auto const& prefix : prefixes) {
|
||||
if (prefix.length() > id.length()) {
|
||||
continue;
|
||||
}
|
||||
if (id.find(prefix) == 0 &&
|
||||
builtin_categories_.can_be_required.count(id) == 0) {
|
||||
builtin_categories_.cannot_be_required.emplace(id);
|
||||
builtin_categories.can_be_required.count(id) == 0) {
|
||||
builtin_categories.cannot_be_required.emplace(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& x : source_) {
|
||||
for (auto const& x : *source) {
|
||||
const std::string& id = x.first;
|
||||
if (0 == builtin_categories_.cannot_be_required.count(id)) {
|
||||
builtin_categories_.can_be_required.emplace(id);
|
||||
if (0 == builtin_categories.cannot_be_required.count(id)) {
|
||||
builtin_categories.can_be_required.emplace(id);
|
||||
}
|
||||
}
|
||||
|
||||
builtin_categories_.is_initialized = true;
|
||||
return builtin_categories;
|
||||
}
|
||||
|
||||
const std::set<std::string>& BuiltinLoader::GetCannotBeRequired() {
|
||||
InitializeBuiltinCategories();
|
||||
return builtin_categories_.cannot_be_required;
|
||||
}
|
||||
|
||||
const std::set<std::string>& BuiltinLoader::GetCanBeRequired() {
|
||||
InitializeBuiltinCategories();
|
||||
return builtin_categories_.can_be_required;
|
||||
}
|
||||
|
||||
bool BuiltinLoader::CanBeRequired(const char* id) {
|
||||
return GetCanBeRequired().count(id) == 1;
|
||||
}
|
||||
|
||||
bool BuiltinLoader::CannotBeRequired(const char* id) {
|
||||
return GetCannotBeRequired().count(id) == 1;
|
||||
}
|
||||
|
||||
BuiltinCodeCacheMap* BuiltinLoader::code_cache() {
|
||||
return &code_cache_;
|
||||
}
|
||||
|
||||
ScriptCompiler::CachedData* BuiltinLoader::GetCodeCache(const char* id) const {
|
||||
Mutex::ScopedLock lock(code_cache_mutex_);
|
||||
const auto it = code_cache_.find(id);
|
||||
if (it == code_cache_.end()) {
|
||||
const ScriptCompiler::CachedData* BuiltinLoader::GetCodeCache(
|
||||
const char* id) const {
|
||||
RwLock::ScopedReadLock lock(code_cache_->mutex);
|
||||
const auto it = code_cache_->map.find(id);
|
||||
if (it == code_cache_->map.end()) {
|
||||
// The module has not been compiled before.
|
||||
return nullptr;
|
||||
}
|
||||
@ -209,12 +183,13 @@ static std::string OnDiskFileName(const char* id) {
|
||||
#endif // NODE_BUILTIN_MODULES_PATH
|
||||
|
||||
MaybeLocal<String> BuiltinLoader::LoadBuiltinSource(Isolate* isolate,
|
||||
const char* id) {
|
||||
const char* id) const {
|
||||
auto source = source_.read();
|
||||
#ifdef NODE_BUILTIN_MODULES_PATH
|
||||
if (strncmp(id, "embedder_main_", strlen("embedder_main_")) == 0) {
|
||||
#endif // NODE_BUILTIN_MODULES_PATH
|
||||
const auto source_it = source_.find(id);
|
||||
if (UNLIKELY(source_it == source_.end())) {
|
||||
const auto source_it = source->find(id);
|
||||
if (UNLIKELY(source_it == source->end())) {
|
||||
fprintf(stderr, "Cannot find native builtin: \"%s\".\n", id);
|
||||
ABORT();
|
||||
}
|
||||
@ -239,14 +214,31 @@ MaybeLocal<String> BuiltinLoader::LoadBuiltinSource(Isolate* isolate,
|
||||
#endif // NODE_BUILTIN_MODULES_PATH
|
||||
}
|
||||
|
||||
namespace {
|
||||
static Mutex externalized_builtins_mutex;
|
||||
std::unordered_map<std::string, std::string> externalized_builtin_sources;
|
||||
} // namespace
|
||||
|
||||
void BuiltinLoader::AddExternalizedBuiltin(const char* id,
|
||||
const char* filename) {
|
||||
std::string source;
|
||||
int r = ReadFileSync(&source, filename);
|
||||
if (r != 0) {
|
||||
fprintf(
|
||||
stderr, "Cannot load externalized builtin: \"%s:%s\".\n", id, filename);
|
||||
ABORT();
|
||||
{
|
||||
Mutex::ScopedLock lock(externalized_builtins_mutex);
|
||||
auto it = externalized_builtin_sources.find(id);
|
||||
if (it != externalized_builtin_sources.end()) {
|
||||
source = it->second;
|
||||
}
|
||||
{
|
||||
int r = ReadFileSync(&source, filename);
|
||||
if (r != 0) {
|
||||
fprintf(stderr,
|
||||
"Cannot load externalized builtin: \"%s:%s\".\n",
|
||||
id,
|
||||
filename);
|
||||
ABORT();
|
||||
}
|
||||
externalized_builtin_sources[id] = source;
|
||||
}
|
||||
}
|
||||
|
||||
Add(id, source);
|
||||
@ -291,12 +283,12 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
|
||||
// `CompileFunction()` call below, because this function may recurse if
|
||||
// there is a syntax error during bootstrap (because the fatal exception
|
||||
// handler is invoked, which may load built-in modules).
|
||||
Mutex::ScopedLock lock(code_cache_mutex_);
|
||||
auto cache_it = code_cache_.find(id);
|
||||
if (cache_it != code_cache_.end()) {
|
||||
RwLock::ScopedLock lock(code_cache_->mutex);
|
||||
auto cache_it = code_cache_->map.find(id);
|
||||
if (cache_it != code_cache_->map.end()) {
|
||||
// Transfer ownership to ScriptCompiler::Source later.
|
||||
cached_data = cache_it->second.release();
|
||||
code_cache_.erase(cache_it);
|
||||
code_cache_->map.erase(cache_it);
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,15 +349,8 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompileInternal(
|
||||
CHECK_NOT_NULL(new_cached_data);
|
||||
|
||||
{
|
||||
Mutex::ScopedLock lock(code_cache_mutex_);
|
||||
const auto it = code_cache_.find(id);
|
||||
// TODO(joyeecheung): it's safer for each thread to have its own
|
||||
// copy of the code cache map.
|
||||
if (it == code_cache_.end()) {
|
||||
code_cache_.emplace(id, std::move(new_cached_data));
|
||||
} else {
|
||||
it->second.reset(new_cached_data.release());
|
||||
}
|
||||
RwLock::ScopedLock lock(code_cache_->mutex);
|
||||
code_cache_->map[id] = std::move(new_cached_data);
|
||||
}
|
||||
|
||||
return scope.Escape(fun);
|
||||
@ -428,9 +413,10 @@ MaybeLocal<Function> BuiltinLoader::LookupAndCompile(Local<Context> context,
|
||||
};
|
||||
}
|
||||
|
||||
MaybeLocal<Function> maybe = GetInstance()->LookupAndCompileInternal(
|
||||
context, id, ¶meters, &result);
|
||||
MaybeLocal<Function> maybe =
|
||||
LookupAndCompileInternal(context, id, ¶meters, &result);
|
||||
if (optional_realm != nullptr) {
|
||||
DCHECK_EQ(this, optional_realm->env()->builtin_loader());
|
||||
RecordResult(id, result, optional_realm);
|
||||
}
|
||||
return maybe;
|
||||
@ -506,8 +492,7 @@ MaybeLocal<Value> BuiltinLoader::CompileAndCall(Local<Context> context,
|
||||
}
|
||||
|
||||
bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
|
||||
BuiltinLoader* loader = GetInstance();
|
||||
std::vector<std::string> ids = loader->GetBuiltinIds();
|
||||
std::vector<std::string> ids = GetBuiltinIds();
|
||||
bool all_succeeded = true;
|
||||
std::string v8_tools_prefix = "internal/deps/v8/tools/";
|
||||
for (const auto& id : ids) {
|
||||
@ -515,7 +500,7 @@ bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
|
||||
continue;
|
||||
}
|
||||
v8::TryCatch bootstrapCatch(context->GetIsolate());
|
||||
USE(loader->LookupAndCompile(context, id.c_str(), nullptr));
|
||||
USE(LookupAndCompile(context, id.c_str(), nullptr));
|
||||
if (bootstrapCatch.HasCaught()) {
|
||||
per_process::Debug(DebugCategory::CODE_CACHE,
|
||||
"Failed to compile code cache for %s\n",
|
||||
@ -527,11 +512,9 @@ bool BuiltinLoader::CompileAllBuiltins(Local<Context> context) {
|
||||
return all_succeeded;
|
||||
}
|
||||
|
||||
void BuiltinLoader::CopyCodeCache(std::vector<CodeCacheInfo>* out) {
|
||||
BuiltinLoader* loader = GetInstance();
|
||||
Mutex::ScopedLock lock(loader->code_cache_mutex());
|
||||
auto in = loader->code_cache();
|
||||
for (auto const& item : *in) {
|
||||
void BuiltinLoader::CopyCodeCache(std::vector<CodeCacheInfo>* out) const {
|
||||
RwLock::ScopedReadLock lock(code_cache_->mutex);
|
||||
for (auto const& item : code_cache_->map) {
|
||||
out->push_back(
|
||||
{item.first,
|
||||
{item.second->data, item.second->data + item.second->length}});
|
||||
@ -539,24 +522,16 @@ void BuiltinLoader::CopyCodeCache(std::vector<CodeCacheInfo>* out) {
|
||||
}
|
||||
|
||||
void BuiltinLoader::RefreshCodeCache(const std::vector<CodeCacheInfo>& in) {
|
||||
BuiltinLoader* loader = GetInstance();
|
||||
Mutex::ScopedLock lock(loader->code_cache_mutex());
|
||||
auto out = loader->code_cache();
|
||||
RwLock::ScopedLock lock(code_cache_->mutex);
|
||||
for (auto const& item : in) {
|
||||
size_t length = item.data.size();
|
||||
uint8_t* buffer = new uint8_t[length];
|
||||
memcpy(buffer, item.data.data(), length);
|
||||
auto new_cache = std::make_unique<v8::ScriptCompiler::CachedData>(
|
||||
buffer, length, v8::ScriptCompiler::CachedData::BufferOwned);
|
||||
auto cache_it = out->find(item.id);
|
||||
if (cache_it != out->end()) {
|
||||
// Release the old cache and replace it with the new copy.
|
||||
cache_it->second.reset(new_cache.release());
|
||||
} else {
|
||||
out->emplace(item.id, new_cache.release());
|
||||
}
|
||||
code_cache_->map[item.id] = std::move(new_cache);
|
||||
}
|
||||
loader->has_code_cache_ = true;
|
||||
code_cache_->has_code_cache = true;
|
||||
}
|
||||
|
||||
void BuiltinLoader::GetBuiltinCategories(
|
||||
@ -566,20 +541,19 @@ void BuiltinLoader::GetBuiltinCategories(
|
||||
Local<Context> context = env->context();
|
||||
Local<Object> result = Object::New(isolate);
|
||||
|
||||
// Copy from the per-process categories
|
||||
std::set<std::string> cannot_be_required =
|
||||
GetInstance()->GetCannotBeRequired();
|
||||
std::set<std::string> can_be_required = GetInstance()->GetCanBeRequired();
|
||||
BuiltinCategories builtin_categories =
|
||||
env->builtin_loader()->GetBuiltinCategories();
|
||||
|
||||
if (!env->owns_process_state()) {
|
||||
can_be_required.erase("trace_events");
|
||||
cannot_be_required.insert("trace_events");
|
||||
builtin_categories.can_be_required.erase("trace_events");
|
||||
builtin_categories.cannot_be_required.insert("trace_events");
|
||||
}
|
||||
|
||||
Local<Value> cannot_be_required_js;
|
||||
Local<Value> can_be_required_js;
|
||||
|
||||
if (!ToV8Value(context, cannot_be_required).ToLocal(&cannot_be_required_js))
|
||||
if (!ToV8Value(context, builtin_categories.cannot_be_required)
|
||||
.ToLocal(&cannot_be_required_js))
|
||||
return;
|
||||
if (result
|
||||
->Set(context,
|
||||
@ -587,7 +561,9 @@ void BuiltinLoader::GetBuiltinCategories(
|
||||
cannot_be_required_js)
|
||||
.IsNothing())
|
||||
return;
|
||||
if (!ToV8Value(context, can_be_required).ToLocal(&can_be_required_js)) return;
|
||||
if (!ToV8Value(context, builtin_categories.can_be_required)
|
||||
.ToLocal(&can_be_required_js))
|
||||
return;
|
||||
if (result
|
||||
->Set(context,
|
||||
OneByteString(isolate, "canBeRequired"),
|
||||
@ -648,16 +624,19 @@ void BuiltinLoader::GetCacheUsage(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
void BuiltinLoader::BuiltinIdsGetter(Local<Name> property,
|
||||
const PropertyCallbackInfo<Value>& info) {
|
||||
Isolate* isolate = info.GetIsolate();
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
std::vector<std::string> ids = GetInstance()->GetBuiltinIds();
|
||||
std::vector<std::string> ids = env->builtin_loader()->GetBuiltinIds();
|
||||
info.GetReturnValue().Set(
|
||||
ToV8Value(isolate->GetCurrentContext(), ids).ToLocalChecked());
|
||||
}
|
||||
|
||||
void BuiltinLoader::ConfigStringGetter(
|
||||
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
info.GetReturnValue().Set(GetConfigString(info.GetIsolate()));
|
||||
Environment* env = Environment::GetCurrent(info);
|
||||
info.GetReturnValue().Set(
|
||||
env->builtin_loader()->GetConfigString(info.GetIsolate()));
|
||||
}
|
||||
|
||||
void BuiltinLoader::RecordResult(const char* id,
|
||||
@ -675,8 +654,8 @@ void BuiltinLoader::CompileFunction(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsString());
|
||||
node::Utf8Value id_v(realm->isolate(), args[0].As<String>());
|
||||
const char* id = *id_v;
|
||||
MaybeLocal<Function> maybe =
|
||||
GetInstance()->LookupAndCompile(realm->context(), id, realm);
|
||||
MaybeLocal<Function> maybe = realm->env()->builtin_loader()->LookupAndCompile(
|
||||
realm->context(), id, realm);
|
||||
Local<Function> fn;
|
||||
if (maybe.ToLocal(&fn)) {
|
||||
args.GetReturnValue().Set(fn);
|
||||
@ -684,8 +663,16 @@ void BuiltinLoader::CompileFunction(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
void BuiltinLoader::HasCachedBuiltins(const FunctionCallbackInfo<Value>& args) {
|
||||
args.GetReturnValue().Set(
|
||||
v8::Boolean::New(args.GetIsolate(), GetInstance()->has_code_cache_));
|
||||
auto instance = Environment::GetCurrent(args)->builtin_loader();
|
||||
RwLock::ScopedReadLock lock(instance->code_cache_->mutex);
|
||||
args.GetReturnValue().Set(v8::Boolean::New(
|
||||
args.GetIsolate(), instance->code_cache_->has_code_cache));
|
||||
}
|
||||
|
||||
void BuiltinLoader::CopySourceAndCodeCacheReferenceFrom(
|
||||
const BuiltinLoader* other) {
|
||||
code_cache_ = other->code_cache_;
|
||||
source_ = other->source_;
|
||||
}
|
||||
|
||||
void BuiltinLoader::CreatePerIsolateProperties(IsolateData* isolate_data,
|
||||
|
@ -6,10 +6,12 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "node_mutex.h"
|
||||
#include "node_threadsafe_cow.h"
|
||||
#include "node_union_bytes.h"
|
||||
#include "v8.h"
|
||||
|
||||
@ -37,6 +39,7 @@ struct CodeCacheInfo {
|
||||
// bootstrap scripts, whose source are bundled into the binary as static data.
|
||||
class NODE_EXTERN_PRIVATE BuiltinLoader {
|
||||
public:
|
||||
BuiltinLoader();
|
||||
BuiltinLoader(const BuiltinLoader&) = delete;
|
||||
BuiltinLoader& operator=(const BuiltinLoader&) = delete;
|
||||
|
||||
@ -50,62 +53,56 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {
|
||||
|
||||
// The parameters used to compile the scripts are detected based on
|
||||
// the pattern of the id.
|
||||
static v8::MaybeLocal<v8::Function> LookupAndCompile(
|
||||
v8::Local<v8::Context> context, const char* id, Realm* optional_realm);
|
||||
v8::MaybeLocal<v8::Function> LookupAndCompile(v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
Realm* optional_realm);
|
||||
|
||||
static v8::MaybeLocal<v8::Value> CompileAndCall(
|
||||
v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
int argc,
|
||||
v8::Local<v8::Value> argv[],
|
||||
Realm* optional_realm);
|
||||
v8::MaybeLocal<v8::Value> CompileAndCall(v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
int argc,
|
||||
v8::Local<v8::Value> argv[],
|
||||
Realm* optional_realm);
|
||||
|
||||
static v8::MaybeLocal<v8::Value> CompileAndCall(
|
||||
v8::Local<v8::Context> context, const char* id, Realm* realm);
|
||||
v8::MaybeLocal<v8::Value> CompileAndCall(v8::Local<v8::Context> context,
|
||||
const char* id,
|
||||
Realm* realm);
|
||||
|
||||
static v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context);
|
||||
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context);
|
||||
// Returns config.gypi as a JSON string
|
||||
static v8::Local<v8::String> GetConfigString(v8::Isolate* isolate);
|
||||
static bool Exists(const char* id);
|
||||
static bool Add(const char* id, const UnionBytes& source);
|
||||
static bool Add(const char* id, std::string_view utf8source);
|
||||
v8::Local<v8::String> GetConfigString(v8::Isolate* isolate);
|
||||
bool Exists(const char* id);
|
||||
bool Add(const char* id, const UnionBytes& source);
|
||||
bool Add(const char* id, std::string_view utf8source);
|
||||
|
||||
static bool CompileAllBuiltins(v8::Local<v8::Context> context);
|
||||
static void RefreshCodeCache(const std::vector<CodeCacheInfo>& in);
|
||||
static void CopyCodeCache(std::vector<CodeCacheInfo>* out);
|
||||
bool CompileAllBuiltins(v8::Local<v8::Context> context);
|
||||
void RefreshCodeCache(const std::vector<CodeCacheInfo>& in);
|
||||
void CopyCodeCache(std::vector<CodeCacheInfo>* out) const;
|
||||
|
||||
void CopySourceAndCodeCacheReferenceFrom(const BuiltinLoader* other);
|
||||
|
||||
private:
|
||||
// Only allow access from friends.
|
||||
friend class CodeCacheBuilder;
|
||||
|
||||
BuiltinLoader();
|
||||
static BuiltinLoader* GetInstance();
|
||||
|
||||
// Generated by tools/js2c.py as node_javascript.cc
|
||||
void LoadJavaScriptSource(); // Loads data into source_
|
||||
UnionBytes GetConfig(); // Return data for config.gypi
|
||||
|
||||
std::vector<std::string> GetBuiltinIds();
|
||||
std::vector<std::string> GetBuiltinIds() const;
|
||||
|
||||
struct BuiltinCategories {
|
||||
bool is_initialized = false;
|
||||
std::set<std::string> can_be_required;
|
||||
std::set<std::string> cannot_be_required;
|
||||
};
|
||||
void InitializeBuiltinCategories();
|
||||
const std::set<std::string>& GetCannotBeRequired();
|
||||
const std::set<std::string>& GetCanBeRequired();
|
||||
// This method builds `BuiltinCategories` from scratch every time,
|
||||
// and is therefore somewhat expensive, but also currently only being
|
||||
// used for testing, so that should not be an issue.
|
||||
BuiltinCategories GetBuiltinCategories() const;
|
||||
|
||||
bool CanBeRequired(const char* id);
|
||||
bool CannotBeRequired(const char* id);
|
||||
|
||||
BuiltinCodeCacheMap* code_cache();
|
||||
const Mutex& code_cache_mutex() const { return code_cache_mutex_; }
|
||||
|
||||
v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const;
|
||||
const v8::ScriptCompiler::CachedData* GetCodeCache(const char* id) const;
|
||||
enum class Result { kWithCache, kWithoutCache };
|
||||
v8::MaybeLocal<v8::String> LoadBuiltinSource(v8::Isolate* isolate,
|
||||
const char* id);
|
||||
const char* id) const;
|
||||
// If an exception is encountered (e.g. source code contains
|
||||
// syntax error), the returned value is empty.
|
||||
v8::MaybeLocal<v8::Function> LookupAndCompileInternal(
|
||||
@ -134,18 +131,18 @@ class NODE_EXTERN_PRIVATE BuiltinLoader {
|
||||
static void HasCachedBuiltins(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void AddExternalizedBuiltin(const char* id, const char* filename);
|
||||
void AddExternalizedBuiltin(const char* id, const char* filename);
|
||||
|
||||
static BuiltinLoader instance_;
|
||||
BuiltinCategories builtin_categories_;
|
||||
BuiltinSourceMap source_;
|
||||
BuiltinCodeCacheMap code_cache_;
|
||||
UnionBytes config_;
|
||||
ThreadsafeCopyOnWrite<BuiltinSourceMap> source_;
|
||||
|
||||
// Used to synchronize access to the code cache map
|
||||
Mutex code_cache_mutex_;
|
||||
const UnionBytes config_;
|
||||
|
||||
bool has_code_cache_;
|
||||
struct BuiltinCodeCache {
|
||||
RwLock mutex;
|
||||
BuiltinCodeCacheMap map;
|
||||
bool has_code_cache = false;
|
||||
};
|
||||
std::shared_ptr<BuiltinCodeCache> code_cache_;
|
||||
|
||||
friend class ::PerProcessTest;
|
||||
};
|
||||
|
@ -159,6 +159,11 @@ NodeMainInstance::CreateMainEnvironment(ExitCode* exit_code) {
|
||||
&(snapshot_data_->env_info),
|
||||
EnvironmentFlags::kDefaultFlags,
|
||||
{}));
|
||||
#ifdef NODE_V8_SHARED_RO_HEAP
|
||||
// TODO(addaleax): Do this as part of creating the Environment
|
||||
// once we store the SnapshotData* itself on IsolateData.
|
||||
env->builtin_loader()->RefreshCodeCache(snapshot_data_->code_cache);
|
||||
#endif
|
||||
context = Context::FromSnapshot(isolate_,
|
||||
SnapshotData::kNodeMainContextIndex,
|
||||
{DeserializeNodeInternalFields, env.get()})
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
using builtins::BuiltinLoader;
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::Function;
|
||||
@ -174,7 +173,8 @@ void Realm::DeserializeProperties(const RealmSerializeInfo* info) {
|
||||
MaybeLocal<Value> Realm::ExecuteBootstrapper(const char* id) {
|
||||
EscapableHandleScope scope(isolate());
|
||||
Local<Context> ctx = context();
|
||||
MaybeLocal<Value> result = BuiltinLoader::CompileAndCall(ctx, id, this);
|
||||
MaybeLocal<Value> result =
|
||||
env()->builtin_loader()->CompileAndCall(ctx, id, this);
|
||||
|
||||
// If there was an error during bootstrap, it must be unrecoverable
|
||||
// (e.g. max call stack exceeded). Clear the stack so that the
|
||||
|
@ -1205,10 +1205,10 @@ ExitCode SnapshotBuilder::Generate(SnapshotData* out,
|
||||
|
||||
#ifdef NODE_USE_NODE_CODE_CACHE
|
||||
// Regenerate all the code cache.
|
||||
if (!builtins::BuiltinLoader::CompileAllBuiltins(main_context)) {
|
||||
if (!env->builtin_loader()->CompileAllBuiltins(main_context)) {
|
||||
return ExitCode::kGenericUserError;
|
||||
}
|
||||
builtins::BuiltinLoader::CopyCodeCache(&(out->code_cache));
|
||||
env->builtin_loader()->CopyCodeCache(&(out->code_cache));
|
||||
for (const auto& item : out->code_cache) {
|
||||
std::string size_str = FormatSize(item.data.size());
|
||||
per_process::Debug(DebugCategory::MKSNAPSHOT,
|
||||
|
54
src/node_threadsafe_cow-inl.h
Normal file
54
src/node_threadsafe_cow-inl.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef SRC_NODE_THREADSAFE_COW_INL_H_
|
||||
#define SRC_NODE_THREADSAFE_COW_INL_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
namespace node {
|
||||
|
||||
template <typename T>
|
||||
T* CopyOnWrite<T>::write() {
|
||||
if (!data_.unique()) {
|
||||
data_ = std::make_shared<T>(*data_);
|
||||
}
|
||||
return data_.get();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ThreadsafeCopyOnWrite<T>::Read::Read(const ThreadsafeCopyOnWrite<T>* cow)
|
||||
: cow_(cow), lock_(cow->impl_->mutex) {}
|
||||
|
||||
template <typename T>
|
||||
const T& ThreadsafeCopyOnWrite<T>::Read::operator*() const {
|
||||
return cow_->impl_->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* ThreadsafeCopyOnWrite<T>::Read::operator->() const {
|
||||
return &cow_->impl_->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ThreadsafeCopyOnWrite<T>::Write::Write(ThreadsafeCopyOnWrite<T>* cow)
|
||||
: cow_(cow), impl_(cow->impl_.write()), lock_(impl_->mutex) {}
|
||||
|
||||
template <typename T>
|
||||
T& ThreadsafeCopyOnWrite<T>::Write::operator*() {
|
||||
return impl_->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* ThreadsafeCopyOnWrite<T>::Write::operator->() {
|
||||
return &impl_->data;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ThreadsafeCopyOnWrite<T>::Impl::Impl(const Impl& other) {
|
||||
RwLock::ScopedReadLock lock(other.mutex);
|
||||
data = other.data;
|
||||
}
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#endif // SRC_NODE_THREADSAFE_COW_INL_H_
|
105
src/node_threadsafe_cow.h
Normal file
105
src/node_threadsafe_cow.h
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef SRC_NODE_THREADSAFE_COW_H_
|
||||
#define SRC_NODE_THREADSAFE_COW_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#include "util.h"
|
||||
#include "uv.h"
|
||||
|
||||
#include <memory> // std::shared_ptr<T>
|
||||
#include <utility> // std::forward<T>
|
||||
|
||||
namespace node {
|
||||
|
||||
// Copy-on-write utility. Not threadsafe, i.e. there is no synchronization
|
||||
// of the copy operation with other operations.
|
||||
template <typename T>
|
||||
class CopyOnWrite final {
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit CopyOnWrite(Args&&... args)
|
||||
: data_(std::make_shared<T>(std::forward<Args>(args)...)) {}
|
||||
|
||||
CopyOnWrite(const CopyOnWrite<T>& other) = default;
|
||||
CopyOnWrite& operator=(const CopyOnWrite<T>& other) = default;
|
||||
CopyOnWrite(CopyOnWrite<T>&& other) = default;
|
||||
CopyOnWrite& operator=(CopyOnWrite<T>&& other) = default;
|
||||
|
||||
const T* read() const { return data_.get(); }
|
||||
T* write();
|
||||
|
||||
const T& operator*() const { return *read(); }
|
||||
const T* operator->() const { return read(); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> data_;
|
||||
};
|
||||
|
||||
// Threadsafe copy-on-write utility. Consumers need to use the Read and
|
||||
// Write helpers to access the target data structure.
|
||||
template <typename T>
|
||||
class ThreadsafeCopyOnWrite final {
|
||||
private:
|
||||
// Define this early since some of the public members depend on it
|
||||
// and some compilers need it to be defined first in that case.
|
||||
struct Impl {
|
||||
explicit Impl(const T& data) : data(data) {}
|
||||
explicit Impl(T&& data) : data(std::move(data)) {}
|
||||
|
||||
Impl(const Impl& other);
|
||||
Impl& operator=(const Impl& other) = delete;
|
||||
Impl(Impl&& other) = delete;
|
||||
Impl& operator=(Impl&& other) = delete;
|
||||
|
||||
RwLock mutex;
|
||||
T data;
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
ThreadsafeCopyOnWrite(Args&&... args)
|
||||
: impl_(T(std::forward<Args>(args)...)) {}
|
||||
|
||||
ThreadsafeCopyOnWrite(const ThreadsafeCopyOnWrite<T>& other) = default;
|
||||
ThreadsafeCopyOnWrite& operator=(const ThreadsafeCopyOnWrite<T>& other) =
|
||||
default;
|
||||
ThreadsafeCopyOnWrite(ThreadsafeCopyOnWrite<T>&& other) = default;
|
||||
ThreadsafeCopyOnWrite& operator=(ThreadsafeCopyOnWrite<T>&& other) = default;
|
||||
|
||||
class Read {
|
||||
public:
|
||||
explicit Read(const ThreadsafeCopyOnWrite<T>* cow);
|
||||
|
||||
const T& operator*() const;
|
||||
const T* operator->() const;
|
||||
|
||||
private:
|
||||
const ThreadsafeCopyOnWrite<T>* cow_;
|
||||
RwLock::ScopedReadLock lock_;
|
||||
};
|
||||
|
||||
class Write {
|
||||
public:
|
||||
explicit Write(ThreadsafeCopyOnWrite<T>* cow);
|
||||
|
||||
T& operator*();
|
||||
T* operator->();
|
||||
|
||||
private:
|
||||
ThreadsafeCopyOnWrite<T>* cow_;
|
||||
typename ThreadsafeCopyOnWrite<T>::Impl* impl_;
|
||||
RwLock::ScopedLock lock_;
|
||||
};
|
||||
|
||||
Read read() const { return Read(this); }
|
||||
Write write() { return Write(this); }
|
||||
|
||||
private:
|
||||
CopyOnWrite<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#endif // SRC_NODE_THREADSAFE_COW_H_
|
@ -1,4 +1,5 @@
|
||||
#include "node_builtins.h"
|
||||
#include "node_threadsafe_cow-inl.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "node_test_fixture.h"
|
||||
@ -11,7 +12,7 @@ using node::builtins::BuiltinSourceMap;
|
||||
class PerProcessTest : public ::testing::Test {
|
||||
protected:
|
||||
static const BuiltinSourceMap get_sources_for_test() {
|
||||
return BuiltinLoader::instance_.source_;
|
||||
return *BuiltinLoader().source_.read();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -57,8 +57,14 @@ namespace builtins {{
|
||||
|
||||
{0}
|
||||
|
||||
namespace {{
|
||||
const ThreadsafeCopyOnWrite<BuiltinSourceMap> global_source_map {{
|
||||
BuiltinSourceMap{{ {1} }}
|
||||
}};
|
||||
}}
|
||||
|
||||
void BuiltinLoader::LoadJavaScriptSource() {{
|
||||
{1}
|
||||
source_ = global_source_map;
|
||||
}}
|
||||
|
||||
UnionBytes BuiltinLoader::GetConfig() {{
|
||||
@ -82,7 +88,7 @@ static const uint16_t {0}[] = {{
|
||||
}};
|
||||
"""
|
||||
|
||||
INITIALIZER = 'source_.emplace("{0}", UnionBytes{{{1}, {2}}});'
|
||||
INITIALIZER = '{{"{0}", UnionBytes{{{1}, {2}}} }},'
|
||||
|
||||
CONFIG_GYPI_ID = 'config_raw'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user