-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Avoid local_internals destruction #4192
Conversation
It allows to avoid possible static deinitialization fiasco.
@jbms could you please take a look? |
LGTM |
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 makes a lot of sense, thanks! I'm almost surprised that this didn't bite sooner.
Did you look for other similar situations in the code?
No, I didn't. I have found this problem only because it caused that freezing in |
// destroyed in another static variable destructor, creation of this static there will cause | ||
// static deinitialization fiasco. In order to avoid it we avoid destruction of the | ||
// local_internals static. | ||
static auto *locals = new local_internals(); |
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.
Hmm, wait doesn't this leak now? as in the local_internals memory is never freed?
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.
Which is fine I suppose since there doesn't appear to be a good alternative, but we need to document 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.
Defining a function-local static variable that leaks like this is a common pattern (recommended by Google C++ style guide, for example: https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables). Since the destructor would only be called when the program exits, the "leak" is irrelevant as long as it is just memory. You can define it in a slightly more complicated way (https://github.com/google/tensorstore/blob/master/tensorstore/internal/no_destructor.h) to avoid the extra heap allocation, but one extra heap allocation is presumably not very important.
There are actually two different cases to consider here:
- For
get_local_internals
used within an extension module, Python never actually unloads extension modules (because in general it is not really feasible to do that safely), so the destructor is never invoked anyway. - The only case where the destructor previously was getting called was in the case where
get_local_internals
is used outside of an extension module, e.g. when using an embedded interpreter, or when statically linking other libraries directly to the Python interpreter itself (as we do internally at Google).
Looking more closely at finalize_interpreter
, though, the use of get_local_internals
seems suspect:
pybind11/include/pybind11/embed.h
Line 229 in 424ac4f
detail::get_local_internals().registered_types_cpp.clear(); |
By default there will be a separate copy of local_internals
for each extension module, and for the embedding program itself. Therefore, this will only free types and exception handlers that were registered module-local by whichever module calls finalize_interpreter
. Any types registered by other modules will remain registered. To work properly, this logic would need to change. Though given all of the bugs and limitations with multiple initialization and finalization of the python interpreter, I think it might be better to just not support that at all.
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.
@jbms How would suggest changing this logic then? Removing that 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.
Either to remove those two lines from finalize_interpreter
, and document that initialize_interpreter
should not be called more than once, or have the global internal state keep a reference to all of the local internals states, so that finalize_interpreter
can free all of the local internals states.
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 first option sounds like something that can break someones old code. Don't we care about it?
About the second option I do not quite understand how will it fix the bug. We still will be able to create static in the destructor routine. Or maybe I do not understand the second option...
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.
Don't we care about it?
Thinking pragmatically, I think the original fix is great, maybe we could add a link to the comment, e.g. what Jeremy provided (https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables). The original fix solves an immediate problem without disturbing anything else. All it does is shift the memory for the local internals from the data section of the binary to the heap (inconsequential side-effect) and not run the destructors (desired fix).
Thinking idealistically, long-term I'd really want to make it difficult to use pybind11 to initialize/finalize the interpreter multiple times. It's a trap / illusion. But I think it's best to not venture there in this PR, or now.
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 option to have finalize_interpreter
free all of the local states would require some changes to how local_internals
works, which would hopefully be done in such a way as to avoid this bug.
I agree that the current PR is the simplest fix, and we can address this other issue of finalize_interpreter
not freeing all of the local internals as a separate issue.
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.
Might be nice to follow up, but looks like a safe patch with an improvement for now.
Description
The first call of
get_local_internals()
creates static variable. It can be, that the first call toget_local_internals()
happens in thefinalize_interpreter()
. If this function is called from another static object destructor, code violates the order of static variable destruction. Such situation can happen, for example, if one decides to storescoped_interpreter
in a singleton class. The broken order of the destruction of statics can result in program freezing insidefinalize_interpreter
(at least on Windows it does).By making
local_internals
a pointer we avoid its destruction and the problem goes away.This PR allows to avoid explicit call to
detail::get_local_internals()
from the user side in the situation described in this issue#4172
Application of this change is wider than it is shown in the issue above. It also solves the problem if user calls
finalize_interpreter
directly in the destructor or situation, when interpreter was created after the singleton wrapper constructor returned.Suggested changelog entry: