Skip to content

Commit

Permalink
GH-94597: deprecate SafeChildWatcher, FastChildWatcher and `Multi…
Browse files Browse the repository at this point in the history
…LoopChildWatcher` child watchers (#98089)
  • Loading branch information
kumaraditya303 authored Oct 8, 2022
1 parent 75751f4 commit d876528
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 38 deletions.
18 changes: 18 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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.

0 comments on commit d876528

Please sign in to comment.