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

This commit is contained in:
Kumar Aditya 2022-10-09 02:22:19 +05:30 committed by GitHub
parent 75751f4aa5
commit d8765284f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 38 deletions

View file

@ -109,6 +109,24 @@ New Modules
Improved 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 pathlib
------- -------

View file

@ -1022,6 +1022,13 @@ class SafeChildWatcher(BaseChildWatcher):
big number of children (O(n) each time SIGCHLD is raised) 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): def close(self):
self._callbacks.clear() self._callbacks.clear()
super().close() super().close()
@ -1100,6 +1107,10 @@ class FastChildWatcher(BaseChildWatcher):
self._lock = threading.Lock() self._lock = threading.Lock()
self._zombies = {} self._zombies = {}
self._forks = 0 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): def close(self):
self._callbacks.clear() self._callbacks.clear()
@ -1212,6 +1223,10 @@ class MultiLoopChildWatcher(AbstractChildWatcher):
def __init__(self): def __init__(self):
self._callbacks = {} self._callbacks = {}
self._saved_sighandler = None 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): def is_active(self):
return self._saved_sighandler is not None return self._saved_sighandler is not None

View file

@ -22,7 +22,7 @@ import errno
import unittest import unittest
from unittest import mock from unittest import mock
import weakref import weakref
import warnings
if sys.platform not in ('win32', 'vxworks'): if sys.platform not in ('win32', 'vxworks'):
import tty import tty
@ -2055,6 +2055,8 @@ else:
class UnixEventLoopTestsMixin(EventLoopTestsMixin): class UnixEventLoopTestsMixin(EventLoopTestsMixin):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher() watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop) watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher) asyncio.set_child_watcher(watcher)
@ -2652,6 +2654,8 @@ class GetEventLoopTestsMixin:
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
if sys.platform != 'win32': if sys.platform != 'win32':
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher() watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop) watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher) asyncio.set_child_watcher(watcher)

View file

@ -9,6 +9,7 @@ import sys
import threading import threading
import unittest import unittest
from unittest import mock from unittest import mock
import warnings
from test.support import socket_helper from test.support import socket_helper
try: try:
import ssl import ssl
@ -791,7 +792,8 @@ os.close(fd)
protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop) protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop)
transport, _ = self.loop.run_until_complete( transport, _ = self.loop.run_until_complete(
self.loop.connect_read_pipe(lambda: protocol, pipe)) self.loop.connect_read_pipe(lambda: protocol, pipe))
with warnings.catch_warnings():
warnings.simplefilter('ignore', DeprecationWarning)
watcher = asyncio.SafeChildWatcher() watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop) watcher.attach_loop(self.loop)
try: try:

View file

@ -4,7 +4,6 @@ import signal
import sys import sys
import unittest import unittest
import warnings import warnings
import functools
from unittest import mock from unittest import mock
import asyncio import asyncio
@ -31,19 +30,6 @@ PROGRAM_CAT = [
'sys.stdout.buffer.write(data)'))] '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(): def tearDownModule():
asyncio.set_event_loop_policy(None) asyncio.set_event_loop_policy(None)
@ -688,7 +674,7 @@ if sys.platform != 'win32':
self.loop = policy.new_event_loop() self.loop = policy.new_event_loop()
self.set_event_loop(self.loop) self.set_event_loop(self.loop)
watcher = self.Watcher() watcher = self._get_watcher()
watcher.attach_loop(self.loop) watcher.attach_loop(self.loop)
policy.set_child_watcher(watcher) policy.set_child_watcher(watcher)
@ -703,32 +689,38 @@ if sys.platform != 'win32':
class SubprocessThreadedWatcherTests(SubprocessWatcherMixin, class SubprocessThreadedWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase): test_utils.TestCase):
Watcher = unix_events.ThreadedChildWatcher def _get_watcher(self):
return 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
class SubprocessSafeWatcherTests(SubprocessWatcherMixin, class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase): 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, class SubprocessFastWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase): test_utils.TestCase):
Watcher = unix_events.FastChildWatcher def _get_watcher(self):
with self.assertWarns(DeprecationWarning):
return unix_events.FastChildWatcher()
@unittest.skipUnless( @unittest.skipUnless(
_has_pidfd_support(), unix_events.can_use_pidfd(),
"operating system does not support pidfds", "operating system does not support pidfds",
) )
class SubprocessPidfdWatcherTests(SubprocessWatcherMixin, class SubprocessPidfdWatcherTests(SubprocessWatcherMixin,
test_utils.TestCase): test_utils.TestCase):
Watcher = unix_events.PidfdChildWatcher
def _get_watcher(self):
return unix_events.PidfdChildWatcher()
class GenericWatcherTests(test_utils.TestCase): class GenericWatcherTests(test_utils.TestCase):
@ -758,7 +750,7 @@ if sys.platform != 'win32':
@unittest.skipUnless( @unittest.skipUnless(
_has_pidfd_support(), unix_events.can_use_pidfd(),
"operating system does not support pidfds", "operating system does not support pidfds",
) )
def test_create_subprocess_with_pidfd(self): def test_create_subprocess_with_pidfd(self):

View file

@ -12,6 +12,7 @@ import sys
import threading import threading
import unittest import unittest
from unittest import mock from unittest import mock
import warnings
from test.support import os_helper from test.support import os_helper
from test.support import socket_helper from test.support import socket_helper
@ -1686,11 +1687,15 @@ class ChildWatcherTestsMixin:
class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase): class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self): def create_watcher(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.SafeChildWatcher() return asyncio.SafeChildWatcher()
class FastChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase): class FastChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
def create_watcher(self): def create_watcher(self):
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
return asyncio.FastChildWatcher() return asyncio.FastChildWatcher()
@ -1724,6 +1729,8 @@ class PolicyTests(unittest.TestCase):
def test_get_child_watcher_after_set(self): def test_get_child_watcher_after_set(self):
policy = self.create_policy() policy = self.create_policy()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.FastChildWatcher() watcher = asyncio.FastChildWatcher()
policy.set_child_watcher(watcher) policy.set_child_watcher(watcher)
@ -1745,6 +1752,8 @@ class PolicyTests(unittest.TestCase):
policy.get_event_loop().close() policy.get_event_loop().close()
policy = self.create_policy() policy = self.create_policy()
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
policy.set_child_watcher(asyncio.SafeChildWatcher()) policy.set_child_watcher(asyncio.SafeChildWatcher())
th = threading.Thread(target=f) th = threading.Thread(target=f)
@ -1757,6 +1766,8 @@ class PolicyTests(unittest.TestCase):
# Explicitly setup SafeChildWatcher, # Explicitly setup SafeChildWatcher,
# default ThreadedChildWatcher has no _loop property # default ThreadedChildWatcher has no _loop property
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
watcher = asyncio.SafeChildWatcher() watcher = asyncio.SafeChildWatcher()
policy.set_child_watcher(watcher) policy.set_child_watcher(watcher)
watcher.attach_loop(loop) watcher.attach_loop(loop)

View file

@ -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.