From af787b87ae0a481d1b124ef51d428f9e649e67ad Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 5 Nov 2019 22:50:24 +0100 Subject: [PATCH] src: allow adding linked bindings to Environment This allows manually adding linked bindings to an `Environment` instance, without having to register modules at program load in a global namespace. PR-URL: https://github.com/nodejs/node/pull/30274 Reviewed-By: Gireesh Punathil Reviewed-By: Ben Noordhuis --- src/api/environment.cc | 28 ++++++++++++++++++++ src/env-inl.h | 13 +++++++++ src/env.cc | 7 ++--- src/env.h | 7 +++++ src/node.h | 11 ++++++++ src/node_binding.cc | 24 ++++++++++------- test/cctest/test_linked_binding.cc | 42 ++++++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 12 deletions(-) diff --git a/src/api/environment.cc b/src/api/environment.cc index 846e4a873da51c..4cb2da4b860ecf 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -498,4 +498,32 @@ uv_loop_t* GetCurrentEventLoop(Isolate* isolate) { return env->event_loop(); } +void AddLinkedBinding(Environment* env, const node_module& mod) { + CHECK_NOT_NULL(env); + Mutex::ScopedLock lock(env->extra_linked_bindings_mutex()); + + node_module* prev_head = env->extra_linked_bindings_head(); + env->extra_linked_bindings()->push_back(mod); + if (prev_head != nullptr) + prev_head->nm_link = &env->extra_linked_bindings()->back(); +} + +void AddLinkedBinding(Environment* env, + const char* name, + addon_context_register_func fn, + void* priv) { + node_module mod = { + NODE_MODULE_VERSION, + NM_F_LINKED, + nullptr, // nm_dso_handle + nullptr, // nm_filename + nullptr, // nm_register_func + fn, + name, + priv, + nullptr // nm_link + }; + AddLinkedBinding(env, mod); +} + } // namespace node diff --git a/src/env-inl.h b/src/env-inl.h index 79708080d1e634..ee170b071503f8 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -868,6 +868,19 @@ inline bool Environment::is_stopping() const { return thread_stopper_.is_stopped(); } +inline std::list* Environment::extra_linked_bindings() { + return &extra_linked_bindings_; +} + +inline node_module* Environment::extra_linked_bindings_head() { + return extra_linked_bindings_.size() > 0 ? + &extra_linked_bindings_.front() : nullptr; +} + +inline const Mutex& Environment::extra_linked_bindings_mutex() const { + return extra_linked_bindings_mutex_; +} + inline performance::performance_state* Environment::performance_state() { return performance_state_.get(); } diff --git a/src/env.cc b/src/env.cc index 67cd0604b18b82..1f607e84d96b82 100644 --- a/src/env.cc +++ b/src/env.cc @@ -934,9 +934,10 @@ void Environment::stop_sub_worker_contexts() { } } -#if HAVE_INSPECTOR - -#endif // HAVE_INSPECTOR +Environment* Environment::worker_parent_env() const { + if (worker_context_ == nullptr) return nullptr; + return worker_context_->env(); +} void MemoryTracker::TrackField(const char* edge_name, const CleanupHookCallback& value, diff --git a/src/env.h b/src/env.h index e91aa5fd69ce13..761668b38e1ef3 100644 --- a/src/env.h +++ b/src/env.h @@ -1074,11 +1074,15 @@ class Environment : public MemoryRetainer { inline bool owns_inspector() const; inline uint64_t thread_id() const; inline worker::Worker* worker_context() const; + Environment* worker_parent_env() const; inline void set_worker_context(worker::Worker* context); inline void add_sub_worker_context(worker::Worker* context); inline void remove_sub_worker_context(worker::Worker* context); void stop_sub_worker_contexts(); inline bool is_stopping() const; + inline std::list* extra_linked_bindings(); + inline node_module* extra_linked_bindings_head(); + inline const Mutex& extra_linked_bindings_mutex() const; inline void ThrowError(const char* errmsg); inline void ThrowTypeError(const char* errmsg); @@ -1374,6 +1378,9 @@ class Environment : public MemoryRetainer { worker::Worker* worker_context_ = nullptr; + std::list extra_linked_bindings_; + Mutex extra_linked_bindings_mutex_; + static void RunTimers(uv_timer_t* handle); struct ExitCallback { diff --git a/src/node.h b/src/node.h index 0d0f3280e6ff26..bc55abb145d29c 100644 --- a/src/node.h +++ b/src/node.h @@ -662,6 +662,17 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); v8::Local module, \ v8::Local context) +// Allows embedders to add a binding to the current Environment* that can be +// accessed through process._linkedBinding() in the target Environment and all +// Worker threads that it creates. +// In each variant, the registration function needs to be usable at least for +// the time during which the Environment exists. +NODE_EXTERN void AddLinkedBinding(Environment* env, const node_module& mod); +NODE_EXTERN void AddLinkedBinding(Environment* env, + const char* name, + addon_context_register_func fn, + void* priv); + /* Called after the event loop exits but before the VM is disposed. * Callbacks are run in reverse order of registration, i.e. newest first. * diff --git a/src/node_binding.cc b/src/node_binding.cc index ffce116efba62a..848d228b87dcd2 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -558,13 +558,6 @@ inline struct node_module* FindModule(struct node_module* list, return mp; } -node_module* get_internal_module(const char* name) { - return FindModule(modlist_internal, name, NM_F_INTERNAL); -} -node_module* get_linked_module(const char* name) { - return FindModule(modlist_linked, name, NM_F_LINKED); -} - static Local InitModule(Environment* env, node_module* mod, Local module) { @@ -592,7 +585,7 @@ void GetInternalBinding(const FunctionCallbackInfo& args) { node::Utf8Value module_v(env->isolate(), module); Local exports; - node_module* mod = get_internal_module(*module_v); + node_module* mod = FindModule(modlist_internal, *module_v, NM_F_INTERNAL); if (mod != nullptr) { exports = InitModule(env, mod, module); } else if (!strcmp(*module_v, "constants")) { @@ -625,7 +618,20 @@ void GetLinkedBinding(const FunctionCallbackInfo& args) { Local module_name = args[0].As(); node::Utf8Value module_name_v(env->isolate(), module_name); - node_module* mod = get_linked_module(*module_name_v); + const char* name = *module_name_v; + node_module* mod = nullptr; + + // Iterate from here to the nearest non-Worker Environment to see if there's + // a linked binding defined locally rather than through the global list. + Environment* cur_env = env; + while (mod == nullptr && cur_env != nullptr) { + Mutex::ScopedLock lock(cur_env->extra_linked_bindings_mutex()); + mod = FindModule(cur_env->extra_linked_bindings_head(), name, NM_F_LINKED); + cur_env = cur_env->worker_parent_env(); + } + + if (mod == nullptr) + mod = FindModule(modlist_linked, name, NM_F_LINKED); if (mod == nullptr) { char errmsg[1024]; diff --git a/test/cctest/test_linked_binding.cc b/test/cctest/test_linked_binding.cc index 90688655527211..6724402c55aef1 100644 --- a/test/cctest/test_linked_binding.cc +++ b/test/cctest/test_linked_binding.cc @@ -41,3 +41,45 @@ TEST_F(LinkedBindingTest, SimpleTest) { CHECK_NOT_NULL(*utf8val); CHECK_EQ(strcmp(*utf8val, "value"), 0); } + +void InitializeLocalBinding(v8::Local exports, + v8::Local module, + v8::Local context, + void* priv) { + ++*static_cast(priv); + v8::Isolate* isolate = context->GetIsolate(); + exports->Set( + context, + v8::String::NewFromOneByte(isolate, + reinterpret_cast("key"), + v8::NewStringType::kNormal).ToLocalChecked(), + v8::String::NewFromOneByte(isolate, + reinterpret_cast("value"), + v8::NewStringType::kNormal).ToLocalChecked()) + .FromJust(); +} + +TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingTest) { + const v8::HandleScope handle_scope(isolate_); + const Argv argv; + Env test_env {handle_scope, argv}; + + int calls = 0; + AddLinkedBinding(*test_env, "local_linked", InitializeLocalBinding, &calls); + + v8::Local context = isolate_->GetCurrentContext(); + + const char* run_script = + "process._linkedBinding('local_linked').key"; + v8::Local script = v8::Script::Compile( + context, + v8::String::NewFromOneByte(isolate_, + reinterpret_cast(run_script), + v8::NewStringType::kNormal).ToLocalChecked()) + .ToLocalChecked(); + v8::Local completion_value = script->Run(context).ToLocalChecked(); + v8::String::Utf8Value utf8val(isolate_, completion_value); + CHECK_NOT_NULL(*utf8val); + CHECK_EQ(strcmp(*utf8val, "value"), 0); + CHECK_EQ(calls, 1); +}