Skip to content

[Bug]: TimerAsyncio does not work with Python 3.14 #30917

Description

@lahwaacz

Bug summary

The test_backends_interactive.py::test_webagg test is failing on Python 3.14, because the implementation of TimerAsyncio uses asyncio.ensure_future when there is no current event loop. asyncio.ensure_future calls asyncio.get_event_loop() when a loop is not given, and that raises a RuntimeError if there is no current event loop.

Code for reproduction

Run the matplotlib testsuite on Python 3.14

Actual outcome

=================================== FAILURES ===================================
_________________________________ test_webagg __________________________________
[gw28] linux -- Python 3.14.2 /build/python-matplotlib/src/matplotlib/test-env/bin/python

    @pytest.mark.skipif('TF_BUILD' in os.environ,
                        reason="this test fails an azure for unknown reasons")
    @pytest.mark.skipif(sys.platform == "win32", reason="Cannot send SIGINT on Windows.")
    def test_webagg():
        pytest.importorskip("tornado")
        proc = subprocess.Popen(
            [sys.executable, "-c",
             inspect.getsource(_test_interactive_impl)
             + "\n_test_interactive_impl()", "{}"],
            env={**os.environ, "MPLBACKEND": "webagg", "SOURCE_DATE_EPOCH": "0"})
        url = f'http://{mpl.rcParams["webagg.address"]}:{mpl.rcParams["webagg.port"]}'
        timeout = time.perf_counter() + _test_timeout
        try:
            while True:
                try:
                    retcode = proc.poll()
                    # check that the subprocess for the server is not dead
>                   assert retcode is None
E                   assert 1 is None

test-env/lib/python3.14/site-packages/matplotlib/tests/test_backends_interactive.py:493: AssertionError
----------------------------- Captured stderr call -----------------------------
Traceback (most recent call last):
  File "<string>", line 83, in <module>
    _test_interactive_impl()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "<string>", line 69, in _test_interactive_impl
    fig.savefig(result, format='png')
    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/figure.py", line 3490, in savefig
    self.canvas.print_figure(fname, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backend_bases.py", line 2186, in print_figure
    result = print_method(
        filename,
    ...<3 lines>...
        bbox_inches_restore=_bbox_inches_restore,
        **kwargs)
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backend_bases.py", line 2042, in <lambda>
    print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
                                                                 ~~~~^
        *args, **{k: v for k, v in kwargs.items() if k not in skip}))
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py", line 481, in print_png
    self._print_pil(filename_or_obj, "png", pil_kwargs, metadata)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py", line 429, in _print_pil
    FigureCanvasAgg.draw(self)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backends/backend_agg.py", line 382, in draw
    self.figure.draw(self.renderer)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/artist.py", line 94, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/artist.py", line 71, in draw_wrapper
    return draw(artist, renderer)
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/figure.py", line 3264, in draw
    DrawEvent("draw_event", self.canvas, renderer)._process()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backend_bases.py", line 1189, in _process
    self.canvas.callbacks.process(self.name, self)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/cbook.py", line 366, in process
    self.exception_handler(exc)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/cbook.py", line 110, in _exception_printer
    raise exc
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/cbook.py", line 361, in process
    func(*args, **kwargs)
    ~~~~^^^^^^^^^^^^^^^^^
  File "<string>", line 65, in <lambda>
    fig.canvas.mpl_connect("draw_event", lambda event: timer.start())
                                                       ~~~~~~~~~~~^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/_api/deprecation.py", line 382, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backend_bases.py", line 1069, in start
    self._timer_start()
    ~~~~~~~~~~~~~~~~~^^
  File "/build/python-matplotlib/src/matplotlib/test-env/lib/python3.14/site-packages/matplotlib/backends/backend_webagg_core.py", line 139, in _timer_start
    self._task = asyncio.ensure_future(
                 ~~~~~~~~~~~~~~~~~~~~~^
        self._timer_task(max(self.interval / 1_000., 1e-6))
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/lib/python3.14/asyncio/tasks.py", line 730, in ensure_future
    loop = events.get_event_loop()
  File "/usr/lib/python3.14/asyncio/events.py", line 715, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
                       % threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'MainThread'.
<sys>:0: RuntimeWarning: coroutine 'TimerAsyncio._timer_task' was never awaited
=========================== short test summary info ============================
FAILED test-env/lib/python3.14/site-packages/matplotlib/tests/test_backends_interactive.py::test_webagg - assert 1 is None

Expected outcome

No test failure

Additional information

No response

Operating system

Arch

Matplotlib Version

3.10.8

Matplotlib Backend

No response

Python version

3.14.2

Jupyter version

No response

Installation

None

Metadata

Metadata

Assignees

No one assigned

    Labels

    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions