-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add process.dlopenFlags #12794
Add process.dlopenFlags #12794
Conversation
doc/api/os.md
Outdated
### dlopen Constants | ||
|
||
The following signal constants are exported by `os.constants.dlopen`. | ||
See `man 3 dlopen` for detailed informatio. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/informatio/information/
Also, it might be better to use the automatic man page linking by formatting the name according to how it's done elsewhere (e.g. in the fs
docs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
doc/api/os.md
Outdated
<th>Constant</th> | ||
</tr> | ||
<tr> | ||
<td><code>RTLD_LAZY</code></td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing descriptions for these constants?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the descriptions were too detailed and complex, and was expecting the reader could refer to the manual instead? It's a 1:1 mapping of the dlopen
flags, so there's not any secret sauce.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the idea is to refer the user to a manual, then having these as links to that manual would be ideal :-) ... still, a very brief one sentence description would be preferred (I know we're fairly inconsistent with that in the docs and we need to get better at it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/node_constants.cc
Outdated
@@ -1287,6 +1321,10 @@ void DefineConstants(v8::Isolate* isolate, Local<Object> target) { | |||
CHECK(zlib_constants->SetPrototype(env->context(), | |||
Null(env->isolate())).FromJust()); | |||
|
|||
Local<Object> dlopen_constants = Object::New(isolate); | |||
CHECK(dlopen_constants->SetPrototype(env->context(), | |||
Null(env->isolate())).FromJust()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line should be indented to align with the first argument to SetPrototype()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
src/node.cc
Outdated
|
||
#ifdef __POSIX__ | ||
// If no flag is passed, fallback to default | ||
if (args.Length() < 3 || args[2]->IsUndefined() || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about falling back to the old behavior if it's simply not an integer instead of just undefined
or null
? That might make more sense for nonsensical values that could be passed (e.g. object, boolean, etc.).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a second thought:
- Shouldn't we better check the type and fail if it's not an integer? The only accepted values/types would be integers, boolean, null and undefined.
dlopen
can't be called with a zero flag, so I was treating the0
as if the user requested passednull
, allowing to fallback to default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose that's fine as well.
doc/api/process.md
Outdated
@@ -683,6 +683,22 @@ process.on('warning', (warning) => { | |||
|
|||
If `warning` is passed as an `Error` object, the `options` argument is ignored. | |||
|
|||
## process.dlopenFlags | |||
<!-- YAML | |||
added: v8.0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use added: REPLACEME
:)
src/node.cc
Outdated
#ifdef __POSIX__ | ||
// If no flag is passed, fallback to default | ||
if (args.Length() < 3 || args[2]->IsUndefined() || | ||
args[2]->IsNull() || args[2]->IntegerValue() == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntegerValue()
without arguments is deprecated, so it would be good if you could either use the overload that returns a Maybe<int64_t>
and pass along the error if one occurs, or alternatively throw a type error if args[2]->IsInt32()
fails and then use args[2].As<Int32>()->Value()
to get the flags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about something like:
int flags = 0;
if (args.Length() > 2) {
if (!args[2]->IsInt32() && !args[2]->IsUndefined()) {
return env->ThrowTypeError("dlopen flags must be undefined or integer.");
}
if (args[2]->IsInt32())
flags = args[2]->Int32Value();
}
if (!flags) {
// If no flag is passed, fallback to default
is_dlopen_error = uv_dlopen(*filename, &lib);
} else {
[the dlopen with flags]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: should this be an API or a command line flag? What are the risks involved with different modules requiring different flags? Perhaps this is something that should be exposed off the module specific require
instead (e.g. require.setdlopenFlags()
)? Just trying to think through the various ramifications.
doc/api/process.md
Outdated
process.dlopenFlags = os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL; | ||
var foo = require('foo'); | ||
process.dlopenFlags = null; | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if a property is the right approach. I'd prefer a function (e.g. setdlopenFlags()
/getdlopenFlags()
that would include type checking on the value passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine either way. Whatever you guys decide it works better.
I don't how doable a About the risks of passing different flags to different modules, I don't think this is a concern, if used properly. We'd want to pass a flag to load a specific addon and then go back to default, like this:
Shared libraries should have properly named symbols, to avoid namespace collision. In any case, we are just providing a mechanism, it's up to developers to avoid shooting themselves. Not sure this answers your question. |
^--- I had same questions as @jasnell from above. This seems it should be localized to a specific addon load, no? |
@sam-github @jasnell Yes, it should be localized to one addon. However, it sounds that would require adding dlopenflags awareness to the |
Java virtual machines were plagued by namespace collision introduced through RTLD_GLOBAL - for example, an incoming symbol through dlopen could collide with an existing symbol in the already loaded images, and load order and other dependancy constraints come into play. To make things worse, JVM could be launched as a shared library from an existing process with already loaded libraries and symbols, and then the load order reverses, and the behavior too. On the other hand, I recognize the use case, and the need to be able to expose loaded symbols outside of the library confinement. The current approach of platform default not only is restrictive, but also is platform dependant. In my opinion, (i) the option to load addons with RTLD_GLOBAL should be localized on a per module basis to reduce side effects(ii) the functionality should be made platform neutral to reduce behavioral inconsistencies. |
1991d15
to
d343194
Compare
Hey guys, First of all, thanks for the great feedback. I'm having lots of fun hacking node! Just updated the branch, exploring the |
lib/module.js
Outdated
@@ -447,6 +447,10 @@ Module._load = function(request, parent, isMain) { | |||
} | |||
|
|||
var module = new Module(filename, parent); | |||
if (opts !== undefined && opts.dlopenFlags) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps if (opts !== null && typeof opts === 'object' && opts.dlopenFlags)
to make the check more precise.
as it is here, `require('foo', null) would throw an error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that 'object' type is checked below. I was expecting Module._load
to be called strictly with either 'undefined' or 'object'. Being internal, I thought I could expect that. let me know if that sounds ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately that is not a safe assumption. assert
should only be used when we know with absolute certainty that the value being tested comes from Node.js itself. In this case, the value comes from the user. Further, this test will fail if opts === null
since typeof null === 'object'
and null !== undefined
lib/module.js
Outdated
@@ -447,6 +447,10 @@ Module._load = function(request, parent, isMain) { | |||
} | |||
|
|||
var module = new Module(filename, parent); | |||
if (opts !== undefined && opts.dlopenFlags) { | |||
assert(util.isNumber(opts.dlopenFlags), 'dlopenFlags must be a number'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be an assert since this is checking a user provided value.
Instead, you'll want something like:
if (!util.isNumber(opts.dlopenFlags)) {
const errors = require('internal/errors');
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
'dlopenFlags', opts.dlopenFlags);
}
lib/module.js
Outdated
@@ -447,6 +447,10 @@ Module._load = function(request, parent, isMain) { | |||
} | |||
|
|||
var module = new Module(filename, parent); | |||
if (opts !== undefined && opts.dlopenFlags) { | |||
assert(util.isNumber(opts.dlopenFlags), 'dlopenFlags must be a number'); | |||
module._dlopenFlags = opts.dlopenFlags; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a non-enumerable Symbol here instead of a new _
prefixed property.
e.g.
const kOpenFlags = Symbol('node-dlopenflags');
// ...
Object.define(module, kOpenFlags, {
enumerable: false,
value: opts.dlopenFlags
});
lib/module.js
Outdated
assert(path, 'missing path'); | ||
assert(typeof path === 'string', 'path must be a string'); | ||
return Module._load(path, this, /* isMain */ false); | ||
assert(opts === undefined || typeof opts === 'object', 'opts must be an object'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, this should not be an assert since it is testing user input. A TypeError should be used instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasnell Makes sense. Should the assert
on the path argument be changed to exceptions too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I'm really wondering if checking the argument here makes any sense at all. The type check at _load
might be just enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments. Getting closer. Still need some more @nodejs/ctc folks to weigh in on this direction tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a general documentation comment: Given that we are widening the symbol scope beyond the library being loaded and thereby opening up potential collisions, it warrants a 'specification statement' in the doc so that we reduce production issues as well as friction with module owners. For example, stereotypical words like cb(though used in more JS than in C++), read(), push() etc. can collide with names in node. On the other hand, module owners can claim that their name(s) pre-date node symbols, and node should rename its symbol to resolve the conflict.
If you agree, my attempt to the spec thus:
"The module beling loaded should make sure that it does not collide with existing symbols which are loaded into the application and virtual machine space, and any issue arises thereon should be resolved by renaming symbols in the library which caused it."
I don't have any comments on the code - with the suggestions from @jasnell, it looks fine to me.
d343194
to
23aa881
Compare
doc/api/modules.md
Outdated
|
||
<!-- type=misc --> | ||
|
||
require() supports an optional parameter than can be used to pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/request()/`require()`/
doc/api/modules.md
Outdated
<!-- type=misc --> | ||
|
||
require() supports an optional parameter than can be used to pass | ||
an object containing modifiers to the require operation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/require operation/module loader/ ?
doc/api/modules.md
Outdated
an object containing modifiers to the require operation. | ||
|
||
Currently, the only property supported is `dlopenFlags`, which | ||
can be used to pass flags to the `dlopen` call that loads |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence sounds a bit odd to me. Perhaps reword/restructure the whole section to something like:
Valid properties:
dlopenFlags
{integer} dlopen(3) flags to be used when loading a Node.js addon. See the documentation for [os.constants.dlopen
][] for valid flags. Example:var dlopen = require('os').constants.dlopen; var foo = require('./foo.node', { dlopenFlags: dlopen.RTLD_NOW | dlopen.RTLD_GLOBAL });
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have several misgivings about this pull request, the biggest being that I think it's a bad idea to extend require() for an esoteric use case that require.resolve() + process.dlopen() would also serve.
I can live with extending process.dlopen() but I'm -1 on touching require().
doc/api/os.md
Outdated
</tr> | ||
<tr> | ||
<td><code>RTLD_DEEPBIND</code></td> | ||
<td>Make a self-contained library use its own symbols in preference to symbols from previously loaded libraries.</td> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long lines in this file, please wrap at 80 columns.
doc/api/modules.md
Outdated
|
||
```js | ||
var os = require('os'); | ||
var foo = require('./foo', {dlopenFlags: os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long line, please wrap at 80 columns.
doc/api/os.md
Outdated
@@ -1160,6 +1160,46 @@ The following error codes are specific to the Windows operating system: | |||
</tr> | |||
</table> | |||
|
|||
### dlopen Constants | |||
|
|||
The following signal constants are exported by `os.constants.dlopen`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'are' is too definitive, they are only exported when available.
src/node.cc
Outdated
// libuv does not support passing flags to dlopen, | ||
// so we nee to open code the uv_dlopen implementation. | ||
dlerror(); | ||
lib.errmsg = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nullptr
src/node.cc
Outdated
const char* errmsg = dlerror(); | ||
|
||
if (errmsg) { | ||
lib.errmsg = strdup(errmsg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This relies a bit too much on libuv implementation details to my taste.
@@ -9,7 +9,7 @@ assert.deepStrictEqual( | |||
); | |||
|
|||
assert.deepStrictEqual( | |||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'errno', 'signals'] | |||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'dlopen', 'errno', 'signals'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Long line. Please run make test
or make lint
, that's what they are for.
@bnoordhuis How about this: https://github.com/ezequielgarcia/node-libao/blob/dlopen-hack/index.js#L7 ? Not sure how problematic, or how nice, it is call |
@ezequielgarcia Something like that, although I don't understand why you pass in |
@bnoordhuis You say an overload that uses the current module? since |
@jasnell |
I haven't yet done a thorough re-review, so I can't sign off but you can consider my initial review addressed and clear it. |
@gireeshpunathil thanks for offering to work with @ezequielgarcia to get the test working for AIX. Can you open a new issue to track that effort ? and +1 to going ahead and landing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have a few questions, that's all.
### dlopen Constants | ||
|
||
If available on the operating system, the following constants | ||
are exported in `os.constants.dlopen`. See dlopen(3) for detailed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Care to link http://man7.org/linux/man-pages/man3/dlopen.3.html?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it done automagically? I think we already discussed this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad. Yes, tools/doc/html.js will take care of this.
doc/api/process.md
Outdated
|
||
* `module` {Object} | ||
* `filename` {string} | ||
* `flags` {integer}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Dot at the end is not required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it would be better to specify the default value of this optional parameter, which is kDefaultFlags
. Right now crypto
module's .md
has examples for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, the default value is platform specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the more important to document it I believe.
doc/api/process.md
Outdated
The `process.dlopen()` method allows to dynamically load shared | ||
objects. It is primarily used by `require()` to load | ||
C++ Addons, and should not be used directly, except in special | ||
cases. In other words, [`require()`][] should be prefered over |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
preferred
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@@ -2498,36 +2502,96 @@ struct node_module* get_linked_module(const char* name) { | |||
return mp; | |||
} | |||
|
|||
// DLOpen is process.dlopen(module, filename). | |||
struct DLib { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a struct
? If we keep it as a class
and accept the values of filename_
and errmsg_
in the constructor, then we don't have to expose all the members as public
right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already discussed. See above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find the actual discussion corresponding to this. I know it is exhausting to search through it, but if you could find it, please point me to it. I'll try again when I get back home.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On #12794 (comment) it was suggested to do a class. So I did it and commented
#12794 (comment). Later, #12794 (comment), where it was suggested to do a struct instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I didn't check properly then.
src/node.cc
Outdated
uv_dlclose(&lib_); | ||
} | ||
#endif | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't calling Close
necessary in the destructor? I see that we are explicitly calling Close
everywhere, but I thought cleaning up in destructor is the best practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handle should only be closed on error. It needs to stay open on success, otherwise the shared object would be unloaded again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are not closing everywhere. The handle is only closed on error. See #12794 (comment)
test/addons/dlopen-ping-pong/test.js
Outdated
|
||
if (common.isWindows) { | ||
common.skip('dlopen global symbol loading is not supported on this os.'); | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return
s are not necessary. skip
will take care of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
|
||
const bindingPath = require.resolve(`./build/${common.buildType}/binding`); | ||
process.dlopen(module, bindingPath, | ||
os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These variables are defined in an if
macro guard. If they are not available in the current OS, then this test should be skipped, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess so.
@@ -9,7 +9,8 @@ assert.deepStrictEqual( | |||
); | |||
|
|||
assert.deepStrictEqual( | |||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'errno', 'signals'] | |||
Object.keys(constants.os).sort(), ['UV_UDP_REUSEADDR', 'dlopen', 'errno', | |||
'signals'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style Nit: The second array would look better in the second line I guess.
Let's add constants for dlopen flags, which are needed for dlopen's flag passing, implemented in next commit. Signed-off-by: Ezequiel Garcia <[email protected]>
69e911e
to
baf1be2
Compare
src/node.cc
Outdated
uv_dlclose(&lib_); | ||
} | ||
#endif | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handle should only be closed on error. It needs to stay open on success, otherwise the shared object would be unloaded again.
src/node.cc
Outdated
|
||
const char* GetError() { | ||
return errmsg_.c_str(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these methods are simple wrappers and only used once, I'd simply inline them at the call site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean like this Local<String> errmsg = OneByteString(env->isolate(), dlib.errmsg_.c_str());
@bnoordhuis ?
src/node.cc
Outdated
void Close() { | ||
uv_dlclose(&lib_); | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this to #endif // !__POSIX__
? (Note the two spaces before the //
.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This commit introduces an optional parameter for process.dlopen(), allowing to pass dlopen flags (using values from os.constants.dlopen). If no flags are passed, the default behavior is to load the library with RTLD_LAZY (perform lazy binding) and RTLD_LOCAL (symbols are available only locally). Signed-off-by: Ezequiel Garcia <[email protected]>
baf1be2
to
3ca00e7
Compare
Just pushed another branch addressing @bnoordhuis and @thefourtheye comments.
|
CI: https://ci.nodejs.org/job/node-test-pull-request/10000/ |
Landed in 5f22375 |
* add constants for dlopen flags, which are needed for dlopen's flag passing. * introduce an optional parameter for process.dlopen(), allowing to pass dlopen flags (using values from os.constants.dlopen). If no flags are passed, the default behavior is to load the library with RTLD_LAZY (perform lazy binding) and RTLD_LOCAL (symbols are available only locally). PR-URL: #12794 Refs: #4105 Refs: libuv/libuv#1331 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]>
@refack thanks for taking care of this! |
* add constants for dlopen flags, which are needed for dlopen's flag passing. * introduce an optional parameter for process.dlopen(), allowing to pass dlopen flags (using values from os.constants.dlopen). If no flags are passed, the default behavior is to load the library with RTLD_LAZY (perform lazy binding) and RTLD_LOCAL (symbols are available only locally). PR-URL: nodejs#12794 Refs: nodejs#4105 Refs: libuv/libuv#1331 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Sakthipriyan Vairamani <[email protected]>
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
* **async_hooks** * Older experimental `async_hooks` APIs have been removed [[`d731369b1d`](d731369b1d)] **(SEMVER-MAJOR)** [#14414](#14414) * **Errors** * Multiple built in modules have been migrated to use static error codes * **Domains** * The long deprecated `.dispose()` method has been removed [[`602fd36d95`](602fd36d95)] **(SEMVER-MAJOR)** [#15412](#15412) * **File system** * `fs.ReadStream` and `fs.WriteStream` now make use of `.destroy()` [[`e5c290bed9`](e5c290bed9)] **(SEMVER-MAJOR)** [#15407](#15407) * `fs` callbacks are now invoked with an undefined `this` context [[`2249234fee`](2249234fee)] **(SEMVER-MAJOR)** [#14645](#14645) * **HTTP** * Socket timeout is set when the socket connects [[`10be20a0e8`](10be20a0e8)] **(SEMVER-MAJOR)** [#8895](#8895) * A bug causing the request `error` event to fire twice has been fixed [[`620ba41694`](620ba41694)] **(SEMVER-MAJOR)** [#14659](#14659) * The `pipe` method on `OutgoingMessage` has been disabled [[`156549d8ff`](156549d8ff)] **(SEMVER-MAJOR)** [#14358](#14358) * **HTTP/2** * The `--expose-http2` command-line argument is no longer required [[`f55ee6e24a`](f55ee6e24a)] **(SEMVER-MAJOR)** [#15535](#15535) * **Internationalization** * The `Intl.v8BreakIterator` class has been removed [[`668ad44922`](668ad44922)] **(SEMVER-MAJOR)** [#15238](#15238) * **OS** * `os.EOL` is now read-only [[`f6caeb9526`](f6caeb9526)] **(SEMVER-MAJOR)** [#14622](#14622) * **Process** * It is now possible to pass additional flags to `dlopen` [[`5f22375922`](5f22375922)] **(SEMVER-MAJOR)** [#12794](#12794) * **Timers** * Using a timeout duration larger than 32-bits will now emit a warning [[`ce3586da31`](ce3586da31)] **(SEMVER-MAJOR)** [#15627](#15627) * **TLS** * `parseCertString` has been deprecated [[`468110b327`](468110b327)] **(SEMVER-MAJOR)** [#14249](#14249) * Type-checking for `key`, `cert`, and `ca` options has been added [[`a7dccd040d`](a7dccd040d)] **(SEMVER-MAJOR)** [#14807](#14807)
This PR is another attempt at support dlopen flags. A previous try was made in #4105, but it never got accepted.
In particular, it's common to require a node addon to be loaded using
dlopen(flag=RTLD_GLOBAL)
. For instance, this is required to implement a libao binding, due to libao's plugins being shared objects that need symbols exposed in a common shared object (see here).It's interesting to mention that this feature is being requested since at least 2010:
nodejs/node-v0.x-archive#436
https://groups.google.com/forum/#!topic/nodejs/L2wNb_azzyU
I really hope we can tackle it this time, so please provide any feedback to make this feature acceptable.
The code is a little bit ugly, open-coding the
dlopen
call instead of abstracting it behind libuv. My initial attempt was through libuv, but it got rejected: libuv/libuv#1331.This is inspired by Python
sys.{get,set}dlopenflags
. Python docs here and here.Checklist
make -j4 test
(UNIX) passesAffected core subsystem(s)
src, process