From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 4 Mar 2024 11:41:18 +0900 Subject: src: preload function for Environment Backport https://github.com/nodejs/node/pull/51539 diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index eb66f0e37b517a03be20e0829863e7572042c7ed..4d5646b6067a1409df5915cc744bdc38d0191bd9 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -186,6 +186,9 @@ function setupUserModules(forceDefaultLoader = false) { initializeESMLoader(forceDefaultLoader); const CJSLoader = require('internal/modules/cjs/loader'); assert(!CJSLoader.hasLoadedAnyUserCJSModule); + if (getEmbedderOptions().hasEmbedderPreload) { + runEmbedderPreload(); + } // Do not enable preload modules if custom loaders are disabled. // For example, loader workers are responsible for doing this themselves. // And preload modules are not supported in ShadowRealm as well. @@ -745,6 +748,10 @@ function initializeFrozenIntrinsics() { } } +function runEmbedderPreload() { + internalBinding('mksnapshot').runEmbedderPreload(process, require); +} + function loadPreloadModules() { // For user code, we preload modules if `-r` is passed const preloadModules = getOptionValue('--require'); diff --git a/src/api/environment.cc b/src/api/environment.cc index 9045de3b17c93c4864a1bb1024b08f7d1ffa83be..e8669ba2a1ede090fea1869c476cac19a2aebbc6 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -556,25 +556,31 @@ NODE_EXTERN std::unique_ptr GetInspectorParentHandle( #endif } -MaybeLocal LoadEnvironment( - Environment* env, - StartExecutionCallback cb) { +MaybeLocal LoadEnvironment(Environment* env, + StartExecutionCallback cb, + EmbedderPreloadCallback preload) { env->InitializeLibuv(); env->InitializeDiagnostics(); + if (preload) { + env->set_embedder_preload(std::move(preload)); + } return StartExecution(env, cb); } MaybeLocal LoadEnvironment(Environment* env, - std::string_view main_script_source_utf8) { + std::string_view main_script_source_utf8, + EmbedderPreloadCallback preload) { CHECK_NOT_NULL(main_script_source_utf8.data()); return LoadEnvironment( - env, [&](const StartExecutionCallbackInfo& info) -> MaybeLocal { + env, + [&](const StartExecutionCallbackInfo& info) -> MaybeLocal { Local main_script = ToV8Value(env->context(), main_script_source_utf8).ToLocalChecked(); return info.run_cjs->Call( env->context(), Null(env->isolate()), 1, &main_script); - }); + }, + std::move(preload)); } Environment* GetCurrentEnvironment(Local context) { diff --git a/src/env-inl.h b/src/env-inl.h index 564de2990c09a54693686666f9ad66398ff76ab5..cc448abbc650b5abb7f555872fd8c15bc09fd643 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -438,6 +438,14 @@ inline void Environment::set_embedder_entry_point(StartExecutionCallback&& fn) { embedder_entry_point_ = std::move(fn); } +inline const EmbedderPreloadCallback& Environment::embedder_preload() const { + return embedder_preload_; +} + +inline void Environment::set_embedder_preload(EmbedderPreloadCallback fn) { + embedder_preload_ = std::move(fn); +} + inline double Environment::new_async_id() { async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1; return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter]; diff --git a/src/env.h b/src/env.h index 448075e354c760a2dbd1dd763f40b7a645730250..d6956873b1b7bdf49ed0217587729aaa974ae89f 100644 --- a/src/env.h +++ b/src/env.h @@ -985,6 +985,8 @@ class Environment : public MemoryRetainer { inline const StartExecutionCallback& embedder_entry_point() const; inline void set_embedder_entry_point(StartExecutionCallback&& fn); + inline const EmbedderPreloadCallback& embedder_preload() const; + inline void set_embedder_preload(EmbedderPreloadCallback fn); inline void set_process_exit_handler( std::function&& handler); @@ -1186,6 +1188,7 @@ class Environment : public MemoryRetainer { builtins::BuiltinLoader builtin_loader_; StartExecutionCallback embedder_entry_point_; + EmbedderPreloadCallback embedder_preload_; // Used by allocate_managed_buffer() and release_managed_buffer() to keep // track of the BackingStore for a given pointer. diff --git a/src/node.h b/src/node.h index 36da93a7b41ea450a5f288ec17b61adae46ae178..c65c96342d7d02ddb37565477f67a93ef55a78ba 100644 --- a/src/node.h +++ b/src/node.h @@ -718,12 +718,33 @@ struct StartExecutionCallbackInfo { using StartExecutionCallback = std::function(const StartExecutionCallbackInfo&)>; +using EmbedderPreloadCallback = + std::function process, + v8::Local require)>; +// Run initialization for the environment. +// +// The |preload| function, usually used by embedders to inject scripts, +// will be run by Node.js before Node.js executes the entry point. +// The function is guaranteed to run before the user land module loader running +// any user code, so it is safe to assume that at this point, no user code has +// been run yet. +// The function will be executed with preload(process, require), and the passed +// require function has access to internal Node.js modules. There is no +// stability guarantee about the internals exposed to the internal require +// function. Expect breakages when updating Node.js versions if the embedder +// imports internal modules with the internal require function. +// Worker threads created in the environment will also respect The |preload| +// function, so make sure the function is thread-safe. NODE_EXTERN v8::MaybeLocal LoadEnvironment( Environment* env, - StartExecutionCallback cb); + StartExecutionCallback cb, + EmbedderPreloadCallback preload = nullptr); NODE_EXTERN v8::MaybeLocal LoadEnvironment( - Environment* env, std::string_view main_script_source_utf8); + Environment* env, + std::string_view main_script_source_utf8, + EmbedderPreloadCallback preload = nullptr); NODE_EXTERN void FreeEnvironment(Environment* env); // Set a callback that is called when process.exit() is called from JS, diff --git a/src/node_options.cc b/src/node_options.cc index 48ce3f3b68a94fc35e5ce93a385ddbebb03741b9..39d34e18e483882a71145110962109711a1566e2 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -1290,6 +1290,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo& args) { .IsNothing()) return; + if (ret->Set(context, + FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"), + Boolean::New(isolate, env->embedder_preload() != nullptr)) + .IsNothing()) + return; + args.GetReturnValue().Set(ret); } diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index 562a47ddcc9c8e61590b7b09d84dc08ab4b3653d..431cbe1c2cb77669ceb10602a7b3ef1c2f7e8718 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -1369,6 +1369,17 @@ static void RunEmbedderEntryPoint(const FunctionCallbackInfo& args) { } } +void RunEmbedderPreload(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->embedder_preload()); + CHECK_EQ(args.Length(), 2); + Local process_obj = args[0]; + Local require_fn = args[1]; + CHECK(process_obj->IsObject()); + CHECK(require_fn->IsFunction()); + env->embedder_preload()(env, process_obj, require_fn); +} + void CompileSerializeMain(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); Local filename = args[0].As(); @@ -1493,6 +1504,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, Local target) { Isolate* isolate = isolate_data->isolate(); SetMethod(isolate, target, "runEmbedderEntryPoint", RunEmbedderEntryPoint); + SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload); SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain); SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback); SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback); @@ -1506,6 +1518,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data, void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(RunEmbedderEntryPoint); + registry->Register(RunEmbedderPreload); registry->Register(CompileSerializeMain); registry->Register(SetSerializeCallback); registry->Register(SetDeserializeCallback); diff --git a/src/node_worker.cc b/src/node_worker.cc index 900674bbe4c90e9aeb2013c06c9979864b06dcd5..52d7473b05ccb49e5fc915224b6d2972a14191da 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -63,6 +63,7 @@ Worker::Worker(Environment* env, thread_id_(AllocateEnvironmentThreadId()), name_(name), env_vars_(env_vars), + embedder_preload_(env->embedder_preload()), snapshot_data_(snapshot_data) { Debug(this, "Creating new worker instance with thread id %llu", thread_id_.id); @@ -381,8 +382,12 @@ void Worker::Run() { } Debug(this, "Created message port for worker %llu", thread_id_.id); - if (LoadEnvironment(env_.get(), StartExecutionCallback{}).IsEmpty()) + if (LoadEnvironment(env_.get(), + StartExecutionCallback{}, + std::move(embedder_preload_)) + .IsEmpty()) { return; + } Debug(this, "Loaded environment for worker %llu", thread_id_.id); } diff --git a/src/node_worker.h b/src/node_worker.h index 531e2b5287010f9206ab4fd7f4dd0f3dec9fe55c..07fd7b460654e169e8b6822474dc3cc70fcec4c0 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -114,6 +114,7 @@ class Worker : public AsyncWrap { std::unique_ptr child_port_data_; std::shared_ptr env_vars_; + EmbedderPreloadCallback embedder_preload_; // A raw flag that is used by creator and worker threads to // sync up on pre-mature termination of worker - while in the diff --git a/test/cctest/test_environment.cc b/test/cctest/test_environment.cc index 2e747c7be58922897abd0424b797f3f12a89ada1..fcffaca89cf5aa24be6e539bfb4d9d6df690a709 100644 --- a/test/cctest/test_environment.cc +++ b/test/cctest/test_environment.cc @@ -773,3 +773,31 @@ TEST_F(EnvironmentTest, RequestInterruptAtExit) { context->Exit(); } + +TEST_F(EnvironmentTest, EmbedderPreload) { + v8::HandleScope handle_scope(isolate_); + v8::Local context = node::NewContext(isolate_); + v8::Context::Scope context_scope(context); + + node::EmbedderPreloadCallback preload = [](node::Environment* env, + v8::Local process, + v8::Local require) { + CHECK(process->IsObject()); + CHECK(require->IsFunction()); + process.As() + ->Set(env->context(), + v8::String::NewFromUtf8Literal(env->isolate(), "prop"), + v8::String::NewFromUtf8Literal(env->isolate(), "preload")) + .Check(); + }; + + std::unique_ptr env( + node::CreateEnvironment(isolate_data_, context, {}, {}), + node::FreeEnvironment); + + v8::Local main_ret = + node::LoadEnvironment(env.get(), "return process.prop;", preload) + .ToLocalChecked(); + node::Utf8Value main_ret_str(isolate_, main_ret); + EXPECT_EQ(std::string(*main_ret_str), "preload"); +}