Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Description
Bug report
Bug summary (suggested fix is in the very bottom)
This internal method is used inplt.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 onevent_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 rendersplt.pause()
practically unusable (this, and the window focusing issues). So either we should deprecateplt.pause()
or fix it, as I suggest below.
Code for reproduction
%pylabqt5importnumpyasnpimportmatplotlib.pyplotaspltfrommatplotlib.animationimportFuncAnimationfrommatplotlib._pylab_helpersimportGcfstopped=Falsefig,ax=plt.subplots()xdata,ydata= [], []ln,=plt.plot([], [],'ro')definit():ax.set_xlim(0,2*np.pi)ax.set_ylim(-1,1)returnln,defupdate(frame):xdata.append(frame)ydata.append(np.sin(frame))ln.set_data(xdata,ydata)ifframe==2*np.pi:stopped=Truereturnln,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 endtry:whilenotstopped:manager=Gcf.get_fig_manager(fig.number)manager.canvas.start_event_loop(1)exceptKeyboardInterrupt:plt.close(fig)print("KeyboardInterrupt caught")exceptAttributeError: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 theexcept KeyboardInterrupt
clause.
Matplotlib version
Operating system: 4.18.16-100.fc27.x86_64Matplotlib version: 3.0.2Matplotlib backend: Qt5AggPython version: 3.6IPython: 7.2.0Jupyter 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 (usingsignal.set_wakeup_fd()
andsocket.createsocketpair()
).
The problematic method should be rewritten as follows:
defstart_event_loop(self,timeout=0):ifhasattr(self,"_event_loop")andself._event_loop.isRunning():raiseRuntimeError("Event loop already running")self._event_loop=event_loop=QtCore.QEventLoop()iftimeout:timer=QtCore.QTimer.singleShot(timeout*1000,event_loop.quit)SIGINTHandler(qApp)interrupted=Falsedefsigint_handler():nonlocalinterruptedevent_loop.quit()interrupted=Trueold_sigint_handler=signal.getsignal(signal.SIGINT)signal.signal(signal.SIGINT,lambdasig,_:sigint_handler())try:event_loop.exec_()finally:signal.signal(signal.SIGINT,old_sigint_handler)ifinterrupted:raiseKeyboardInterrupt
SIGINTHandler class may be found in#13306