Skip to content

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

Closed

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

Activity

  1. lahwaacz commented on Dec 31, 2025

    @lahwaacz
    Author

    The code which fails:

    self._task = asyncio.ensure_future(
    self._timer_task(max(self.interval / 1_000., 1e-6))
    )

  2. added this to the v3.11.0 milestone on Dec 31, 2025
  3. tacaswell commented on Dec 31, 2025

    @tacaswell
    Member

    Ah, we don't have a 3.14 CI job yet.

  4. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Dec 31, 2025
  5. added a commit that references this issue on Dec 31, 2025
    2a94a7f
  6. tacaswell commented on Dec 31, 2025

    @tacaswell
    Member

    The fix here is to create an event loop in the test code. I don't think we want to restore the behavior upstream removed (implicitly creating an EventLoop) which will likely cause some down-stream breakage but the fix is for user to track the upstream changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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