Skip to content

Segmentation fault with macosx backend #17061

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
cbrnr opened this issue Apr 7, 2020 · 11 comments · Fixed by #17084
Closed

Segmentation fault with macosx backend #17061

cbrnr opened this issue Apr 7, 2020 · 11 comments · Fixed by #17084

Comments

@cbrnr
Copy link
Contributor

cbrnr commented Apr 7, 2020

The following short example produces a segmentation fault when using the default macosx backend (of course on macOS):

from functools import partial
import matplotlib.pyplot as plt


def close_event(event, fig):
    plt.close(fig)


fig, ax = plt.subplots()
close_callback = partial(close_event, fig=fig)
fig.canvas.mpl_connect("close_event", close_callback)
plt.show(block=False)  # block=True works

To reproduce, run the script with python example.py. This produces the following segfault:

[1]    62624 segmentation fault  python example.py

Running the code in interactive mode works. Also, block=True does not produce the segfault.

This script also runs normally with the PyQt5 backend, e.g. MPLBACKEND=qt5agg python example.py, so it seems like this is an issue with the macosx backend.

  • Operating system: macOS 10.15.4
  • Matplotlib version: 3.2.1
  • Matplotlib backend: macosx
  • Python version: 3.8.2

I can reproduce this problem with Homebrew Python as well as Anaconda.

@QuLogic
Copy link
Member

QuLogic commented Apr 7, 2020

Can you get a backtrace?

@tacaswell
Copy link
Member

See https://docs.python.org/3/library/faulthandler.html for how to get a good backtrace.

My guess is that we are not being careful enough not reusing dead objects in the OSX backend. My guess is that the double close is trying to tear down a already torn down objectivec object which leads to a null deference which leads to segfault?

I wonder if #16942 has already fixed this, can you try installing from master @cbrnr ?

@tacaswell tacaswell added this to the v3.2.2 milestone Apr 7, 2020
@jklymak
Copy link
Member

jklymak commented Apr 7, 2020

On master:

Fatal Python error: Segmentation fault

Current thread 0x000000010f54adc0 (most recent call first):
  File "/Users/jklymak/matplotlib/lib/matplotlib/_pylab_helpers.py", line 86 in destroy_all
[2]    87588 segmentation fault  python3 -q -X faulthandler testmacosx.py

@cbrnr
Copy link
Contributor Author

cbrnr commented Apr 8, 2020

@jklymak beat me to it, the traceback points to the same line for me (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/_pylab_helpers.py#L86).

@anntzer
Copy link
Contributor

anntzer commented Apr 8, 2020

Can't test this, but first thing I'd consider is adding a check whether canvas is NULL at https://github.com/matplotlib/matplotlib/blob/master/src/_macosx.m#L1730 -- possibly https://github.com/matplotlib/matplotlib/blob/master/src/_macosx.m#L349 has already been called at that point.

@cbrnr
Copy link
Contributor Author

cbrnr commented Apr 8, 2020

I changed this function to:

- (void)windowWillClose:(NSNotification*)notification
{
    PyGILState_STATE gstate;
    PyObject* result;

    gstate = PyGILState_Ensure();
    if(canvas != NULL)
    {
        result = PyObject_CallMethod(canvas, "close_event", "");
        if(result)
            Py_DECREF(result);
        else
            PyErr_Print();
    }
    PyGILState_Release(gstate);
}

I still get the segfault though (after I made the changes I did pip install -ve . so that should have worked).

@anntzer
Copy link
Contributor

anntzer commented Apr 8, 2020

Well, guessed wrong, then :)

@cbrnr
Copy link
Contributor Author

cbrnr commented Apr 9, 2020

Not sure why there are these two very similar functions:

matplotlib/src/_macosx.m

Lines 723 to 734 in a86d4fb

static void
FigureManager_dealloc(FigureManager* self)
{
Window* window = self->window;
if(window)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[window close];
[pool release];
}
Py_TYPE(self)->tp_free((PyObject*)self);
}

matplotlib/src/_macosx.m

Lines 750 to 762 in a86d4fb

static PyObject*
FigureManager_destroy(FigureManager* self)
{
Window* window = self->window;
if(window)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[window close];
[pool release];
self->window = NULL;
}
Py_RETURN_NONE;
}

If I comment out the lines related to the pool in FigureManager_destroy (i.e. lines 756 and 758), the example works. However, I'm not sure if this doesn't leave deleted windows lying around because I'm absolutely not familiar with this codebase.

@tacaswell
Copy link
Member

@cbrnr can you put in a (draft) PR with your changes?

@cbrnr
Copy link
Contributor Author

cbrnr commented Apr 9, 2020

Yes, will do.

@cbrnr
Copy link
Contributor Author

cbrnr commented Apr 9, 2020

@tacaswell see #17084.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants