-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
src: add addon entry point symbol cache #20759
Conversation
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.
It’s hard to tell the actual behavioural differences here – as far as I can tell:
- We call .Close() if we have already loaded the addon. I think at least on POSIX systems that won’t make much of a difference?
- The bailout when an N-API addon has a
NULL
entry point. That seems like a good idea, but I’m not sure how it depends on the rest?
src/node.cc
Outdated
} else { \ | ||
addon_entry_points.insert((key_value)); \ | ||
} \ | ||
} while (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.
Can this rather be an inline function?
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 not sure how to capture all the different ways we initialize an addon in a single inline function.
We have
callback(exports, module, context)
for well-known native addon symbolsnapi_module_register_by_symbol(exports, module, context, napi_callback)
for N-APImp->nm_context_register_func(exports, module, context, mp->nm_priv)
for self-registering context-aware addonsmp->nm_register_func(exports, module, mp->nm_priv)
for self-registering non-context-aware addons
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.
Making some headway with lambdas. Lessee ...
src/node.cc
Outdated
CALL_ADDON_INIT( | ||
napi_module_register_by_symbol(exports, module, context, | ||
napi_callback), | ||
napi_callback, 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.
Style nit: Indentation is off here
Oh, also: Maybe take a look at addaleax/node@1d8b74d. It’s WIP and not going to be PR’ed soon, but that’s the direction into which I’d like to move the addon loading mechanism :) |
src/node.cc
Outdated
if (addon_entry_points.count((key_value)) == 1) { \ | ||
(dlib).Close(); \ | ||
} else { \ | ||
addon_entry_points.insert((key_value)); \ |
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 be DRY'd to this:
if (!addon_entry_points.insert(key_value).second) dlib.Close();
Apropos the name key_value
, that to me suggests it's a std::pair
when it's not.
@addaleax I've left a comment on your commit: addaleax@1d8b74d#commitcomment-28999355. tl;dr: Awesome work, but please use the module entry point rather than the module file name as the key. |
@addaleax I've replaced the macro with an inline function. @bnoordhuis I've switched to |
does this change also allows an addon defined by |
@fs-eire the ability to re-initialize an addon, possibly with a different context, has already been available since 3828fc6. This change merely makes sure that dlopen() handles do not leak as you load a given module multiple times. So, with this change, for any given module, if dlopen() is called more than once, then the second and subsequent times dlclose() will be called immediately afterwards, thereby ensuring that only the first dlopen() handle remains open. |
Running CI https://ci.nodejs.org/job/node-test-pull-request/14966/ to see if there are any platform-specific issues. |
@gabrielschulhof |
@fs-eire this change does not affect the behaviour of addons. As far as addons are concerned, there is no change. The cleanup is internal, and only done if the addon is loaded a second time. |
Any progress here? |
50a57ee
to
6227204
Compare
@addaleax @bnoordhuis could you please take another look? |
1 similar comment
@addaleax @bnoordhuis could you please take another look? |
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.
@gabrielschulhof Can you explain how to trigger this condition? Ideally also add a test case? That would help a lot
@@ -2257,6 +2258,22 @@ inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) { | |||
reinterpret_cast<napi_addon_register_func>(dlib->GetSymbolAddress(name)); | |||
} | |||
|
|||
// Initialize the addon, and close the DLib if this is a second-or-later | |||
// initialization call. |
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.
Shouldn’t it be the reverse situation? Why do we initialize the addon and then close 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.
We do not close the addon. We close the dlopen()
handle, because, if the symbol was already in the cache, we know that we've obtainted a dlopen()
handle to it in the past, and thus we can conclude that the dlopen()
handle we received this time is not a new handle, the closing of which would cause the addon to be unmapped from our address space, but rather a refcounted version of the old handle we received in the past. Thus, closing the new handle is safe.
Determine whether the dlopen() of a certain addon is the first load of said addon, or if the addon had previously been loaded. This is accomplished by storing the addon's entry point in a set. If, upon a subsequent load, after determining the addon's entry point, it is found to already be present in the cache, then the handle returned by the subsequent dlopen() is closed.
@addaleax I'm not sure how to produce a testable result as a function of whether If you run This ensures that we do not leak libdl refcounts for the addons we load. |
6227204
to
46bbbf0
Compare
In retrospect, I believe the correct way of handling multiple |
Determine whether the dlopen() of a certain addon is the first load of
said addon, or if the addon had previously been loaded. This is
accomplished by storing the addon's entry point in a set. If, upon a
subsequent load, after determining the addon's entry point, it is found
to already be present in the cache, then the handle returned by the
subsequent dlopen() is closed.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes