Skip to content

Commit a257e36

Browse files
Clean up Qt socket notifier to avoid spurious interrupt handler calls
Closes #29688 Objects without a parent are not necessarily cleaned up in `PyQt5/6` when their reference count reaches zero, and must be explicitly cleaned up with `deleteLater()` This prevents the notifier firing after the signal handling was supposed to have been reset to its previous state. Rather than have both `bakend_bases._allow_interrupt()` and `backend_qt._allow_interrupt_qt()` hold a reference to the notifier, we pass it to the backend-specific `handle_signint()` function for cleanup. Note the approach to cleaning up the notifier with `.deleteLater()` followed by `sendPostedEvents()` is the documented workaround for when immediate deletion is desired: https://doc.qt.io/qt-6/qobject.html#deleteLater This ensures the object is still deleted up even if the same event loop does not run again.
1 parent d05b43d commit a257e36

File tree

3 files changed

+17
-10
lines changed

3 files changed

+17
-10
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,12 +1614,13 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
16141614
function which organizes a write of the signal number into a socketpair.
16151615
A backend-specific function, *prepare_notifier*, arranges to listen to
16161616
the pair's read socket while the event loop is running. (If it returns a
1617-
notifier object, that object is kept alive while the context manager runs.)
1617+
notifier object, that object is kept alive while the context manager runs)
16181618
16191619
If SIGINT was indeed caught, after exiting the on_signal() function the
16201620
interpreter reacts to the signal according to the handler function which
16211621
had been set up by a signal.signal() call; here, we arrange to call the
1622-
backend-specific *handle_sigint* function. Finally, we call the old SIGINT
1622+
backend-specific *handle_sigint* function, passing the notifier object
1623+
as returned by prepare_notifier(). Finally, we call the old SIGINT
16231624
handler with the same arguments that were given to our custom handler.
16241625
16251626
We do this only if the old handler for SIGINT was not None, which means
@@ -1629,7 +1630,7 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
16291630
Parameters
16301631
----------
16311632
prepare_notifier : Callable[[socket.socket], object]
1632-
handle_sigint : Callable[[], object]
1633+
handle_sigint : Callable[[object], object]
16331634
"""
16341635

16351636
old_sigint_handler = signal.getsignal(signal.SIGINT)
@@ -1645,9 +1646,10 @@ def _allow_interrupt(prepare_notifier, handle_sigint):
16451646
notifier = prepare_notifier(rsock)
16461647

16471648
def save_args_and_handle_sigint(*args):
1648-
nonlocal handler_args
1649+
nonlocal handler_args, notifier
16491650
handler_args = args
1650-
handle_sigint()
1651+
handle_sigint(notifier)
1652+
notifier = None
16511653

16521654
signal.signal(signal.SIGINT, save_args_and_handle_sigint)
16531655
try:

lib/matplotlib/backends/backend_qt.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,14 @@ def _may_clear_sock():
169169
# be forgiving about reading an empty socket.
170170
pass
171171

172-
return sn # Actually keep the notifier alive.
173-
174-
def handle_sigint():
172+
# We return the QSocketNotifier so that the caller holds a reference, and we
173+
# also explicitly clean it up in handle_sigint(). Without doing both, deletion
174+
# of the socket notifier can happen prematurely or not at all.
175+
return sn
176+
177+
def handle_sigint(sn):
178+
sn.deleteLater()
179+
QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete)
175180
if hasattr(qapp_or_eventloop, 'closeAllWindows'):
176181
qapp_or_eventloop.closeAllWindows()
177182
qapp_or_eventloop.quit()

src/_macosx.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ static void lazy_init(void) {
258258
}
259259

260260
static PyObject*
261-
stop(PyObject* self)
261+
stop(PyObject* self, PyObject* /* ignored */)
262262
{
263263
stopWithEvent();
264264
Py_RETURN_NONE;
@@ -1863,7 +1863,7 @@ - (void)flagsChanged:(NSEvent *)event
18631863
"written on the file descriptor given as argument.")},
18641864
{"stop",
18651865
(PyCFunction)stop,
1866-
METH_NOARGS,
1866+
METH_VARARGS,
18671867
PyDoc_STR("Stop the NSApp.")},
18681868
{"show",
18691869
(PyCFunction)show,

0 commit comments

Comments
 (0)