Skip to content

Commit d718625

Browse files
Gabriel Schulhoftargos
Gabriel Schulhof
authored andcommitted
src: unload addons when environment quits
This is an alternative to #23319 which attaches the loaded addons to the environment and closes them when the environment is destroyed. Backport-PR-URL: #25258 PR-URL: #24861 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 95353c7 commit d718625

File tree

8 files changed

+163
-109
lines changed

8 files changed

+163
-109
lines changed

src/env-inl.h

+10
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,16 @@ inline uv_loop_t* Environment::event_loop() const {
396396
return isolate_data()->event_loop();
397397
}
398398

399+
inline void Environment::TryLoadAddon(
400+
const char* filename,
401+
int flags,
402+
std::function<bool(binding::DLib*)> was_loaded) {
403+
loaded_addons_.emplace_back(filename, flags);
404+
if (!was_loaded(&loaded_addons_.back())) {
405+
loaded_addons_.pop_back();
406+
}
407+
}
408+
399409
inline Environment::AsyncHooks* Environment::async_hooks() {
400410
return &async_hooks_;
401411
}

src/env.cc

+5
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ Environment::~Environment() {
276276

277277
TRACE_EVENT_NESTABLE_ASYNC_END0(
278278
TRACING_CATEGORY_NODE1(environment), "Environment", this);
279+
280+
// Dereference all addons that were loaded into this environment.
281+
for (binding::DLib& addon : loaded_addons_) {
282+
addon.Close();
283+
}
279284
}
280285

