@@ -1135,7 +1135,17 @@ node_module* get_linked_module(const char* name) {
1135
1135
return FindModule (modlist_linked, name, NM_F_LINKED);
1136
1136
}
1137
1137
1138
- class DLib {
1138
+ namespace {
1139
+
1140
+ Mutex dlib_mutex;
1141
+
1142
+ class DLib ;
1143
+
1144
+ std::unordered_map<std::string, std::shared_ptr<DLib>> dlopen_cache;
1145
+ std::unordered_map<decltype(uv_lib_t ().handle), std::shared_ptr<DLib>>
1146
+ handle_to_dlib;
1147
+
1148
+ class DLib : public std ::enable_shared_from_this<DLib> {
1139
1149
public:
1140
1150
#ifdef __POSIX__
1141
1151
static const int kDefaultFlags = RTLD_LAZY;
@@ -1146,17 +1156,27 @@ class DLib {
1146
1156
inline DLib (const char * filename, int flags)
1147
1157
: filename_(filename), flags_(flags), handle_(nullptr ) {}
1148
1158
1159
+ inline DLib () {}
1160
+ inline ~DLib () {
1161
+ Close ();
1162
+ }
1163
+
1149
1164
inline bool Open ();
1150
1165
inline void Close ();
1151
1166
inline void * GetSymbolAddress (const char * name);
1167
+ inline void AddEnvironment (Environment* env);
1152
1168
1153
- const std::string filename_;
1154
- const int flags_;
1169
+ std::string filename_;
1170
+ int flags_;
1155
1171
std::string errmsg_;
1156
- void * handle_;
1172
+ void * handle_ = nullptr ;
1173
+ std::unordered_set<Environment*> users_;
1174
+ node_module* own_info = nullptr ;
1175
+
1157
1176
#ifndef __POSIX__
1158
1177
uv_lib_t lib_;
1159
1178
#endif
1179
+
1160
1180
private:
1161
1181
DISALLOW_COPY_AND_ASSIGN (DLib);
1162
1182
};
@@ -1225,18 +1245,49 @@ void InitModpendingOnce() {
1225
1245
CHECK_EQ (0 , uv_key_create (&thread_local_modpending));
1226
1246
}
1227
1247
1248
+ void DLib::AddEnvironment (Environment* env) {
1249
+ if (users_.count (env) > 0 ) return ;
1250
+ users_.insert (env);
1251
+ if (env->is_main_thread ()) return ;
1252
+ struct cleanup_hook_data {
1253
+ std::shared_ptr<DLib> info;
1254
+ Environment* env;
1255
+ };
1256
+ env->AddCleanupHook ([](void * arg) {
1257
+ Mutex::ScopedLock lock (dlib_mutex);
1258
+ std::unique_ptr<cleanup_hook_data> cbdata (
1259
+ static_cast <cleanup_hook_data*>(arg));
1260
+ std::shared_ptr<DLib> info = cbdata->info ;
1261
+ info->users_ .erase (cbdata->env );
1262
+
1263
+ if (info->users_ .empty ()) {
1264
+ // No more Environments left using this DLib -> remove filename from
1265
+ // caches.
1266
+ std::vector<std::string> filenames;
1267
+
1268
+ for (const auto & entry : dlopen_cache) {
1269
+ if (entry.second == info)
1270
+ filenames.push_back (entry.first );
1271
+ }
1272
+ for (const std::string& filename : filenames)
1273
+ dlopen_cache.erase (filename);
1274
+
1275
+ handle_to_dlib.erase (info->handle_ );
1276
+ }
1277
+ }, static_cast <void *>(new cleanup_hook_data { shared_from_this (), env }));
1278
+ }
1279
+
1280
+ } // anonymous namespace
1281
+
1228
1282
// DLOpen is process.dlopen(module, filename, flags).
1229
1283
// Used to load 'module.node' dynamically shared objects.
1230
- //
1231
- // FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict
1232
- // when two contexts try to load the same shared object. Maybe have a shadow
1233
- // cache that's a plain C list or hash table that's shared across contexts?
1234
1284
static void DLOpen (const FunctionCallbackInfo<Value>& args) {
1235
1285
Environment* env = Environment::GetCurrent (args);
1236
- auto context = env->context ();
1237
-
1238
1286
uv_once (&init_modpending_once, InitModpendingOnce);
1239
- CHECK_NULL (uv_key_get (&thread_local_modpending));
1287
+
1288
+ Local<Context> context = env->context ();
1289
+ node_module* mp;
1290
+ std::shared_ptr<DLib> dlib;
1240
1291
1241
1292
if (args.Length () < 2 ) {
1242
1293
env->ThrowError (" process.dlopen needs at least 2 arguments." );
@@ -1257,83 +1308,128 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
1257
1308
return ; // Exception pending.
1258
1309
}
1259
1310
1260
- node::Utf8Value filename (env->isolate (), args[1 ]); // Cast
1261
- DLib dlib (*filename, flags);
1262
- bool is_opened = dlib.Open ();
1311
+ // Use a do { ... } while(false) loop to be able to break out early
1312
+ // if a cached entry was found.
1313
+ do {
1314
+ CHECK_NULL (uv_key_get (&thread_local_modpending));
1315
+ Mutex::ScopedLock lock (dlib_mutex);
1316
+
1317
+ if (args.Length () < 2 ) {
1318
+ env->ThrowError (" process.dlopen needs at least 2 arguments." );
1319
+ return ;
1320
+ }
1321
+
1322
+ int32_t flags = DLib::kDefaultFlags ;
1323
+ if (args.Length () > 2 && !args[2 ]->Int32Value (context).To (&flags)) {
1324
+ return env->ThrowTypeError (" flag argument must be an integer." );
1325
+ }
1326
+
1327
+ node::Utf8Value filename (env->isolate (), args[1 ]); // Cast
1328
+ auto it = dlopen_cache.find (*filename);
1329
+
1330
+ if (it != dlopen_cache.end ()) {
1331
+ dlib = it->second ;
1332
+ mp = dlib->own_info ;
1333
+ dlib->AddEnvironment (env);
1334
+ break ;
1335
+ }
1336
+
1337
+ dlib = std::make_shared<DLib>();
1338
+ dlib->filename_ = *filename;
1339
+ dlib->flags_ = flags;
1340
+ bool is_opened = dlib->Open ();
1263
1341
1264
- // Objects containing v14 or later modules will have registered themselves
1265
- // on the pending list. Activate all of them now. At present, only one
1266
- // module per object is supported.
1267
- node_module* const mp = static_cast <node_module*>(
1268
- uv_key_get (&thread_local_modpending)) ;
1269
- uv_key_set (&thread_local_modpending, nullptr );
1342
+ if (is_opened && handle_to_dlib. count (dlib-> handle_ ) > 0 ) {
1343
+ dlib = handle_to_dlib[dlib-> handle_ ];
1344
+ mp = dlib-> own_info ;
1345
+ dlib-> AddEnvironment (env);
1346
+ break ;
1347
+ }
1270
1348
1271
- if (!is_opened) {
1272
- Local<String> errmsg = OneByteString (env->isolate (), dlib.errmsg_ .c_str ());
1273
- dlib.Close ();
1349
+ // Objects containing v14 or later modules will have registered themselves
1350
+ // on the pending list. Activate all of them now. At present, only one
1351
+ // module per object is supported.
1352
+ mp = static_cast <node_module*>(uv_key_get (&thread_local_modpending));
1353
+ uv_key_set (&thread_local_modpending, nullptr );
1354
+
1355
+ if (!is_opened) {
1356
+ Local<String> errmsg =
1357
+ OneByteString (env->isolate (), dlib->errmsg_ .c_str ());
1274
1358
#ifdef _WIN32
1275
- // Windows needs to add the filename into the error message
1276
- errmsg = String::Concat (
1277
- env->isolate (), errmsg, args[1 ]->ToString (context).ToLocalChecked ());
1359
+ // Windows needs to add the filename into the error message
1360
+ errmsg = String::Concat (env->isolate (),
1361
+ errmsg,
1362
+ args[1 ]->ToString (context).ToLocalChecked ());
1278
1363
#endif // _WIN32
1279
- env->isolate ()->ThrowException (Exception::Error (errmsg));
1280
- return ;
1281
- }
1364
+ env->isolate ()->ThrowException (Exception::Error (errmsg));
1365
+ return ;
1366
+ }
1282
1367
1283
- if (mp == nullptr ) {
1284
- if (auto callback = GetInitializerCallback (&dlib)) {
1285
- callback (exports, module, context);
1286
- } else if (auto napi_callback = GetNapiInitializerCallback (&dlib)) {
1287
- napi_module_register_by_symbol (exports, module, context, napi_callback);
1288
- } else {
1289
- dlib.Close ();
1290
- env->ThrowError (" Module did not self-register." );
1368
+ if (mp == nullptr ) {
1369
+ if (auto callback = GetInitializerCallback (dlib.get ())) {
1370
+ callback (exports, module, context);
1371
+ } else if (auto napi_callback = GetNapiInitializerCallback (dlib.get ())) {
1372
+ napi_module_register_by_symbol (exports, module, context, napi_callback);
1373
+ } else {
1374
+ dlib->Close ();
1375
+ env->ThrowError (" Module did not self-register." );
1376
+ }
1377
+ return ;
1291
1378
}
1292
- return ;
1293
- }
1294
1379
1295
- // -1 is used for N-API modules
1296
- if ((mp->nm_version != -1 ) && (mp->nm_version != NODE_MODULE_VERSION)) {
1297
- // Even if the module did self-register, it may have done so with the wrong
1298
- // version. We must only give up after having checked to see if it has an
1299
- // appropriate initializer callback.
1300
- if (auto callback = GetInitializerCallback (&dlib)) {
1301
- callback (exports, module, context);
1380
+ // -1 is used for N-API modules
1381
+ if ((mp->nm_version != -1 ) && (mp->nm_version != NODE_MODULE_VERSION)) {
1382
+ // Even if the module did self-register, it may have done so with the
1383
+ // wrong version. We must only give up after having checked to see if it
1384
+ // has an appropriate initializer callback.
1385
+ if (auto callback = GetInitializerCallback (dlib.get ())) {
1386
+ callback (exports, module, context);
1387
+ return ;
1388
+ }
1389
+ char errmsg[1024 ];
1390
+ snprintf (errmsg,
1391
+ sizeof (errmsg),
1392
+ " The module '%s'"
1393
+ " \n was compiled against a different Node.js version using"
1394
+ " \n NODE_MODULE_VERSION %d. This version of Node.js requires"
1395
+ " \n NODE_MODULE_VERSION %d. Please try re-compiling or "
1396
+ " re-installing\n the module (for instance, using `npm rebuild` "
1397
+ " or `npm install`)." ,
1398
+ *filename, mp->nm_version , NODE_MODULE_VERSION);
1399
+
1400
+ // NOTE: `mp` is allocated inside of the shared library's memory,
1401
+ // calling `dlclose` will deallocate it
1402
+ env->ThrowError (errmsg);
1302
1403
return ;
1303
1404
}
1304
- char errmsg[1024 ];
1305
- snprintf (errmsg,
1306
- sizeof (errmsg),
1307
- " The module '%s'"
1308
- " \n was compiled against a different Node.js version using"
1309
- " \n NODE_MODULE_VERSION %d. This version of Node.js requires"
1310
- " \n NODE_MODULE_VERSION %d. Please try re-compiling or "
1311
- " re-installing\n the module (for instance, using `npm rebuild` "
1312
- " or `npm install`)." ,
1313
- *filename, mp->nm_version , NODE_MODULE_VERSION);
1314
-
1315
- // NOTE: `mp` is allocated inside of the shared library's memory, calling
1316
- // `dlclose` will deallocate it
1317
- dlib.Close ();
1318
- env->ThrowError (errmsg);
1319
- return ;
1320
- }
1321
- if (mp->nm_flags & NM_F_BUILTIN) {
1322
- dlib.Close ();
1323
- env->ThrowError (" Built-in module self-registered." );
1324
- return ;
1325
- }
1326
1405
1327
- mp->nm_dso_handle = dlib.handle_ ;
1328
- mp->nm_link = modlist_addon;
1329
- modlist_addon = mp;
1406
+ if (mp->nm_flags & NM_F_BUILTIN) {
1407
+ env->ThrowError (" Built-in module self-registered." );
1408
+ return ;
1409
+ }
1410
+
1411
+ if (mp->nm_context_register_func == nullptr &&
1412
+ mp->nm_register_func == nullptr ) {
1413
+ env->ThrowError (" Module has no declared entry point." );
1414
+ return ;
1415
+ }
1416
+
1417
+ dlib->own_info = mp;
1418
+ handle_to_dlib[dlib->handle_ ] = dlib;
1419
+ dlopen_cache[*filename] = dlib;
1420
+
1421
+ dlib->AddEnvironment (env);
1422
+
1423
+ mp->nm_dso_handle = dlib->handle_ ;
1424
+ mp->nm_link = modlist_addon;
1425
+ modlist_addon = mp;
1426
+ } while (false );
1330
1427
1331
1428
if (mp->nm_context_register_func != nullptr ) {
1332
1429
mp->nm_context_register_func (exports, module, context, mp->nm_priv );
1333
1430
} else if (mp->nm_register_func != nullptr ) {
1334
1431
mp->nm_register_func (exports, module, mp->nm_priv );
1335
1432
} else {
1336
- dlib.Close ();
1337
1433
env->ThrowError (" Module has no declared entry point." );
1338
1434
return ;
1339
1435
}
0 commit comments