Skip to content

Commit 660f102

Browse files
pythonGH-94597: Deprecate child watcher getters and setters (python#98215)
This is the next step for deprecating child watchers. Until we've removed the API completely we have to use it, so this PR is mostly suppressing a lot of warnings when using the API internally. Once the child watcher API is totally removed, the two child watcher implementations we actually use and need (Pidfd and Thread) will be turned into internal helpers.
1 parent bb56dea commit 660f102

File tree

9 files changed

+105
-52
lines changed

9 files changed

+105
-52
lines changed

Doc/library/asyncio-policy.rst

+14
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,16 @@ The abstract event loop policy base class is defined as follows:
8888

8989
This function is Unix specific.
9090

91+
.. deprecated:: 3.12
92+
9193
.. method:: set_child_watcher(watcher)
9294

9395
Set the current child process watcher to *watcher*.
9496

9597
This function is Unix specific.
9698

99+
.. deprecated:: 3.12
100+
97101

98102
.. _asyncio-policy-builtin:
99103

@@ -158,12 +162,16 @@ implementation used by the asyncio event loop:
158162

159163
Return the current child watcher for the current policy.
160164

165+
.. deprecated:: 3.12
166+
161167
.. function:: set_child_watcher(watcher)
162168

163169
Set the current child watcher to *watcher* for the current
164170
policy. *watcher* must implement methods defined in the
165171
:class:`AbstractChildWatcher` base class.
166172

173+
.. deprecated:: 3.12
174+
167175
.. note::
168176
Third-party event loops implementations might not support
169177
custom child watchers. For such event loops, using
@@ -245,6 +253,8 @@ implementation used by the asyncio event loop:
245253

246254
.. versionadded:: 3.8
247255

256+
.. deprecated:: 3.12
257+
248258
.. class:: SafeChildWatcher
249259

250260
This implementation uses active event loop from the main thread to handle
@@ -257,6 +267,8 @@ implementation used by the asyncio event loop:
257267
This solution is as safe as :class:`MultiLoopChildWatcher` and has the same *O(N)*
258268
complexity but requires a running event loop in the main thread to work.
259269

270+
.. deprecated:: 3.12
271+
260272
.. class:: FastChildWatcher
261273

262274
This implementation reaps every terminated processes by calling
@@ -269,6 +281,8 @@ implementation used by the asyncio event loop:
269281
This solution requires a running event loop in the main thread to work, as
270282
:class:`SafeChildWatcher`.
271283

284+
.. deprecated:: 3.12
285+
272286
.. class:: PidfdChildWatcher
273287

274288
This implementation polls process file descriptors (pidfds) to await child

Doc/whatsnew/3.12.rst

+6
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ asyncio
126126
if supported and :class:`~asyncio.ThreadedChildWatcher` otherwise).
127127
(Contributed by Kumar Aditya in :gh:`94597`.)
128128

129+
* :func:`asyncio.set_child_watcher`, :func:`asyncio.get_child_watcher`,
130+
:meth:`asyncio.AbstractEventLoopPolicy.set_child_watcher` and
131+
:meth:`asyncio.AbstractEventLoopPolicy.get_child_watcher` are deprecated
132+
and will be removed in Python 3.14.
133+
(Contributed by Kumar Aditya in :gh:`94597`.)
134+
129135

130136
pathlib
131137
-------

Lib/asyncio/unix_events.py

