Title: Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt · Issue #13315 · matplotlib/matplotlib · GitHub
Open Graph Title: Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt · Issue #13315 · matplotlib/matplotlib
X Title: Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt · Issue #13315 · matplotlib/matplotlib
Description: 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 w...
Open Graph Description: 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 si...
X Description: 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 approximatel...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/13315
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Can't interrupt start_event_loop() at backend_qt5.py::FigureCanvasQt ","articleBody":"\u003c!--To help us understand and resolve your issue, please fill out the form to the best of your ability.--\u003e\r\n\u003c!--You can feel free to delete the sections that do not apply.--\u003e\r\n\r\n### Bug report\r\n\r\n**Bug summary** (suggested fix is in the very bottom)\r\n\r\nThis internal method is used in `plt.pause()` and BlockingInput class (I don't know what the latter does). \r\n\r\nThe 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.\r\n\r\nThe 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. \r\n\r\nIn 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.\r\n\r\n**Code for reproduction**\r\n\r\n\u003c!--A minimum code snippet required to reproduce the bug.\r\nPlease make sure to minimize the number of dependencies required, and provide\r\nany necessary plotted data.\r\nAvoid using threads, as Matplotlib is (explicitly) not thread-safe.--\u003e\r\n\r\n```python\r\n%pylab qt5\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nfrom matplotlib.animation import FuncAnimation\r\nfrom matplotlib._pylab_helpers import Gcf\r\n\r\nstopped = False\r\nfig, ax = plt.subplots()\r\nxdata, ydata = [], []\r\nln, = plt.plot([], [], 'ro')\r\n\r\ndef init():\r\n ax.set_xlim(0, 2*np.pi)\r\n ax.set_ylim(-1, 1)\r\n return ln,\r\n\r\ndef update(frame):\r\n xdata.append(frame)\r\n ydata.append(np.sin(frame))\r\n ln.set_data(xdata, ydata)\r\n if frame == 2*np.pi:\r\n stopped = True\r\n return ln,\r\n\r\nani = FuncAnimation(fig, update, frames=np.linspace(0, 2*np.pi, 128),\r\n init_func=init, blit=True, interval = 50, repeat=False)\r\n\r\n# wait for the plotting/underlying process to end\r\ntry:\r\n while not stopped:\r\n manager = Gcf.get_fig_manager(fig.number)\r\n manager.canvas.start_event_loop(1) \r\nexcept KeyboardInterrupt:\r\n plt.close(fig)\r\n print(\"KeyboardInterrupt caught\")\r\nexcept AttributeError:\r\n print(\"Figure closed from GUI\")\r\n```\r\n\r\n**Actual outcome**\r\n\r\nOn pressing stop button in Jupyter:\r\n\r\n\u003cimg src=\"https://user-images.githubusercontent.com/3819012/51903616-ac730e00-23cd-11e9-9b6e-4094f08beaf2.png\" width=\"500\"\u003e\r\n\r\nAnd then the waiting cycle continues with no error. The exceptions on the Qt event loop get caught somewhere inside?\r\n\r\n**Expected outcome**\r\n\r\nClean stop and closed figure on the `except KeyboardInterrupt` clause.\r\n\r\n**Matplotlib version**\r\n\r\n Operating system: 4.18.16-100.fc27.x86_64\r\n Matplotlib version: 3.0.2\r\n Matplotlib backend: Qt5Agg\r\n Python version: 3.6\r\n IPython: 7.2.0\r\n Jupyter notebook: 5.7.4\r\n\r\nEverything installed with pip in a virtualenv.\r\n\r\n### Suggested fix\r\n\r\nThis 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()`). \r\n\r\nThe problematic method should be rewritten as follows:\r\n```python\r\ndef start_event_loop(self, timeout=0):\r\n if hasattr(self, \"_event_loop\") and self._event_loop.isRunning():\r\n raise RuntimeError(\"Event loop already running\")\r\n self._event_loop = event_loop = QtCore.QEventLoop()\r\n if timeout:\r\n timer = QtCore.QTimer.singleShot(timeout * 1000, event_loop.quit)\r\n\r\n SIGINTHandler(qApp)\r\n interrupted = False\r\n\r\n def sigint_handler():\r\n nonlocal interrupted\r\n event_loop.quit()\r\n interrupted = True\r\n\r\n old_sigint_handler = signal.getsignal(signal.SIGINT)\r\n signal.signal(signal.SIGINT, lambda sig, _: sigint_handler())\r\n\r\n try:\r\n event_loop.exec_()\r\n finally:\r\n signal.signal(signal.SIGINT, old_sigint_handler)\r\n if interrupted:\r\n raise KeyboardInterrupt\r\n```\r\nSIGINTHandler class may be found in #13306","author":{"url":"https://github.com/vdrhtc","@type":"Person","name":"vdrhtc"},"datePublished":"2019-01-29T12:00:48.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":13},"url":"https://github.com/13315/matplotlib/issues/13315"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:a135cf9c-d259-03df-fadd-30e46cc2b0b9 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | CCA2:1CCA14:6B3B30:906476:6A52FF8E |
| html-safe-nonce | eb4a697e6495c15e44d9ffd36ce820ae615e0798ca33b3c276a6b3e0938f565c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJDQ0EyOjFDQ0ExNDo2QjNCMzA6OTA2NDc2OjZBNTJGRjhFIiwidmlzaXRvcl9pZCI6Ijc1NTM5NDYyMzY2OTAzMDA4MTQiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 9a9c9681f1dd8cf53ba8b1ed4bf81c5109bf32ec2cd3980d22b9284bf629b66a |
| hovercard-subject-tag | issue:404257692 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/13315/issue_layout |
| twitter:image | https://opengraph.githubassets.com/cc55bcd63d27aaff30fc929da9376395b20663b6c78671dd4eee15c7eebac3e5/matplotlib/matplotlib/issues/13315 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/cc55bcd63d27aaff30fc929da9376395b20663b6c78671dd4eee15c7eebac3e5/matplotlib/matplotlib/issues/13315 |
| og:image:alt | 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 si... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | vdrhtc |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 07a982c1d40157c619b364352b704c3ce66bb332 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width