Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-94597: deprecate SafeChildWatcher, FastChildWatcher and MultiLoopChildWatcher child watchers #98089

Merged
merged 4 commits into from
Oct 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ New Modules
Improved Modules
================

asyncio
-------

* On Linux, :mod:`asyncio` uses :class:`~asyncio.PidfdChildWatcher` by default
if :func:`os.pidfd_open` is available and functional instead of
:class:`~asyncio.ThreadedChildWatcher`.
(Contributed by Kumar Aditya in :gh:`98024`.)

* The child watcher classes :class:`~asyncio.MultiLoopChildWatcher`,
:class:`~asyncio.FastChildWatcher` and
:class:`~asyncio.SafeChildWatcher` are deprecated and
will be removed in Python 3.14. It is recommended to not manually
configure a child watcher as the event loop now uses the best available
child watcher for each platform (:class:`~asyncio.PidfdChildWatcher`
if supported and :class:`~asyncio.ThreadedChildWatcher` otherwise).
(Contributed by Kumar Aditya in :gh:`94597`.)


pathlib
-------

Expand Down
15 changes: 15 additions & 0 deletions Lib/asyncio/unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,13 @@ class SafeChildWatcher(BaseChildWatcher):
big number of children (O(n) each time SIGCHLD is raised)
"""

def __init__(self):
super().__init__()
warnings._deprecated("SafeChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))

def close(self):
self._callbacks.clear()
super().close()
Expand Down Expand Up @@ -1100,6 +1107,10 @@ def __init__(self):
self._lock = threading.Lock()
self._zombies = {}
self._forks = 0
warnings._deprecated("FastChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))

def close(self):
self._callbacks.clear()
Expand Down Expand Up @@ -1212,6 +1223,10 @@ class MultiLoopChildWatcher(AbstractChildWatcher):
def __init__(self):
self._callbacks = {}
self._saved_sighandler = None
warnings._deprecated("MultiLoopChildWatcher",
"{name!r} is deprecated as of Python 3.12 and will be "
"removed in Python {remove}.",
remove=(3, 14))

def is_active(self):
return self._saved_sighandler is not None
Expand Down
10 changes: 7 additions & 3 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import unittest
from unittest import mock
import weakref

import warnings
if sys.platform not in ('win32', 'vxworks'):
import tty

Expand Down Expand Up @@ -2055,7 +2055,9 @@ def test_remove_fds_after_closing(self):
class UnixEventLoopTestsMixin(EventLoopTestsMixin):
def setUp(self):
super().setUp()
watcher = asyncio.SafeChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher)

Expand Down Expand Up @@ -2652,7 +2654,9 @@ def setUp(self):
asyncio.set_event_loop(self.loop)

if sys.platform != 'win32':
watcher = asyncio.SafeChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher)

Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_asyncio/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import threading
import unittest
from unittest import mock
import warnings
from test.support import socket_helper
try:
import ssl
Expand Down Expand Up @@ -791,8 +792,9 @@ def test_read_all_from_pipe_reader(self):
protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop)
transport, _ = self.loop.run_until_complete(
self.loop.connect_read_pipe(lambda: protocol, pipe))

watcher = asyncio.SafeChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
try:
asyncio.set_child_watcher(watcher)
Expand Down
48 changes: 20 additions & 28 deletions Lib/test/test_asyncio/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
import unittest
import warnings
import functools
from unittest import mock

import asyncio
Expand All @@ -31,19 +30,6 @@
'sys.stdout.buffer.write(data)'))]


@functools.cache
def _has_pidfd_support():
if not hasattr(os, 'pidfd_open'):
return False

try:
os.close(os.pidfd_open(os.getpid()))
except OSError:
return False

return True


def tearDownModule():
asyncio.set_event_loop_policy(None)

Expand Down Expand Up @@ -688,7 +674,7 @@ def setUp(self):
self.loop = policy.new_event_loop()
self.set_event_loop(self.loop)

watcher = self.Watcher()
watcher = self._get_watcher()
watcher.attach_loop(self.loop)
policy.set_child_watcher(watcher)

Expand All @@ -703,32 +689,38 @@ def tearDown(self):
class SubprocessThreadedWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):

Watcher = unix_events.ThreadedChildWatcher

@unittest.skip("bpo-38323: MultiLoopChildWatcher has a race condition \
and these tests can hang the test suite")
class SubprocessMultiLoopWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):

Watcher = unix_events.MultiLoopChildWatcher
def _get_watcher(self):
return unix_events.ThreadedChildWatcher()

class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):

Watcher = unix_events.SafeChildWatcher
def _get_watcher(self):
with self.assertWarns(DeprecationWarning):
return unix_events.SafeChildWatcher()

class MultiLoopChildWatcherTests(test_utils.TestCase):

def test_warns(self):
with self.assertWarns(DeprecationWarning):
unix_events.MultiLoopChildWatcher()

class SubprocessFastWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):

Watcher = unix_events.FastChildWatcher
def _get_watcher(self):
with self.assertWarns(DeprecationWarning):
return unix_events.FastChildWatcher()

@unittest.skipUnless(
_has_pidfd_support(),
unix_events.can_use_pidfd(),
"operating system does not support pidfds",
)
class SubprocessPidfdWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase):
Watcher = unix_events.PidfdChildWatcher

def _get_watcher(self):
return unix_events.PidfdChildWatcher()


class GenericWatcherTests(test_utils.TestCase):
Expand Down Expand Up @@ -758,7 +750,7 @@ async def execute():


@unittest.skipUnless(
_has_pidfd_support(),
unix_events.can_use_pidfd(),
"operating system does not support pidfds",
)
def test_create_subprocess_with_pidfd(self):
Expand Down
21 changes: 16 additions & 5 deletions Lib/test/test_asyncio/test_unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import threading
import unittest
from unittest import mock
import warnings
from test.support import os_helper
from test.support import socket_helper

Expand Down Expand Up @@ -1686,12 +1687,16 @@ def test_close(self, m_waitpid):

class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self):
return asyncio.SafeChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.SafeChildWatcher()


class FastChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self):
return asyncio.FastChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.FastChildWatcher()


class PolicyTests(unittest.TestCase):
Expand Down Expand Up @@ -1724,7 +1729,9 @@ def test_get_default_child_watcher(self):

def test_get_child_watcher_after_set(self):
policy = self.create_policy()
watcher = asyncio.FastChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.FastChildWatcher()

policy.set_child_watcher(watcher)
self.assertIs(policy._watcher, watcher)
Expand All @@ -1745,7 +1752,9 @@ def f():
policy.get_event_loop().close()

policy = self.create_policy()
policy.set_child_watcher(asyncio.SafeChildWatcher())
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
policy.set_child_watcher(asyncio.SafeChildWatcher())

th = threading.Thread(target=f)
th.start()
Expand All @@ -1757,7 +1766,9 @@ def test_child_watcher_replace_mainloop_existing(self):

# Explicitly setup SafeChildWatcher,
# default ThreadedChildWatcher has no _loop property
watcher = asyncio.SafeChildWatcher()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.SafeChildWatcher()
policy.set_child_watcher(watcher)
watcher.attach_loop(loop)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The child watcher classes :class:`~asyncio.MultiLoopChildWatcher`, :class:`~asyncio.FastChildWatcher` and :class:`~asyncio.SafeChildWatcher` are deprecated and will be removed in Python 3.14. Patch by Kumar Aditya.