+32-24
Original file line numberDiff line numberDiff line change
@@ -195,30 +195,32 @@ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
195195
async def _make_subprocess_transport(self, protocol, args, shell,
196196
stdin, stdout, stderr, bufsize,
197197
extra=None, **kwargs):
198-
with events.get_child_watcher() as watcher:
199-
if not watcher.is_active():
200-
# Check early.
201-
# Raising exception before process creation
202-
# prevents subprocess execution if the watcher
203-
# is not ready to handle it.
204-
raise RuntimeError("asyncio.get_child_watcher() is not activated, "
205-
"subprocess support is not installed.")
206-
waiter = self.create_future()
207-
transp = _UnixSubprocessTransport(self, protocol, args, shell,
208-
stdin, stdout, stderr, bufsize,
209-
waiter=waiter, extra=extra,
210-
**kwargs)
211-
212-
watcher.add_child_handler(transp.get_pid(),
213-
self._child_watcher_callback, transp)
214-
try:
215-
await waiter
216-
except (SystemExit, KeyboardInterrupt):
217-
raise
218-
except BaseException:
219-
transp.close()
220-
await transp._wait()
221-
raise
198+
with warnings.catch_warnings():
199+
warnings.simplefilter('ignore', DeprecationWarning)
200+
with events.get_child_watcher() as watcher:
201+
if not watcher.is_active():
202+
# Check early.
203+
# Raising exception before process creation
204+
# prevents subprocess execution if the watcher
205+
# is not ready to handle it.
206+
raise RuntimeError("asyncio.get_child_watcher() is not activated, "
207+
"subprocess support is not installed.")
208+
waiter = self.create_future()
209+
transp = _UnixSubprocessTransport(self, protocol, args, shell,
210+
stdin, stdout, stderr, bufsize,
211+
waiter=waiter, extra=extra,
212+
**kwargs)
213+
214+
watcher.add_child_handler(transp.get_pid(),
215+
self._child_watcher_callback, transp)
216+
try:
217+
await waiter
218+
except (SystemExit, KeyboardInterrupt):
219+
raise
220+
except BaseException:
221+
transp.close()
222+
await transp._wait()
223+
raise
222224

223225
return transp
224226

@@ -1469,6 +1471,9 @@ def get_child_watcher(self):
14691471
if self._watcher is None:
14701472
self._init_watcher()
14711473

1474+
warnings._deprecated("get_child_watcher",
1475+
"{name!r} is deprecated as of Python 3.12 and will be "
1476+
"removed in Python {remove}.", remove=(3, 14))
14721477
return self._watcher
14731478

14741479
def set_child_watcher(self, watcher):
@@ -1480,6 +1485,9 @@ def set_child_watcher(self, watcher):
14801485
self._watcher.close()
14811486

14821487
self._watcher = watcher
1488+
warnings._deprecated("set_child_watcher",
1489+
"{name!r} is deprecated as of Python 3.12 and will be "
1490+
"removed in Python {remove}.", remove=(3, 14))
14831491

14841492

14851493
SelectorEventLoop = _UnixSelectorEventLoop

Lib/test/test_asyncio/test_events.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -2058,11 +2058,13 @@ def setUp(self):
20582058
with warnings.catch_warnings():
20592059
warnings.simplefilter('ignore', DeprecationWarning)
20602060
watcher = asyncio.SafeChildWatcher()
2061-
watcher.attach_loop(self.loop)
2062-
asyncio.set_child_watcher(watcher)
2061+
watcher.attach_loop(self.loop)
2062+
asyncio.set_child_watcher(watcher)
20632063

20642064
def tearDown(self):
2065-
asyncio.set_child_watcher(None)
2065+
with warnings.catch_warnings():
2066+
warnings.simplefilter('ignore', DeprecationWarning)
2067+
asyncio.set_child_watcher(None)
20662068
super().tearDown()
20672069

20682070

@@ -2657,13 +2659,15 @@ def setUp(self):
26572659
with warnings.catch_warnings():
26582660
warnings.simplefilter('ignore', DeprecationWarning)
26592661
watcher = asyncio.SafeChildWatcher()
2660-
watcher.attach_loop(self.loop)
2661-
asyncio.set_child_watcher(watcher)
2662+
watcher.attach_loop(self.loop)
2663+
asyncio.set_child_watcher(watcher)
26622664

26632665
def tearDown(self):
26642666
try:
26652667
if sys.platform != 'win32':
2666-
asyncio.set_child_watcher(None)
2668+
with warnings.catch_warnings():
2669+
warnings.simplefilter('ignore', DeprecationWarning)
2670+
asyncio.set_child_watcher(None)
26672671

