| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 6f9a764 commit a9a2c58
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -251,6 +251,11 @@ down. If necessary, such hooks can be removed using | |||
| 251 | 251 | `RemoveEnvironmentCleanupHook()` before they are run, which has the same | |
| 252 | 252 | signature. | |
| 253 | 253 | ||
| 254 | + In order to be loaded from multiple Node.js environments, | ||
| 255 | + such as a main thread and a Worker thread, an add-on needs to either: | ||
| 256 | + - Be an N-API addon, or | ||
| 257 | + - Be declared as context-aware using `NODE_MODULE_INIT()` as described above | ||
| 258 | + | ||
| 254 | 259 | ### Building | |
| 255 | 260 | ||
| 256 | 261 | Once the source code has been written, it must be compiled into the binary | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -360,11 +360,8 @@ Notable differences inside a Worker environment are: | |||
| 360 | 360 | being invoked. | |
| 361 | 361 | - IPC channels from parent processes are not accessible. | |
| 362 | 362 | - The [`trace_events`][] module is not supported. | |
| 363 | - | ||
| 364 | - Currently, the following differences also exist until they are addressed: | ||
| 365 | - | ||
| 366 | - - The [`inspector`][] module is not available yet. | ||
| 367 | - - Native addons are not supported yet. | ||
| 363 | + - Native add-ons can only be loaded from multiple threads if they fulfill | ||
| 364 | + [certain conditions][Addons worker support]. | ||
| 368 | 365 | ||
| 369 | 366 | Creating `Worker` instances inside of other `Worker`s is possible. | |
| 370 | 367 | ||
@@ -580,7 +577,6 @@ active handle in the event system. If the worker is already `unref()`ed calling | |||
| 580 | 577 | [`WebAssembly.Module`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module | |
| 581 | 578 | [`Worker`]: #worker_threads_class_worker | |
| 582 | 579 | [`cluster` module]: cluster.html | |
| 583 | - [`inspector`]: inspector.html | ||
| 584 | 580 | [`port.on('message')`]: #worker_threads_event_message | |
| 585 | 581 | [`port.postMessage()`]: #worker_threads_port_postmessage_value_transferlist | |
| 586 | 582 | [`process.abort()`]: process.html#process_process_abort | |
@@ -602,6 +598,7 @@ active handle in the event system. If the worker is already `unref()`ed calling | |||
| 602 | 598 | [`worker.postMessage()`]: #worker_threads_worker_postmessage_value_transferlist | |
| 603 | 599 | [`worker.terminate()`]: #worker_threads_worker_terminate_callback | |
| 604 | 600 | [`worker.threadId`]: #worker_threads_worker_threadid_1 | |
| 601 | + [Addons worker support]: addons.html#addons_worker_support | ||
| 605 | 602 | [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm | |
| 606 | 603 | [Signals events]: process.html#process_signal_events | |
| 607 | 604 | [Web Workers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -484,7 +484,7 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports, | |||
| 484 | 484 | void napi_module_register(napi_module* mod) { | |
| 485 | 485 | node::node_module* nm = new node::node_module { | |
| 486 | 486 | -1, | |
| 487 | - mod->nm_flags, | ||
| 487 | + mod->nm_flags | NM_F_DELETEME, | ||
| 488 | 488 | nullptr, | |
| 489 | 489 | mod->nm_filename, | |
| 490 | 490 | nullptr, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | #include "env-inl.h" | |
| 3 | 3 | #include "node_native_module.h" | |
| 4 | 4 | #include "util.h" | |
| 5 | + #include <atomic> | ||
| 5 | 6 | ||
| 6 | 7 | #if HAVE_OPENSSL | |
| 7 | 8 | #define NODE_BUILTIN_OPENSSL_MODULES(V) V(crypto) V(tls_wrap) | |
@@ -96,6 +97,130 @@ | |||
| 96 | 97 | NODE_BUILTIN_MODULES(V) | |
| 97 | 98 | #undef V | |
| 98 | 99 | ||
| 100 | + #ifdef _AIX | ||
| 101 | + // On AIX, dlopen() behaves differently from other operating systems, in that | ||
| 102 | + // it returns unique values from each call, rather than identical values, when | ||
| 103 | + // loading the same handle. | ||
| 104 | + // We try to work around that by providing wrappers for the dlopen() family of | ||
| 105 | + // functions, and using st_dev and st_ino for the file that is to be loaded | ||
| 106 | + // as keys for a cache. | ||
| 107 | + | ||
| 108 | + namespace node { | ||
| 109 | + namespace dlwrapper { | ||
| 110 | + | ||
| 111 | + struct dl_wrap { | ||
| 112 | + uint64_t st_dev; | ||
| 113 | + uint64_t st_ino; | ||
| 114 | + uint64_t refcount; | ||
| 115 | + void* real_handle; | ||
| 116 | + | ||
| 117 | + struct hash { | ||
| 118 | + size_t operator()(const dl_wrap* wrap) const { | ||
| 119 | + return std::hash<uint64_t>()(wrap->st_dev) ^ | ||
| 120 | + std::hash<uint64_t>()(wrap->st_ino); | ||
| 121 | + } | ||
| 122 | + }; | ||
| 123 | + | ||
| 124 | + struct equal { | ||
| 125 | + bool operator()(const dl_wrap* a, | ||
| 126 | + const dl_wrap* b) const { | ||
| 127 | + return a->st_dev == b->st_dev && a->st_ino == b->st_ino; | ||
| 128 | + } | ||
| 129 | + }; | ||
| 130 | + }; | ||
| 131 | + | ||
| 132 | + static Mutex dlhandles_mutex; | ||
| 133 | + static std::unordered_set<dl_wrap*, dl_wrap::hash, dl_wrap::equal> | ||
| 134 | + dlhandles; | ||
| 135 | + static thread_local std::string dlerror_storage; | ||
| 136 | + | ||
| 137 | + char* wrapped_dlerror() { | ||
| 138 | + return &dlerror_storage[0]; | ||
| 139 | + } | ||
| 140 | + | ||
| 141 | + void* wrapped_dlopen(const char* filename, int flags) { | ||
| 142 | + CHECK_NOT_NULL(filename); // This deviates from the 'real' dlopen(). | ||
| 143 | + Mutex::ScopedLock lock(dlhandles_mutex); | ||
| 144 | + | ||
| 145 | + uv_fs_t req; | ||
| 146 | + OnScopeLeave cleanup([&]() { uv_fs_req_cleanup(&req); }); | ||
| 147 | + int rc = uv_fs_stat(nullptr, &req, filename, nullptr); | ||
| 148 | + | ||
| 149 | + if (rc != 0) { | ||
| 150 | + dlerror_storage = uv_strerror(rc); | ||
| 151 | + return nullptr; | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + dl_wrap search = { | ||
| 155 | + req.statbuf.st_dev, | ||
| 156 | + req.statbuf.st_ino, | ||
| 157 | + 0, nullptr | ||
| 158 | + }; | ||
| 159 | + | ||
| 160 | + auto it = dlhandles.find(&search); | ||
| 161 | + if (it != dlhandles.end()) { | ||
| 162 | + (*it)->refcount++; | ||
| 163 | + return *it; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + void* real_handle = dlopen(filename, flags); | ||
| 167 | + if (real_handle == nullptr) { | ||
| 168 | + dlerror_storage = dlerror(); | ||
| 169 | + return nullptr; | ||
| 170 | + } | ||
| 171 | + dl_wrap* wrap = new dl_wrap(); | ||
| 172 | + wrap->st_dev = req.statbuf.st_dev; | ||
| 173 | + wrap->st_ino = req.statbuf.st_ino; | ||
| 174 | + wrap->refcount = 1; | ||
| 175 | + wrap->real_handle = real_handle; | ||
| 176 | + dlhandles.insert(wrap); | ||
| 177 | + return wrap; | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + int wrapped_dlclose(void* handle) { | ||
| 181 | + Mutex::ScopedLock lock(dlhandles_mutex); | ||
| 182 | + dl_wrap* wrap = static_cast<dl_wrap*>(handle); | ||
| 183 | + int ret = 0; | ||
| 184 | + CHECK_GE(wrap->refcount, 1); | ||
| 185 | + if (--wrap->refcount == 0) { | ||
| 186 | + ret = dlclose(wrap->real_handle); | ||
| 187 | + if (ret != 0) dlerror_storage = dlerror(); | ||
| 188 | + dlhandles.erase(wrap); | ||
| 189 | + delete wrap; | ||
| 190 | + } | ||
| 191 | + return ret; | ||
| 192 | + } | ||
| 193 | + | ||
| 194 | + void* wrapped_dlsym(void* handle, const char* symbol) { | ||
| 195 | + if (handle == RTLD_DEFAULT || handle == RTLD_NEXT) | ||
| 196 | + return dlsym(handle, symbol); | ||
| 197 | + dl_wrap* wrap = static_cast<dl_wrap*>(handle); | ||
| 198 | + return dlsym(wrap->real_handle, symbol); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + #define dlopen node::dlwrapper::wrapped_dlopen | ||
| 202 | + #define dlerror node::dlwrapper::wrapped_dlerror | ||
| 203 | + #define dlclose node::dlwrapper::wrapped_dlclose | ||
| 204 | + #define dlsym node::dlwrapper::wrapped_dlsym | ||
| 205 | + | ||
| 206 | + } // namespace dlwrapper | ||
| 207 | + } // namespace node | ||
| 208 | + | ||
| 209 | + #endif // _AIX | ||
| 210 | + | ||
| 211 | + #ifdef __linux__ | ||
| 212 | + static bool libc_may_be_musl() { | ||
| 213 | + static std::atomic_bool retval; // Cache the return value. | ||
| 214 | + static std::atomic_bool has_cached_retval { false }; | ||
| 215 | + if (has_cached_retval) return retval; | ||
| 216 | + retval = dlsym(RTLD_DEFAULT, "gnu_get_libc_version") == nullptr; | ||
| 217 | + has_cached_retval = true; | ||
| 218 | + return retval; | ||
| 219 | + } | ||
| 220 | + #else // __linux__ | ||
| 221 | + static bool libc_may_be_musl() { return false; } | ||
| 222 | + #endif // __linux__ | ||
| 223 | + | ||
| 99 | 224 | namespace node { | |
| 100 | 225 | ||
| 101 | 226 | using v8::Context; | |
@@ -110,7 +235,6 @@ using v8::Value; | |||
| 110 | 235 | // Globals per process | |
| 111 | 236 | static node_module* modlist_internal; | |
| 112 | 237 | static node_module* modlist_linked; | |
| 113 | - static node_module* modlist_addon; | ||
| 114 | 238 | static uv_once_t init_modpending_once = UV_ONCE_INIT; | |
| 115 | 239 | static uv_key_t thread_local_modpending; | |
| 116 | 240 | ||
@@ -136,6 +260,57 @@ extern "C" void node_module_register(void* m) { | |||
| 136 | 260 | ||
| 137 | 261 | namespace binding { | |
| 138 | 262 | ||
| 263 | + static struct global_handle_map_t { | ||
| 264 | + public: | ||
| 265 | + void set(void* handle, node_module* mod) { | ||
| 266 | + CHECK_NE(handle, nullptr); | ||
| 267 | + Mutex::ScopedLock lock(mutex_); | ||
| 268 | + | ||
| 269 | + map_[handle].module = mod; | ||
| 270 | + // We need to store this flag internally to avoid a chicken-and-egg problem | ||
| 271 | + // during cleanup. By the time we actually use the flag's value, | ||
| 272 | + // the shared object has been unloaded, and its memory would be gone, | ||
| 273 | + // making it impossible to access fields of `mod` -- | ||
| 274 | + // unless `mod` *is* dynamically allocated, but we cannot know that | ||
| 275 | + // without checking the flag. | ||
| 276 | + map_[handle].wants_delete_module = mod->nm_flags & NM_F_DELETEME; | ||
| 277 | + map_[handle].refcount++; | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + node_module* get_and_increase_refcount(void* handle) { | ||
| 281 | + CHECK_NE(handle, nullptr); | ||
| 282 | + Mutex::ScopedLock lock(mutex_); | ||
| 283 | + | ||
| 284 | + auto it = map_.find(handle); | ||
| 285 | + if (it == map_.end()) return nullptr; | ||
| 286 | + it->second.refcount++; | ||
| 287 | + return it->second.module; | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + void erase(void* handle) { | ||
| 291 | + CHECK_NE(handle, nullptr); | ||
| 292 | + Mutex::ScopedLock lock(mutex_); | ||
| 293 | + | ||
| 294 | + auto it = map_.find(handle); | ||
| 295 | + if (it == map_.end()) return; | ||
| 296 | + CHECK_GE(it->second.refcount, 1); | ||
| 297 | + if (--it->second.refcount == 0) { | ||
| 298 | + if (it->second.wants_delete_module) | ||
| 299 | + delete it->second.module; | ||
| 300 | + map_.erase(handle); | ||
| 301 | + } | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + private: | ||
| 305 | + Mutex mutex_; | ||
| 306 | + struct Entry { | ||
| 307 | + unsigned int refcount; | ||
| 308 | + bool wants_delete_module; | ||
| 309 | + node_module* module; | ||
| 310 | + }; | ||
| 311 | + std::unordered_map<void*, Entry> map_; | ||
| 312 | + } global_handle_map; | ||
| 313 | + | ||
| 139 | 314 | DLib::DLib(const char* filename, int flags) | |
| 140 | 315 | : filename_(filename), flags_(flags), handle_(nullptr) {} | |
| 141 | 316 | ||
@@ -149,7 +324,21 @@ bool DLib::Open() { | |||
| 149 | 324 | ||
| 150 | 325 | void DLib::Close() { | |
| 151 | 326 | if (handle_ == nullptr) return; | |
| 152 | - dlclose(handle_); | ||
| 327 | + | ||
| 328 | + if (libc_may_be_musl()) { | ||
| 329 | + // musl libc implements dlclose() as a no-op which returns 0. | ||
| 330 | + // As a consequence, trying to re-load a previously closed addon at a later | ||
| 331 | + // point will not call its static constructors, which Node.js uses. | ||
| 332 | + // Therefore, when we may be using musl libc, we assume that the shared | ||
| 333 | + // object exists indefinitely and keep it in our handle map. | ||
| 334 | + return; | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + int err = dlclose(handle_); | ||
| 338 | + if (err == 0) { | ||
| 339 | + if (has_entry_in_global_handle_map_) | ||
| 340 | + global_handle_map.erase(handle_); | ||
| 341 | + } | ||
| 153 | 342 | handle_ = nullptr; | |
| 154 | 343 | } | |
| 155 | 344 | ||
@@ -170,6 +359,8 @@ bool DLib::Open() { | |||
| 170 | 359 | ||
| 171 | 360 | void DLib::Close() { | |
| 172 | 361 | if (handle_ == nullptr) return; | |
| 362 | + if (has_entry_in_global_handle_map_) | ||
| 363 | + global_handle_map.erase(handle_); | ||
| 173 | 364 | uv_dlclose(&lib_); | |
| 174 | 365 | handle_ = nullptr; | |
| 175 | 366 | } | |
@@ -181,6 +372,16 @@ void* DLib::GetSymbolAddress(const char* name) { | |||
| 181 | 372 | } | |
| 182 | 373 | #endif // !__POSIX__ | |
| 183 | 374 | ||
| 375 | + void DLib::SaveInGlobalHandleMap(node_module* mp) { | ||
| 376 | + has_entry_in_global_handle_map_ = true; | ||
| 377 | + global_handle_map.set(handle_, mp); | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + node_module* DLib::GetSavedModuleFromGlobalHandleMap() { | ||
| 381 | + has_entry_in_global_handle_map_ = true; | ||
| 382 | + return global_handle_map.get_and_increase_refcount(handle_); | ||
| 383 | + } | ||
| 384 | + | ||
| 184 | 385 | using InitializerCallback = void (*)(Local<Object> exports, | |
| 185 | 386 | Local<Value> module, | |
| 186 | 387 | Local<Context> context); | |
@@ -235,12 +436,15 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) { | |||
| 235 | 436 | ||
| 236 | 437 | node::Utf8Value filename(env->isolate(), args[1]); // Cast | |
| 237 | 438 | env->TryLoadAddon(*filename, flags, [&](DLib* dlib) { | |
| 439 | + static Mutex dlib_load_mutex; | ||
| 440 | + Mutex::ScopedLock lock(dlib_load_mutex); | ||
| 441 | + | ||
| 238 | 442 | const bool is_opened = dlib->Open(); | |
| 239 | 443 | ||
| 240 | 444 | // Objects containing v14 or later modules will have registered themselves | |
| 241 | 445 | // on the pending list. Activate all of them now. At present, only one | |
| 242 | 446 | // module per object is supported. | |
| 243 | - node_module* const mp = | ||
| 447 | + node_module* mp = | ||
| 244 | 448 | static_cast<node_module*>(uv_key_get(&thread_local_modpending)); | |
| 245 | 449 | uv_key_set(&thread_local_modpending, nullptr); | |
| 246 | 450 | ||
@@ -257,17 +461,24 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) { | |||
| 257 | 461 | return false; | |
| 258 | 462 | } | |
| 259 | 463 | ||
| 260 | - if (mp == nullptr) { | ||
| 464 | + if (mp != nullptr) { | ||
| 465 | + mp->nm_dso_handle = dlib->handle_; | ||
| 466 | + dlib->SaveInGlobalHandleMap(mp); | ||
| 467 | + } else { | ||
| 261 | 468 | if (auto callback = GetInitializerCallback(dlib)) { | |
| 262 | 469 | callback(exports, module, context); | |
| 470 | + return true; | ||
| 263 | 471 | } else if (auto napi_callback = GetNapiInitializerCallback(dlib)) { | |
| 264 | 472 | napi_module_register_by_symbol(exports, module, context, napi_callback); | |
| 473 | + return true; | ||
| 265 | 474 | } else { | |
| 266 | - dlib->Close(); | ||
| 267 | - env->ThrowError("Module did not self-register."); | ||
| 268 | - return false; | ||
| 475 | + mp = dlib->GetSavedModuleFromGlobalHandleMap(); | ||
| 476 | + if (mp == nullptr || mp->nm_context_register_func == nullptr) { | ||
| 477 | + dlib->Close(); | ||
| 478 | + env->ThrowError("Module did not self-register."); | ||
| 479 | + return false; | ||
| 480 | + } | ||
| 269 | 481 | } | |
| 270 | - return true; | ||
| 271 | 482 | } | |
| 272 | 483 | ||
| 273 | 484 | // -1 is used for N-API modules | |
@@ -300,10 +511,8 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) { | |||
| 300 | 511 | } | |
| 301 | 512 | CHECK_EQ(mp->nm_flags & NM_F_BUILTIN, 0); | |
| 302 | 513 | ||
| 303 | - mp->nm_dso_handle = dlib->handle_; | ||
| 304 | - mp->nm_link = modlist_addon; | ||
| 305 | - modlist_addon = mp; | ||
| 306 | - | ||
| 514 | + // Do not keep the lock while running userland addon loading code. | ||
| 515 | + Mutex::ScopedUnlock unlock(lock); | ||
| 307 | 516 | if (mp->nm_context_register_func != nullptr) { | |
| 308 | 517 | mp->nm_context_register_func(exports, module, context, mp->nm_priv); | |
| 309 | 518 | } else if (mp->nm_register_func != nullptr) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,7 @@ enum { | |||
| 18 | 18 | NM_F_BUILTIN = 1 << 0, // Unused. | |
| 19 | 19 | NM_F_LINKED = 1 << 1, | |
| 20 | 20 | NM_F_INTERNAL = 1 << 2, | |
| 21 | + NM_F_DELETEME = 1 << 3, | ||
| 21 | 22 | }; | |
| 22 | 23 | ||
| 23 | 24 | #define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \ | |
@@ -62,6 +63,8 @@ class DLib { | |||
| 62 | 63 | bool Open(); | |
| 63 | 64 | void Close(); | |
| 64 | 65 | void* GetSymbolAddress(const char* name); | |
| 66 | + void SaveInGlobalHandleMap(node_module* mp); | ||
| 67 | + node_module* GetSavedModuleFromGlobalHandleMap(); | ||
| 65 | 68 | ||
| 66 | 69 | const std::string filename_; | |
| 67 | 70 | const int flags_; | |
@@ -70,6 +73,7 @@ class DLib { | |||
| 70 | 73 | #ifndef __POSIX__ | |
| 71 | 74 | uv_lib_t lib_; | |
| 72 | 75 | #endif | |
| 76 | + bool has_entry_in_global_handle_map_ = false; | ||
| 73 | 77 | ||
| 74 | 78 | private: | |
| 75 | 79 | DISALLOW_COPY_AND_ASSIGN(DLib); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments