Bug 2350287 - cockpit fails to build from source with Python 3.14: AttributeError: module 'asyncio' has no attribute 'SafeChildWatcher'
Summary: cockpit fails to build from source with Python 3.14: AttributeError: module '...
Keywords:
Status: POST
Alias: None
Product: Fedora
Classification: Fedora
Component: cockpit
Version: rawhide
Hardware: Unspecified
OS: Linux
unspecified
medium
Target Milestone: ---
Assignee: Martin Pitt
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: PYTHON3.14
TreeView+ depends on / blocked
 
Reported: 2025-03-06 09:52 UTC by Karolina Surma
Modified: 2025-03-27 15:37 UTC (History)
8 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Type: ---
Embargoed:


Attachments (Terms of Use)

Description Karolina Surma 2025-03-06 09:52:32 UTC
Because of the build time of cockpit I don't have a full build log yet, but before the timeout, there was at least one error associated with Python 3.14 - see below.

+ /usr/bin/pytest
/usr/lib/python3.14/site-packages/pytest_asyncio/plugin.py:208: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset.
The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session"

  warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET))
============================= test session starts ==============================
platform linux -- Python 3.14.0a5, pytest-8.3.4, pluggy-1.5.0
rootdir: /builddir/build/BUILD/cockpit-334-build/cockpit-334
configfile: pyproject.toml
testpaths: test/pytest
plugins: asyncio-0.24.0, timeout-2.3.1
asyncio: mode=Mode.STRICT, default_loop_scope=None
collected 150 items

test/pytest/test_beiboot.py::test_bridge_beiboot ERROR                   [  0%]
-------------------------------- live log call ---------------------------------
ERROR    asyncio:base_events.py:1868 Exception in callback BusMessage.reply_method_function_return_value.<locals>.<lambda>() at /builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/_vendor/systemd_ctypes/bus.py:202
handle: <Handle BusMessage.reply_method_function_return_value.<locals>.<lambda>() at /builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/_vendor/systemd_ctypes/bus.py:202>
Traceback (most recent call last):
  File "/usr/lib64/python3.14/asyncio/events.py", line 98, in _run
    self._context.run(self._callback, *self._args)
    ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/_vendor/systemd_ctypes/bus.py", line 202, in <lambda>
    task.add_done_callback(lambda task: self._coroutine_task_complete(out_type, task))
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/_vendor/systemd_ctypes/bus.py", line 177, in _coroutine_task_complete
    self.reply_method_function_return_value(out_type, task.result())
                                                      ~~~~~~~~~~~^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/superuser.py", line 241, in start
    await self.go(name, self)
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/superuser.py", line 189, in go
    await self.peer.start(init_host=self.router.init_host)
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/peer.py", line 100, in start
    init_message = await self.init_future
                   ^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/peer.py", line 84, in _connect_task_done
    task.result()
    ~~~~~~~~~~~^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/superuser.py", line 76, in do_connect_transport
    transport = await self.spawn(self.args, env, stderr=agent, start_new_session=True)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/peer.py", line 62, in spawn
    return SubprocessTransport(loop, self, argv, env=dict(os.environ, **user_env), **kwargs)
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/transports.py", line 405, in __init__
    self._get_watcher(loop).add_child_handler(self._process.pid, self._exited)
    ~~~~~~~~~~~~~~~~~^^^^^^
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/transports.py", line 330, in _get_watcher
    watcher = SubprocessTransport._create_watcher()
  File "/builddir/build/BUILD/cockpit-334-build/cockpit-334/src/cockpit/transports.py", line 322, in _create_watcher
    return asyncio.SafeChildWatcher()
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/asyncio/__init__.py", line 65, in __getattr__
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
AttributeError: module 'asyncio' has no attribute 'SafeChildWatcher'

According to https://docs.python.org/dev/whatsnew/3.14.html:

Remove the following classes and functions. They were all deprecated and emitted deprecation warnings since Python 3.12:
    asyncio.get_child_watcher()
    asyncio.set_child_watcher()
    asyncio.AbstractEventLoopPolicy.get_child_watcher()
    asyncio.AbstractEventLoopPolicy.set_child_watcher()
    asyncio.AbstractChildWatcher
    asyncio.FastChildWatcher
    asyncio.MultiLoopChildWatcher
    asyncio.PidfdChildWatcher
    asyncio.SafeChildWatcher
    asyncio.ThreadedChildWatcher
(Contributed by Kumar Aditya in gh-120804.)

The testing takes place in Copr, where you can see the previous attempts to build the package.
https://copr.fedorainfracloud.org/coprs/g/python/python3.14/package/cockpit/

I'll try to make a full build with full log, and be back here in a few days.

Reproducible: Always

Comment 1 Allison Karlitskaya 2025-03-06 14:29:18 UTC
The root cause of this issue is the removal of PidfdChildWatcher in https://github.com/python/cpython/pull/120893 

The code in question only falls back to trying to use SafeChildWatcher if PidfdChildWatcher is not present:

    @staticmethod
    def _create_watcher() -> asyncio.AbstractChildWatcher:
        try:
            os.close(os.pidfd_open(os.getpid(), 0))  # check for kernel support
            return asyncio.PidfdChildWatcher()
        except (AttributeError, OSError):
            pass

        return asyncio.SafeChildWatcher()


This was a public API, documented in the official documentation, and not deprecated nor officially slated for removal.

https://docs.python.org/3/library/asyncio-policy.html#asyncio.PidfdChildWatcher

Comment 2 Allison Karlitskaya 2025-03-06 14:40:41 UTC
I just filed https://github.com/python/cpython/issues/130915

Hopefully they can change this before the release.

Comment 3 Allison Karlitskaya 2025-03-11 08:26:49 UTC
The Python asyncio maintainer has just indicated (in the linked bug report) that dropping a public documented symbol from one release to the next without deprecation or any other warning is "intended behavior".  I think we're going to have to deal with this :(

Comment 4 Martin Pitt 2025-03-26 05:04:22 UTC
I am currently looking into this, and I'm stumped how the API is actually supposed to work in current Python (i.e. >= 3.12):
 - https://docs.python.org/3/whatsnew/3.12.html#asyncio and https://github.com/python/cpython/issues/130915#issuecomment-2704219327 mention that the entire child watcher API is deprecated.
 - https://docs.python.org/3.13/library/asyncio-policy.html#process-watchers also has everything deprecated, i.e. it doesn't mention any current API
 - So maybe this cannot be used with arbitrary processes any more, but just with the "Process" class that asyncio.create_subprocess_exec() returns? But https://docs.python.org/3.13/library/asyncio-subprocess.html#asyncio.subprocess.Process *also* doesn't have anything like "invoke this callback then the process exits". The only thing it has is `async wait`, which could possibly be run in a task?

Comment 5 Victor Stinner 2025-03-27 13:59:23 UTC
I proposed a fix upstream, can someone please have a look and/or test it?
https://github.com/cockpit-project/cockpit/pull/21787

Comment 6 Martin Pitt 2025-03-27 15:37:27 UTC
We now have a fix in https://github.com/cockpit-project/cockpit/pull/21775 which will land soon. Thanks Victor! Looking forward to the upstream discussion, maybe they can expose the watcher property as API.


Note You need to log in before you can comment on or make changes to this bug.