8000 Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt · Issue #13315 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content
Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt  #13315
Closed as not planned
@vdrhtc

Description

@vdrhtc

Bug report

Bug summary (suggested fix is in the very bottom)

This internal method is used in plt.pause() and BlockingInput class (I don't know what the latter does).

The problem is approximately since Matplotlib 2.0.0 (but I don't know what exactly has changed). In the current implementation, the method execution can't be interrupted easily since it is based on event_loop.exec_() (internally C++) function that locks out the Python interpreter and prevents it from processing SIGINT as KeyboardInterrupt.

The problem is clearly visible when you run some kind of animation (see example below), since the SIGINT is getting understood as KeyboardInterrupt by the target function of the Timer (which is in Python and thus the interpreter gets to run from time to time in the event loop). However, this exception in the timer target can't stop the even loop, and it continues.

In overall, this problem actually renders plt.pause() practically unusable (this, and the window focusing issues). So either we should deprecate plt.pause() or fix it, as I suggest below.

Code for reproduction

%pylab qt5
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib._pylab_helpers import Gcf

stopped = False
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')

def init():
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1, 1)
    return ln,

def update(frame):
    xdata.append(frame)
    ydata.append(np.sin(frame))
    ln.set_data(xdata, ydata)
    if frame == 2*np.pi:
        stopped = True
    return ln,

ani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),
                    init_func=init, blit=True, interval = 50, repeat=False)

# wait for the plotting/underlying process to end
try:
    while not stopped:
        manager = Gcf.get_fig_manager(fig.number)
        manager.canvas.start_event_loop(1)  
except KeyboardInterrupt:
    plt.close(fig)
    print("KeyboardInterrupt caught")
except AttributeError:
    print("Figure closed from GUI")

Actual outcome

On pressing stop button in Jupyter:

And then the waiting cycle continues with no error. The exceptions on the Qt event loop get caught somewhere inside?

Expected outcome

Clean stop and closed figure on the except KeyboardInterrupt clause.

Matplotlib version

Operating system: 4.18.16-100.fc27.x86_64
Matplotlib version: 3.0.2
Matplotlib backend: Qt5Agg
Python version: 3.6
IPython: 7.2.0
Jupyter notebook: 5.7.4

Everything installed with pip in a virtualenv.

Suggested fix

This problem has annoyed me for quite some time, and I am very glad that it seems that there is the solution. It is very closely related to #13302, and is solved similarly (using signal.set_wakeup_fd() and socket.createsocketpair()).

The problematic method should be rewritten as follows:

def start_event_loop(self, timeout=0):
    if hasattr(self, "_event_loop") and self._event_loop.isRunning():
        raise RuntimeError("Event loop already running")
    self._event_loop = event_loop = QtCore.QEventLoop()
    if timeout:
        timer = QtCore.QTimer.singleShot(timeout * 1000, event_loop.quit)

    SIGINTHandler(qApp)
    interrupted = False

    def sigint_handler():
        nonlocal interrupted
        event_loop.quit()
        interrupted = True

    old_sigint_handler = signal.getsignal(signal.SIGINT)
    signal.signal(signal.SIGINT, lambda sig, _: sigint_handler())

    try:
        event_loop.exec_()
    finally:
        signal.signal(signal.SIGINT, old_sigint_handler)
        if interrupted:
            raise KeyboardInterrupt

SIGINTHandler class may be found in #13306

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUI: Qtstatus: closed as inactiveIssues closed by the "Stale" Github Action. Please comment on any you think should still be open.status: inactiveMarked by the “Stale” Github Action

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0