-
Notifications
You must be signed in to change notification settings - Fork 2.2k
fix: Make non-embedded module clear their locals before deallocation #3798
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1232,7 +1232,7 @@ class module_ : public object { | |
| /* m_slots */ nullptr, | ||
| /* m_traverse */ nullptr, | ||
| /* m_clear */ nullptr, | ||
| /* m_free */ nullptr}; | ||
| /* m_free */ [](void *) { detail::clear_local_internals(); }}; | ||
| auto *m = PyModule_Create(def); | ||
| if (m == nullptr) { | ||
| if (PyErr_Occurred()) { | ||
|
|
@@ -1245,6 +1245,38 @@ class module_ : public object { | |
| // For Python 2, reinterpret_borrow was correct. | ||
| return reinterpret_borrow<module_>(m); | ||
| } | ||
|
|
||
| /** \rst | ||
| Create a new top-level embedded module. | ||
|
|
||
| ``def`` should point to a statically allocated module_def. | ||
| \endrst */ | ||
| static module_ | ||
| create_embedded_extension_module(const char *name, const char *doc, module_def *def) { | ||
|
||
| // module_def is PyModuleDef | ||
| // Placement new (not an allocation). | ||
| def = new (def) | ||
| PyModuleDef{/* m_base */ PyModuleDef_HEAD_INIT, | ||
| /* m_name */ name, | ||
| /* m_doc */ options::show_user_defined_docstrings() ? doc : nullptr, | ||
| /* m_size */ -1, | ||
| /* m_methods */ nullptr, | ||
| /* m_slots */ nullptr, | ||
| /* m_traverse */ nullptr, | ||
| /* m_clear */ nullptr, | ||
| /* m_free */ nullptr}; | ||
| auto *m = PyModule_Create(def); | ||
| if (m == nullptr) { | ||
| if (PyErr_Occurred()) { | ||
| throw error_already_set(); | ||
| } | ||
| pybind11_fail("Internal error in module_::create_embedded_extension_module()"); | ||
| } | ||
| // TODO: Should be reinterpret_steal for Python 3, but Python also steals it again when | ||
| // returned from PyInit_... | ||
| // For Python 2, reinterpret_borrow was correct. | ||
| return reinterpret_borrow<module_>(m); | ||
| } | ||
| }; | ||
|
|
||
| // When inside a namespace (or anywhere as long as it's not the first item on a line), | ||
|
|
||
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 a little unclear if I understand this correctly. I'm wondering is
but all embedded modules share the same locals.
correct? If not I'm confused, if yes that would be my suggested wording.
Uh oh!
There was an error while loading. Please reload this page.
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 sorry if it does not sound clear, English is not my native language. What I meant is that since the static local variable of
get_local_internalsdoes not get dynamically linked when a non-embedded module is loaded by the interpreter, you end up with one definition of the locals for the interpreter, and one definition for each non-embedded modules.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 think in that case my suggested wording is clearer. But I'm not a native speaker, too!
Let's see what @Skylion007 thinks.
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'm having serious 2nd thoughts.
E.g. at Google, Python and all extension modules for a given binary (or test) are linked together statically. Each binary or test is hermetic.
Others may use
RTLD_GLOBAL. Probably rare, but maybe in certain environment that's what's needed?What does this PR mean in those situations? I'm not sure. Does someone else have a clear understanding of how the
m_clearfunctionality plays out across all environments?Guessing a lot: I think to be completely safe, we'd need some kind of lookup table, to keep track of which
module_defpointers are associated with a givenlocal_internalspointer, and if more than one, we cannot safelyclear_local_internalsuntil the last one disappears.Or we could have
static std::unordered_map<module_def *, local_internals>with all the required logic to register & clear types given amodule_defpointer, but search for types over all keys of the map.It seems pretty tricky to get the
m_clearfunctionality correct in all environments.Given those uncertainties, and that pybind11 existed to this point without using
m_clear, maybe this should be an opt-in, e.g. via some#define, or runtime option? That would be a safer and less sudden way to find out what's possible and what not.