26682672
super().tearDown()
26692673
finally:

Lib/test/test_asyncio/test_streams.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -797,15 +797,19 @@ def test_read_all_from_pipe_reader(self):
797797
watcher = asyncio.SafeChildWatcher()
798798
watcher.attach_loop(self.loop)
799799
try:
800-
asyncio.set_child_watcher(watcher)
800+
with warnings.catch_warnings():
801+
warnings.simplefilter('ignore', DeprecationWarning)
802+
asyncio.set_child_watcher(watcher)
801803
create = asyncio.create_subprocess_exec(
802804
*args,
803805
pass_fds={wfd},
804806
)
805807
proc = self.loop.run_until_complete(create)
806808
self.loop.run_until_complete(proc.wait())
807809
finally:
808-
asyncio.set_child_watcher(None)
810+
with warnings.catch_warnings():
811+
warnings.simplefilter('ignore', DeprecationWarning)
812+
asyncio.set_child_watcher(None)
809813

810814
os.close(wfd)
811815
data = self.loop.run_until_complete(reader.read(-1))

Lib/test/test_asyncio/test_subprocess.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,9 @@ async def kill_running():
582582
# manually to avoid a warning when the watcher is detached.
583583
if (sys.platform != 'win32' and
584584
isinstance(self, SubprocessFastWatcherTests)):
585-
asyncio.get_child_watcher()._callbacks.clear()
585+
with warnings.catch_warnings():
586+
warnings.simplefilter('ignore', DeprecationWarning)
587+
asyncio.get_child_watcher()._callbacks.clear()
586588

587589
async def _test_popen_error(self, stdin):
588590
if sys.platform == 'win32':
@@ -696,13 +698,17 @@ def setUp(self):
696698

697699
watcher = self._get_watcher()
698700
watcher.attach_loop(self.loop)
699-
policy.set_child_watcher(watcher)
701+
with warnings.catch_warnings():
702+
warnings.simplefilter('ignore', DeprecationWarning)
703+
policy.set_child_watcher(watcher)
700704

701705
def tearDown(self):
702706
super().tearDown()
703707
policy = asyncio.get_event_loop_policy()
704-
watcher = policy.get_child_watcher()
705-
policy.set_child_watcher(None)
708+
with warnings.catch_warnings():
709+
warnings.simplefilter('ignore', DeprecationWarning)
710+
watcher = policy.get_child_watcher()
711+
policy.set_child_watcher(None)
706712
watcher.attach_loop(None)
707713
watcher.close()
708714

@@ -752,7 +758,9 @@ def test_create_subprocess_fails_with_inactive_watcher(self):
752758
)
753759

754760
async def execute():
755-
asyncio.set_child_watcher(watcher)
761+
with warnings.catch_warnings():
762+
warnings.simplefilter('ignore', DeprecationWarning)
763+
asyncio.set_child_watcher(watcher)
756764

