Repository: matplotlib/matplotlib
File: lib/matplotlib/backends/backend_qt.py (Python)

Code before:
    # Use QSocketNotifier to read the socketpair while the Qt event loop runs.
    sn = None

    def prepare_notifier(rsock):
        nonlocal sn
        sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read)

        @sn.activated.connect
        def _may_clear_sock():
            # Running a Python function on socket activation gives the interpreter a
            # chance to handle the signal in Python land.  We also need to drain the
            # socket with recv() to re-arm it, because it will be written to as part of
            # the wakeup.  (We need this in case set_wakeup_fd catches a signal other
            # than SIGINT and we shall continue waiting.)
            try:
                rsock.recv(1)
            except BlockingIOError:
                # This may occasionally fire too soon or more than once on Windows, so
                # be forgiving about reading an empty socket.
                pass

        return sn  # Actually keep the notifier alive.

    def handle_sigint():
        # Ensure notifier does not outlive the event loop shutting down:
        nonlocal sn
        sn.deleteLater()
        QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete)
        if hasattr(qapp_or_eventloop, 'closeAllWindows'):
            qapp_or_eventloop.closeAllWindows()
        qapp_or_eventloop.quit()
        sn = None

    return _allow_interrupt(prepare_notifier, handle_sigint)


class TimerQT(TimerBase):
    """Subclass of `.TimerBase` using QTimer events."""

    def __init__(self, *args, **kwargs):
        # Create a new timer and connect the timeout() signal to the
        # _on_timer method.
        self._timer = QtCore.QTimer()
        self._timer.timeout.connect(self._on_timer)
        super().__init__(*args, **kwargs)

    def __del__(self):
        # The check for deletedness is needed to avoid an error at animation
        # shutdown with PySide2.
        if not _isdeleted(self._timer):
            self._timer_stop()

Review comment (suggestion):
I think I would rather do something like (untested, and without the necessary changes to the docstring of _allow_interrupt).
```patch
diff --git i/lib/matplotlib/backend_bases.py w/lib/matplotlib/backend_bases.py
index 527d8c0107..40ef8458b8 100644
--- i/lib/matplotlib/backend_bases.py
+++ w/lib/matplotlib/backend_bases.py
@@ -1645,9 +1645,10 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
     notifier = prepare_notifier(rsock)
 
     def save_args_and_handle_sigint(*args):
-        nonlocal handler_args
+        nonlocal handler_args, notifier
         handler_args = args
-        handle_sigint()
+        handle_sigint(notifier)
+        notifier = None
 
     signal.signal(signal.SIGINT, save_args_and_handle_sigint)
     try:
diff --git i/lib/matplotlib/backends/backend_qt.py w/lib/matplotlib/backends/backend_qt.py
index 5cde4866ca..198ee93e9a 100644
--- i/lib/matplotlib/backends/backend_qt.py
+++ w/lib/matplotlib/backends/backend_qt.py
@@ -171,7 +171,10 @@ def _allow_interrupt_qt(qapp_or_eventloop):
 
         return sn  # Actually keep the notifier alive.
 
-    def handle_sigint():
+    def handle_sigint(sn):
+        sn.deleteLater()
+        QtCore.QCoreApplication.sendPostedEvents(
+            sn, QtCore.QEvent.Type.DeferredDelete)
         if hasattr(qapp_or_eventloop, 'closeAllWindows'):
             qapp_or_eventloop.closeAllWindows()
         qapp_or_eventloop.quit()
diff --git i/src/_macosx.m w/src/_macosx.m
index aa2a6e68cd..0d021663bb 100755
--- i/src/_macosx.m
+++ w/src/_macosx.m
@@ -258,7 +258,7 @@ static void lazy_init(void) {
 }
 
 static PyObject*
-stop(PyObject* self)
+stop(PyObject* self, PyObject* /* ignored */)
 {
     stopWithEvent();
     Py_RETURN_NONE;
@@ -1863,7 +1863,7 @@ - (void)flagsChanged:(NSEvent *)event
             "written on the file descriptor given as argument.")},
         {"stop",
          (PyCFunction)stop,
-         METH_NOARGS,
+         METH_VARARGS,
          PyDoc_STR("Stop the NSApp.")},
         {"show",
          (PyCFunction)show,
```
i.e. let backend_bases._allow_interrupt handle the lifetime of the notifier object; the way things are written now it's a bit trickier because there's two long-lived frames which need to handle the lifetime of the notifier object (_allow_interrupt *and* _allow_interrupt_qt)

Code after:
    # Use QSocketNotifier to read the socketpair while the Qt event loop runs.

    def prepare_notifier(rsock):
        sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read)

        @sn.activated.connect
        def _may_clear_sock():
            # Running a Python function on socket activation gives the interpreter a
            # chance to handle the signal in Python land.  We also need to drain the
            # socket with recv() to re-arm it, because it will be written to as part of
            # the wakeup.  (We need this in case set_wakeup_fd catches a signal other
            # than SIGINT and we shall continue waiting.)
            try:
                rsock.recv(1)
            except BlockingIOError:
                # This may occasionally fire too soon or more than once on Windows, so
                # be forgiving about reading an empty socket.
                pass

        # We return the QSocketNotifier so that the caller holds a reference, and we
        # also explicitly clean it up in handle_sigint(). Without doing both, deletion
        # of the socket notifier can happen prematurely or not at all.
        return sn

    def handle_sigint(sn):
        sn.deleteLater()
        QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete)
        if hasattr(qapp_or_eventloop, 'closeAllWindows'):
            qapp_or_eventloop.closeAllWindows()
        qapp_or_eventloop.quit()

    return _allow_interrupt(prepare_notifier, handle_sigint)


class TimerQT(TimerBase):
    """Subclass of `.TimerBase` using QTimer events."""

    def __init__(self, *args, **kwargs):
        # Create a new timer and connect the timeout() signal to the
        # _on_timer method.
        self._timer = QtCore.QTimer()
        self._timer.timeout.connect(self._on_timer)
        super().__init__(*args, **kwargs)

    def __del__(self):
        # The check for deletedness is needed to avoid an error at animation
        # shutdown with PySide2.
        if not _isdeleted(self._timer):
            self._timer_stop()

    def _timer_set_single_shot(self):