281286
void Environment::Start(const std::vector<std::string>& args,

src/env.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,20 @@
3030
#endif
3131
#include "handle_wrap.h"
3232
#include "node.h"
33+
#include "node_binding.h"
3334
#include "node_http2_state.h"
3435
#include "node_options.h"
3536
#include "req_wrap.h"
3637
#include "util.h"
3738
#include "uv.h"
3839
#include "v8.h"
3940

40-
#include <list>
4141
#include <stdint.h>
42-
#include <vector>
42+
#include <functional>
43+
#include <list>
4344
#include <unordered_map>
4445
#include <unordered_set>
46+
#include <vector>
4547

4648
struct nghttp2_rcbuf;
4749

@@ -640,6 +642,9 @@ class Environment {
640642
inline v8::Isolate* isolate() const;
641643
inline uv_loop_t* event_loop() const;
642644
inline uint32_t watched_providers() const;
645+
inline void TryLoadAddon(const char* filename,
646+
int flags,
647+
std::function<bool(binding::DLib*)> was_loaded);
643648

644649
static inline Environment* from_timer_handle(uv_timer_t* handle);
645650
inline uv_timer_t* timer_handle();
@@ -926,6 +931,7 @@ class Environment {
926931
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
927932
const char* errmsg);
928933

934+
std::list<binding::DLib> loaded_addons_;
929935
v8::Isolate* const isolate_;
930936
IsolateData* const isolate_data_;
931937
uv_timer_t timer_handle_;

src/node_binding.cc

+80-102
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22
#include "node_internals.h"
33
#include "node_native_module.h"
44

5-
#if defined(__POSIX__)
6-
#include <dlfcn.h>
7-
#endif
8-
95
#if HAVE_OPENSSL
106
#define NODE_BUILTIN_OPENSSL_MODULES(V) V(crypto) V(tls_wrap)
117
#else
@@ -126,31 +122,8 @@ extern "C" void node_module_register(void* m) {
126122

127123
namespace binding {
128124

129-
class DLib {
130-
public:
131-
#ifdef __POSIX__
132-
static const int kDefaultFlags = RTLD_LAZY;
133-
#else
134-
static const int kDefaultFlags = 0;
135-
#endif
136-
137-
inline DLib(const char* filename, int flags)
138-
: filename_(filename), flags_(flags), handle_(nullptr) {}
139-
140-
inline bool Open();
141-
inline void Close();
142-
inline void* GetSymbolAddress(const char* name);
143-
144-
const std::string filename_;
145-
const int flags_;
146-
std::string errmsg_;
147-
void* handle_;
148-
#ifndef __POSIX__
149-
uv_lib_t lib_;
150-
#endif
151-
private:
152-
DISALLOW_COPY_AND_ASSIGN(DLib);
153-
};
125+
DLib::DLib(const char* filename, int flags)
126+
: filename_(filename), flags_(flags), handle_(nullptr) {}
154127

155128
#ifdef __POSIX__
156129
bool DLib::Open() {
@@ -247,87 +220,92 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
247220
}
248221

249222
node::Utf8Value filename(env->isolate(), args[1]); // Cast
250-
DLib dlib(*filename, flags);
251-
bool is_opened = dlib.Open();
252-
253-
// Objects containing v14 or later modules will have registered themselves
254-
// on the pending list. Activate all of them now. At present, only one
255-
// module per object is supported.
256-
node_module* const mp =
257-
static_cast<node_module*>(uv_key_get(&thread_local_modpending));
258-
uv_key_set(&thread_local_modpending, nullptr);
259-
260-
if (!is_opened) {
261-
Local<String> errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str());
262-
dlib.Close();
223+
env->TryLoadAddon(*filename, flags, [&](DLib* dlib) {
224+
const bool is_opened = dlib->Open();
225+
226+
// Objects containing v14 or later modules will have registered themselves
227+
// on the pending list. Activate all of them now. At present, only one
228+
// module per object is supported.
229+
node_module* const mp =
230+
static_cast<node_module*>(uv_key_get(&thread_local_modpending));
231+
uv_key_set(&thread_local_modpending, nullptr);
232+
233+
if (!is_opened) {
234+
Local<String> errmsg =
235+
OneByteString(env->isolate(), dlib->errmsg_.c_str());
236+
dlib->Close();
263237
#ifdef _WIN32
264-
// Windows needs to add the filename into the error message
265-
errmsg = String::Concat(
266-
env->isolate(), errmsg, args[1]->ToString(context).ToLocalChecked());
238+
// Windows needs to add the filename into the error message
239+
errmsg = String::Concat(
240+
env->isolate(), errmsg, args[1]->ToString(context).ToLocalChecked());
267241
#endif // _WIN32
268-
env->isolate()->ThrowException(Exception::Error(errmsg));
269-
return;
270-
}
242+
env->isolate()->ThrowException(Exception::Error(errmsg));
243+
return false;
244+
}
271245

272-
if (mp == nullptr) {
273-
if (auto callback = GetInitializerCallback(&dlib)) {
274-
callback(exports, module, context);
275-
} else if (auto napi_callback = GetNapiInitializerCallback(&dlib)) {
276-
napi_module_register_by_symbol(exports, module, context, napi_callback);
277-
} else {
278-
dlib.Close();
279-
env->ThrowError("Module did not self-register.");
246+
if (mp == nullptr) {
247+
if (auto callback = GetInitializerCallback(dlib)) {
248+
callback(exports, module, context);
249+
} else if (auto napi_callback = GetNapiInitializerCallback(dlib)) {
250+
napi_module_register_by_symbol(exports, module, context, napi_callback);
251+
} else {
252+
dlib->Close();
253+
env->ThrowError("Module did not self-register.");
254+
return false;
255+
}
256+
return true;
280257
}
281-
return;
282-
}
283258

284-
// -1 is used for N-API modules
285-
if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) {
286-
// Even if the module did self-register, it may have done so with the wrong
287-
// version. We must only give up after having checked to see if it has an
288-
// appropriate initializer callback.
289-
if (auto callback = GetInitializerCallback(&dlib)) {
290-
callback(exports, module, context);
291-
return;
259+
// -1 is used for N-API modules
260+
if ((mp->nm_version != -1) && (mp->nm_version != NODE_MODULE_VERSION)) {
261+
// Even if the module did self-register, it may have done so with the
262+
// wrong version. We must only give up after having checked to see if it
263+
// has an appropriate initializer callback.
264+
if (auto callback = GetInitializerCallback(dlib)) {
265+
callback(exports, module, context);
266+
return true;
267+
}
268+
char errmsg[1024];
269+
snprintf(errmsg,
270+
sizeof(errmsg),
271+
"The module '%s'"
272+
"\nwas compiled against a different Node.js version using"
273+
"\nNODE_MODULE_VERSION %d. This version of Node.js requires"
274+
"\nNODE_MODULE_VERSION %d. Please try re-compiling or "
275+
"re-installing\nthe module (for instance, using `npm rebuild` "
276+
"or `npm install`).",
277+
*filename,
278+
mp->nm_version,
279+
NODE_MODULE_VERSION);
280+
281+
// NOTE: `mp` is allocated inside of the shared library's memory, calling
282+
// `dlclose` will deallocate it
283+
dlib->Close();
284+
env->ThrowError(errmsg);
285+
return false;
286+
}
287+
if (mp->nm_flags & NM_F_BUILTIN) {
288+
dlib->Close();
289+
env->ThrowError("Built-in module self-registered.");
290+
return false;
292291
}
293-
char errmsg[1024];
294-
snprintf(errmsg,
295-
sizeof(errmsg),
296-
"The module '%s'"
297-
"\nwas compiled against a different Node.js version using"
298-
"\nNODE_MODULE_VERSION %d. This version of Node.js requires"
299-
"\nNODE_MODULE_VERSION %d. Please try re-compiling or "
300-
"re-installing\nthe module (for instance, using `npm rebuild` "
301-
"or `npm install`).",
302-
*filename,
303-
mp->nm_version,
304-
NODE_MODULE_VERSION);
305-
306-
// NOTE: `mp` is allocated inside of the shared library's memory, calling
307-
// `dlclose` will deallocate it
308-
dlib.Close();
309-
env->ThrowError(errmsg);
310-
return;
311-
}
312-
if (mp->nm_flags & NM_F_BUILTIN) {
313-
dlib.Close();
314-
env->ThrowError("Built-in module self-registered.");
315-
return;
316-
}
317292

318-
mp->nm_dso_handle = dlib.handle_;
319-
mp->nm_link = modlist_addon;
320-
modlist_addon = mp;
293+
mp->nm_dso_handle = dlib->handle_;
294+
mp->nm_link = modlist_addon;
295+
modlist_addon = mp;
321296

322-
if (mp->nm_context_register_func != nullptr) {
323-
mp->nm_context_register_func(exports, module, context, mp->nm_priv);
324-
} else if (mp->nm_register_func != nullptr) {
325-
mp->nm_register_func(exports, module, mp->nm_priv);
326-
} else {
327-
dlib.Close();
328-
env->ThrowError("Module has no declared entry point.");
329-
return;
330-
}
297+
if (mp->nm_context_register_func != nullptr) {
298+
mp->nm_context_register_func(exports, module, context, mp->nm_priv);
299+
} else if (mp->nm_register_func != nullptr) {
300+
mp->nm_register_func(exports, module, mp->nm_priv);
301+
} else {
302+
dlib->Close();
303+
env->ThrowError("Module has no declared entry point.");
304+
return false;
305+
}
306+
307+
return true;
308+
});
331309

332310
// Tell coverity that 'handle' should not be freed when we return.
333311
// coverity[leaked_storage]

src/node_binding.h

+34
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33

44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

6+
#if defined(__POSIX__)
7+
#include <dlfcn.h>
8+
#endif
9+
610
#include "node.h"
11+
#define NAPI_EXPERIMENTAL
712
#include "node_api.h"
13+
#include "util.h"
814
#include "uv.h"
915
#include "v8.h"
1016

@@ -46,6 +52,32 @@ extern bool node_is_initialized;
4652

4753
namespace binding {
4854

55+
class DLib {
56+
public:
57+
#ifdef __POSIX__
58+
static const int kDefaultFlags = RTLD_LAZY;
59+
#else
60+
static const int kDefaultFlags = 0;
61+
#endif
62+
63+
DLib(const char* filename, int flags);
64+
65+
bool Open();
66+
void Close();
67+
void* GetSymbolAddress(const char* name);
68+
69+
const std::string filename_;
70+
const int flags_;
71+
std::string errmsg_;
72+
void* handle_;
73+
#ifndef __POSIX__
74+
uv_lib_t lib_;
75+
#endif
76+
77+
private:
78+
DISALLOW_COPY_AND_ASSIGN(DLib);
79+
};
80+
4981
// Call _register<module_name> functions for all of
5082
// the built-in modules. Because built-in modules don't
5183
// use the __attribute__((constructor)). Need to
@@ -60,5 +92,7 @@ void DLOpen(const v8::FunctionCallbackInfo<v8::Value>& args);
6092

6193
} // namespace node
6294

95+
#include "node_binding.h"
96+
6397
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
6498
#endif // SRC_NODE_BINDING_H_

test/abort/test-addon-uv-handle-leak.js

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ if (!fs.existsSync(bindingPath))
1818
common.skip('binding not built yet');
1919

2020
if (process.argv[2] === 'child') {
21+
22+
// The worker thread loads and then unloads `bindingPath`. Because of this the
23+
// symbols in `bindingPath` are lost when the worker thread quits, but the
24+
// number of open handles in the worker thread's event loop is assessed in the
25+
// main thread afterwards, and the names of the callbacks associated with the
26+
// open handles is retrieved at that time as well. Thus, we require
27+
// `bindingPath` here so that the symbols and their names survive the life
28+
// cycle of the worker thread.
29+
require(bindingPath);
30+
2131
new Worker(`
2232
const binding = require(${JSON.stringify(bindingPath)});
2333

test/addons/at-exit/binding.cc

+14-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ using v8::Isolate;
99
using v8::Local;
1010
using v8::Object;
1111

12+
#if defined(_MSC_VER)
13+
#pragma section(".CRT$XPU", read)
14+
#define NODE_C_DTOR(fn) \
15+
NODE_CTOR_PREFIX void __cdecl fn(void); \
16+
__declspec(dllexport, allocate(".CRT$XPU")) \
17+
void (__cdecl*fn ## _)(void) = fn; \
18+
NODE_CTOR_PREFIX void __cdecl fn(void)
19+
#else
20+
#define NODE_C_DTOR(fn) \
21+
NODE_CTOR_PREFIX void fn(void) __attribute__((destructor)); \
22+
NODE_CTOR_PREFIX void fn(void)
23+
#endif
24+
1225
static char cookie[] = "yum yum";
1326
static int at_exit_cb1_called = 0;
1427
static int at_exit_cb2_called = 0;
@@ -27,7 +40,7 @@ static void at_exit_cb2(void* arg) {
2740
at_exit_cb2_called++;
2841
}
2942

30-
static void sanity_check(void) {
43+
NODE_C_DTOR(sanity_check) {
3144
assert(at_exit_cb1_called == 1);
3245
assert(at_exit_cb2_called == 2);
3346
}
@@ -36,7 +49,6 @@ void init(Local<Object> exports) {
3649
AtExit(at_exit_cb1, exports->GetIsolate());
3750
AtExit(at_exit_cb2, cookie);
3851
AtExit(at_exit_cb2, cookie);
39-
atexit(sanity_check);
4052
}
4153

4254
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

0 commit comments

Comments
 (0)