Skip to content

Fix a memory leak when creating Python3 modules. #2024

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

Closed
wants to merge 1 commit into from

Conversation

T045T
Copy link
Contributor

@T045T T045T commented Dec 12, 2019

With no segfaults, this time!

Copy link
Collaborator

@bstaletic bstaletic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own understanding, what is module in the lambda? Is that m_ptr?
Also is the return value of PyModule_GetDef the def from a few lines above?

@@ -799,7 +799,11 @@ class module : public object {
def->m_name = name;
def->m_doc = doc;
def->m_size = -1;
Py_INCREF(def);
def->m_free = [](void* module ) {
if (module != NULL) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be if (module). If you really want to be explicit in your bool expressions, use module != nullptr.

@@ -795,11 +795,17 @@ class module : public object {
if (!options::show_user_defined_docstrings()) doc = nullptr;
#if PY_MAJOR_VERSION >= 3
PyModuleDef *def = new PyModuleDef();
// Ensure that Python does not try to delete this.
Py_INCREF(def);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's happening here? Why would the python allocator be trying to free memory that it has never allocated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this was me trying a theory on Travis, because I could not reproduce the failure locally even in Docker

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(My theory was wrong, anyway)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't notice the travis failure before. I can still repro, though I get the same error as travis, but on pytest, not cpptest.

Hop over to gitter. It will be easier to chat there and look into this.

@T045T
Copy link
Contributor Author

T045T commented Dec 12, 2019

Yes, you're right - module is m_ptr, but cast to void*, and PyModule_GetDef() returns def.

The issue here is that PyModule is a struct that's defined only in a .cc file, so we cannot cast module to PyModule* ourselves, and instead have to rely on PyModule_GetDef().

@wjakob
Copy link
Member

wjakob commented Dec 12, 2019

I think you will need to memset() the new-allocated memory region to zero before running INCREF on it.

@bstaletic
Copy link
Collaborator

I think you will need to memset()

That would make memset() immediately follow new. That's perfectly fine, but that's exactly what calloc() does, so the two lines could be replaced with just calloc(1, sizeof(...)).

That property of calloc() is subtle, so I'll leave to you two to decide whether this is a good idea.

@wjakob
Copy link
Member

wjakob commented Dec 12, 2019

Either variant is fine with me, let's just not mix calloc and delete :-)

@T045T
Copy link
Contributor Author

T045T commented Dec 12, 2019

This turned out to be more tricky than anticipated. The current version is not meant to be merged as-is, but to share my current status for you to see.

@wjakob
Copy link
Member

wjakob commented Jun 30, 2020

Any news here? Is anyone interested in picking up the work on this minor leak (which I understand can be rather annoying in the context of valgrind).

@bstaletic
Copy link
Collaborator

Yeah, I've worked on this with @T045T for a while in December. One approach fixes one version of python, other fixes two other versions of python, yadda yadda yadda...

It seems like CPython API expects this thing to have static lifetime and different versions of python rely on that assumption in different ways. I don't think we can solve this leak by a cleverly placed delete and not be at risk of it breaking when the next version of python comes out. Or the version after that.

Maybe valgrind users should just accept the 104*interpreter_finalization_calls bytes being leaked and write a suppression rule.

Here's the suppression rule for python 3.6 compiled in debug mode:

{
	Pybind11 leak, see https://github.com/pybind/pybind11/pull/2024 - definitely lost
	Memcheck:Leak
	match-leak-kinds: definite
	fun:_Znwm
	fun:_ZN8pybind116moduleC1EPKcS2_
	fun:PyInit_ycm_core
	fun:_PyImport_LoadDynamicModuleWithSpec
	fun:_imp_create_dynamic_impl
	fun:_imp_create_dynamic
	fun:cfunction_vectorcall_FASTCALL
	fun:PyVectorcall_Call
	fun:do_call_core
	fun:_PyEval_EvalFrameDefault
	fun:_PyEval_EvalCodeWithName
	fun:_PyFunction_Vectorcall
	fun:_PyObject_Vectorcall
	fun:call_function
	fun:_PyEval_EvalFrameDefault
	fun:function_code_fastcall
}

@bstaletic
Copy link
Collaborator

bstaletic commented Jul 14, 2020

We likely want to use this thing.

From the Python extension module tutorial on defining types:

> What we’re showing here is the traditional way of defining static extension types. It should be adequate for most uses. The C API also allows defining heap-allocated extension types using the PyType_FromSpec() function, which isn’t covered in this tutorial.

EDIT: Forget what I've just said. I mixed something up.

@rwgk
Copy link
Collaborator

rwgk commented Sep 27, 2020

I just merged #2413 which solves the leak check issue in a different way. Closing this PR. Please let us know if you're still seeing leak check errors in your environment (seems very unlikely).

@rwgk rwgk closed this Sep 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants