Skip to content
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

Segfault fix in cpp_function::dispatcher of an overloaded function with *args/**kwargs #1223

Conversation

YannickJadoul
Copy link
Collaborator

Fixing a segfault in the cpp_function::dispatcher of an overloaded function with args/kwargs, during the second pass over the overloads (with conversions).

Inside the overload loop, new py::objects are created for the *args and **kwargs arguments (extra_args and kwargs), but these go out of scope by the time a second pass with conversions over the different overloads is executed.
At the destruction of the argument_loader, its nested casters, and the py::args object, if Python already garbage collected, things go wrong.

A quick, 3-line solution is to keep a std::vector<py::object> and add the extra_args and kwargs instances to keep them alive for long enough.

Attached is a trace of the segfault in GDB (running python3-gdb; python3 just gets corrupted instead of segfaulting and shouts Fatal Python error: GC object already tracked somewhere later):
args-crash-gdb-bt.txt

I don't know if there might be a better/cleaner/... solution than this 'keepalive' vector, but this at least seems to be solving the segfaults/corruption and does not change the flow of the dispatcher.

…nction with args/kwargs, during the second pass over the overloads (with conversions).

Inside the overload loop, new `py::object`s are created for the *args and **kwargs arguments (`extra_args` and `kwargs`), but these go out of scope by the time a second pass with conversions over the different overloads is executed.
At the destruction of the `argument_loader`, its nested casters, and the `py::args` object, if Python already garbage collected, things go wrong.

A quick, 3-line solution is to keep a `std::vector<py::object>` and add the `extra_args` and `kwargs` instances to keep them alive for long enough.
@jagerman
Copy link
Member

I can't look into this in detail right now, but my initial thought (which may well be wrong) is that it seems as though they should be kept alive by being inside the call.args vector. If adding them to a second vector is fixing this, is it perhaps just masking somewhere that we missed an inc_ref (or equivalently that we stole somewhere where we should have borrowed)?

@YannickJadoul
Copy link
Collaborator Author

As far as I saw, call.args is a std::vector<handle>, and won't do any reference counting?

@jagerman
Copy link
Member

Ah yes, I see. (I forgot: we wanted a handle vector there to avoid a bunch of unnecessary inc_ref/dec_refs in here).

jagerman added a commit to jagerman/pybind11 that referenced this pull request Dec 23, 2017
The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in pybind#1223).
@jagerman
Copy link
Member

I agree that we need another keep-alive reference here, but I don't really like the idea of introducing another vector here—we'd be introducing extra mallocs into the function dispatcher (which is performance-critical code). I think putting the extra keepalive objects as two new members in the function_call should work; it makes function_call slightly larger, but that way we're just allocating slightly larger function_call objects rather than adding new allocations.

Try out 7c306b1 -- I think that should solve it.

@YannickJadoul
Copy link
Collaborator Author

Ok, agreed, that's a fine solution too. And the error in the specific case I had is also solved by it (as I just tested), so ... thanks!

@jagerman
Copy link
Member

(I'm going to keep this open until I merge that commit into master just to keep it on my radar).

@jagerman jagerman reopened this Dec 23, 2017
jagerman added a commit to jagerman/pybind11 that referenced this pull request Dec 23, 2017
The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in pybind#1223).
jagerman added a commit that referenced this pull request Dec 23, 2017
The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in #1223).
@jagerman
Copy link
Member

Merged the fix into master (48e1f9a).

@jagerman jagerman closed this Dec 23, 2017
@jagerman jagerman added this to the v2.2.2 milestone Dec 23, 2017
@YannickJadoul
Copy link
Collaborator Author

Thanks for the quick fix!

jagerman added a commit to jagerman/pybind11 that referenced this pull request Jan 12, 2018
The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in pybind#1223).
@YannickJadoul YannickJadoul deleted the dispatcher-args-kwargs-keepalive branch January 23, 2018 16:04
wjakob pushed a commit that referenced this pull request Feb 7, 2018
The `py::args` or `py::kwargs` arguments aren't properly referenced
when added to the function_call arguments list: their reference counts
drop to zero if the first (non-converting) function call fails, which
means they might be cleaned up before the second pass call runs.

This commit adds a couple of extra `object`s to the `function_call`
where we can stash a reference to them when needed to tie their
lifetime to the function_call object's lifetime.

(Credit to YannickJadoul for catching and proposing a fix in #1223).
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.

None yet

2 participants