757765
with self.assertRaises(RuntimeError):
758766
await subprocess.create_subprocess_exec(
@@ -761,7 +769,9 @@ async def execute():
761769
watcher.add_child_handler.assert_not_called()
762770

763771
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
764-
self.assertIsNone(runner.run(execute()))
772+
with warnings.catch_warnings():
773+
warnings.simplefilter('ignore', DeprecationWarning)
774+
self.assertIsNone(runner.run(execute()))
765775
self.assertListEqual(watcher.mock_calls, [
766776
mock.call.__enter__(),
767777
mock.call.__enter__().is_active(),
@@ -788,15 +798,16 @@ async def main():
788798
with self.assertRaises(RuntimeError):
789799
asyncio.get_event_loop_policy().get_event_loop()
790800
return await asyncio.to_thread(asyncio.run, in_thread())
791-
792-
asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
801+
with self.assertWarns(DeprecationWarning):
802+
asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
793803
try:
794804
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
795805
returncode, stdout = runner.run(main())
796806
self.assertEqual(returncode, 0)
797807
self.assertEqual(stdout, b'some data')
798808
finally:
799-
asyncio.set_child_watcher(None)
809+
with self.assertWarns(DeprecationWarning):
810+
asyncio.set_child_watcher(None)
800811
else:
801812
# Windows
802813
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):

Lib/test/test_asyncio/test_unix_events.py

+12-9
Original file line numberDiff line numberDiff line change
@@ -1709,33 +1709,36 @@ def test_get_default_child_watcher(self):
17091709
self.assertIsNone(policy._watcher)
17101710
unix_events.can_use_pidfd = mock.Mock()
17111711
unix_events.can_use_pidfd.return_value = False
1712-
watcher = policy.get_child_watcher()
1712+
with self.assertWarns(DeprecationWarning):
1713+
watcher = policy.get_child_watcher()
17131714
self.assertIsInstance(watcher, asyncio.ThreadedChildWatcher)
17141715

17151716
self.assertIs(policy._watcher, watcher)
1716-
1717-
self.assertIs(watcher, policy.get_child_watcher())
1717+
with self.assertWarns(DeprecationWarning):
1718+
self.assertIs(watcher, policy.get_child_watcher())
17181719

17191720
policy = self.create_policy()
17201721
self.assertIsNone(policy._watcher)
17211722
unix_events.can_use_pidfd = mock.Mock()
17221723
unix_events.can_use_pidfd.return_value = True
1723-
watcher = policy.get_child_watcher()
1724+
with self.assertWarns(DeprecationWarning):
1725+
watcher = policy.get_child_watcher()
17241726
self.assertIsInstance(watcher, asyncio.PidfdChildWatcher)
17251727

17261728
self.assertIs(policy._watcher, watcher)
1727-
1728-
self.assertIs(watcher, policy.get_child_watcher())
1729+
with self.assertWarns(DeprecationWarning):
1730+
self.assertIs(watcher, policy.get_child_watcher())
17291731

17301732
def test_get_child_watcher_after_set(self):
17311733
policy = self.create_policy()
17321734
with warnings.catch_warnings():
17331735
warnings.simplefilter("ignore", DeprecationWarning)
17341736
watcher = asyncio.FastChildWatcher()
1737+
policy.set_child_watcher(watcher)
17351738

1736-
policy.set_child_watcher(watcher)
17371739
self.assertIs(policy._watcher, watcher)
1738-
self.assertIs(watcher, policy.get_child_watcher())
1740+
with self.assertWarns(DeprecationWarning):
1741+
self.assertIs(watcher, policy.get_child_watcher())
17391742

17401743
def test_get_child_watcher_thread(self):
17411744

@@ -1769,7 +1772,7 @@ def test_child_watcher_replace_mainloop_existing(self):
17691772
with warnings.catch_warnings():
17701773
warnings.simplefilter("ignore", DeprecationWarning)
17711774
watcher = asyncio.SafeChildWatcher()
1772-
policy.set_child_watcher(watcher)
1775+
policy.set_child_watcher(watcher)
17731776
watcher.attach_loop(loop)
17741777

17751778
self.assertIs(watcher._loop, loop)

Lib/test/test_asyncio/utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import threading
1515
import unittest
1616
import weakref
17-
17+
import warnings
1818
from unittest import mock
1919

2020
from http.server import HTTPServer
@@ -544,7 +544,9 @@ def close_loop(loop):
544544
policy = support.maybe_get_event_loop_policy()
545545
if policy is not None:
546546
try:
547-
watcher = policy.get_child_watcher()
547+
with warnings.catch_warnings():
548+
warnings.simplefilter('ignore', DeprecationWarning)
549+
watcher = policy.get_child_watcher()
548550
except NotImplementedError:
549551
# watcher is not implemented by EventLoopPolicy, e.g. Windows
550552
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecated :meth:`asyncio.AbstractEventLoopPolicy.get_child_watcher` and :meth:`asyncio.AbstractEventLoopPolicy.set_child_watcher` methods to be removed in Python 3.14. Patch by Kumar Aditya.

0 commit comments

Comments
 (0)