Skip to content

Segmentation fault with macosx backend #17061

Closed

Description

@cbrnr

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.

Activity

  1. QuLogic commented on Apr 7, 2020

    @QuLogic
    Member

    Can you get a backtrace?

  2. tacaswell commented on Apr 7, 2020

    @tacaswell
    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 ?

  3. added this to the v3.2.2 milestone on Apr 7, 2020
  4. jklymak commented on Apr 7, 2020

    @jklymak
    Member

    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
    
  5. cbrnr commented on Apr 8, 2020

    @cbrnr
    ContributorAuthor
  6. anntzer commented on Apr 8, 2020

    @anntzer
    Contributor

    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.

  7. cbrnr commented on Apr 8, 2020

    @cbrnr
    ContributorAuthor

    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).

  8. anntzer commented on Apr 8, 2020

    @anntzer
    Contributor

    Well, guessed wrong, then :)

  9. cbrnr commented on Apr 9, 2020

    @cbrnr
    ContributorAuthor

    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.

  10. tacaswell commented on Apr 9, 2020

    @tacaswell
    Member

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

  11. cbrnr commented on Apr 9, 2020

    @cbrnr
    ContributorAuthor

    Yes, will do.

  12. cbrnr commented on Apr 9, 2020

    @cbrnr
    ContributorAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions