Skip to content

Commit 96ead91

Browse files
GH-120804: Remove get_child_watcher and set_child_watcher from asyncio (#120818)
1 parent 4717aaa commit 96ead91

File tree

6 files changed

+18
-227
lines changed

6 files changed

+18
-227
lines changed

Lib/asyncio/events.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
'Handle', 'TimerHandle',
1111
'get_event_loop_policy', 'set_event_loop_policy',
1212
'get_event_loop', 'set_event_loop', 'new_event_loop',
13-
'get_child_watcher', 'set_child_watcher',
1413
'_set_running_loop', 'get_running_loop',
1514
'_get_running_loop',
1615
)
@@ -652,17 +651,6 @@ def new_event_loop(self):
652651
the current context, set_event_loop must be called explicitly."""
653652
raise NotImplementedError
654653

655-
# Child processes handling (Unix only).
656-
657-
def get_child_watcher(self):
658-
"Get the watcher for child processes."
659-
raise NotImplementedError
660-
661-
def set_child_watcher(self, watcher):
662-
"""Set the watcher for child processes."""
663-
raise NotImplementedError
664-
665-
666654
class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
667655
"""Default policy implementation for accessing the event loop.
668656
@@ -837,17 +825,6 @@ def new_event_loop():
837825
return get_event_loop_policy().new_event_loop()
838826

839827

840-
def get_child_watcher():
841-
"""Equivalent to calling get_event_loop_policy().get_child_watcher()."""
842-
return get_event_loop_policy().get_child_watcher()
843-
844-
845-
def set_child_watcher(watcher):
846-
"""Equivalent to calling
847-
get_event_loop_policy().set_child_watcher(watcher)."""
848-
return get_event_loop_policy().set_child_watcher(watcher)
849-
850-
851828
# Alias pure-Python implementations for testing purposes.
852829
_py__get_running_loop = _get_running_loop
853830
_py__set_running_loop = _set_running_loop

Lib/asyncio/unix_events.py

Lines changed: 5 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ async def _make_subprocess_transport(self, protocol, args, shell,
199199
extra=None, **kwargs):
200200
with warnings.catch_warnings():
201201
warnings.simplefilter('ignore', DeprecationWarning)
202-
watcher = events.get_child_watcher()
202+
watcher = events.get_event_loop_policy()._watcher
203203

204204
with watcher:
205205
if not watcher.is_active():
@@ -1009,59 +1009,6 @@ def remove_child_handler(self, pid):
10091009
return True
10101010

10111011

1012-
class BaseChildWatcher(AbstractChildWatcher):
1013-
1014-
def __init__(self):
1015-
self._loop = None
1016-
self._callbacks = {}
1017-
1018-
def close(self):
1019-
self.attach_loop(None)
1020-
1021-
def is_active(self):
1022-
return self._loop is not None and self._loop.is_running()
1023-
1024-
def _do_waitpid(self, expected_pid):
1025-
raise NotImplementedError()
1026-
1027-
def _do_waitpid_all(self):
1028-
raise NotImplementedError()
1029-
1030-
def attach_loop(self, loop):
1031-
assert loop is None or isinstance(loop, events.AbstractEventLoop)
1032-
1033-
if self._loop is not None and loop is None and self._callbacks:
1034-
warnings.warn(
1035-
'A loop is being detached '
1036-
'from a child watcher with pending handlers',
1037-
RuntimeWarning)
1038-
1039-
if self._loop is not None:
1040-
self._loop.remove_signal_handler(signal.SIGCHLD)
1041-
1042-
self._loop = loop
1043-
if loop is not None:
1044-
loop.add_signal_handler(signal.SIGCHLD, self._sig_chld)
1045-
1046-
# Prevent a race condition in case a child terminated
1047-
# during the switch.
1048-
self._do_waitpid_all()
1049-
1050-
def _sig_chld(self):
1051-
try:
1052-
self._do_waitpid_all()
1053-
except (SystemExit, KeyboardInterrupt):
1054-
raise
1055-
except BaseException as exc:
1056-
# self._loop should always be available here
1057-
# as '_sig_chld' is added as a signal handler
1058-
# in 'attach_loop'
1059-
self._loop.call_exception_handler({
1060-
'message': 'Unknown exception in SIGCHLD handler',
1061-
'exception': exc,
1062-
})
1063-
1064-
10651012
class ThreadedChildWatcher(AbstractChildWatcher):
10661013
"""Threaded child watcher implementation.
10671014
@@ -1161,15 +1108,10 @@ class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
11611108

11621109
def __init__(self):
11631110
super().__init__()
1164-
self._watcher = None
1165-
1166-
def _init_watcher(self):
1167-
with events._lock:
1168-
if self._watcher is None: # pragma: no branch
1169-
if can_use_pidfd():
1170-
self._watcher = PidfdChildWatcher()
1171-
else:
1172-
self._watcher = ThreadedChildWatcher()
1111+
if can_use_pidfd():
1112+
self._watcher = PidfdChildWatcher()
1113+
else:
1114+
self._watcher = ThreadedChildWatcher()
11731115

11741116
def set_event_loop(self, loop):
11751117
"""Set the event loop.
@@ -1185,33 +1127,6 @@ def set_event_loop(self, loop):
11851127
threading.current_thread() is threading.main_thread()):
11861128
self._watcher.attach_loop(loop)
11871129

1188-
def get_child_watcher(self):
1189-
"""Get the watcher for child processes.
1190-
1191-
If not yet set, a ThreadedChildWatcher object is automatically created.
1192-
"""
1193-
if self._watcher is None:
1194-
self._init_watcher()
1195-
1196-
warnings._deprecated("get_child_watcher",
1197-
"{name!r} is deprecated as of Python 3.12 and will be "
1198-
"removed in Python {remove}.", remove=(3, 14))
1199-
return self._watcher
1200-
1201-
def set_child_watcher(self, watcher):
1202-
"""Set the watcher for child processes."""
1203-
1204-
assert watcher is None or isinstance(watcher, AbstractChildWatcher)
1205-
1206-
if self._watcher is not None:
1207-
self._watcher.close()
1208-
1209-
self._watcher = watcher
1210-
warnings._deprecated("set_child_watcher",
1211-
"{name!r} is deprecated as of Python 3.12 and will be "
1212-
"removed in Python {remove}.", remove=(3, 14))
1213-
1214-
12151130
SelectorEventLoop = _UnixSelectorEventLoop
12161131
DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy
12171132
EventLoop = SelectorEventLoop

Lib/test/test_asyncio/test_events.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2212,16 +2212,14 @@ def test_remove_fds_after_closing(self):
22122212
class UnixEventLoopTestsMixin(EventLoopTestsMixin):
22132213
def setUp(self):
22142214
super().setUp()
2215-
with warnings.catch_warnings():
2216-
warnings.simplefilter('ignore', DeprecationWarning)
2217-
watcher = asyncio.ThreadedChildWatcher()
2218-
watcher.attach_loop(self.loop)
2219-
asyncio.set_child_watcher(watcher)
2215+
watcher = asyncio.ThreadedChildWatcher()
2216+
watcher.attach_loop(self.loop)
2217+
policy = asyncio.get_event_loop_policy()
2218+
policy._watcher = watcher
22202219

22212220
def tearDown(self):
2222-
with warnings.catch_warnings():
2223-
warnings.simplefilter('ignore', DeprecationWarning)
2224-
asyncio.set_child_watcher(None)
2221+
policy = asyncio.get_event_loop_policy()
2222+
policy._watcher = None
22252223
super().tearDown()
22262224

22272225

@@ -2716,9 +2714,6 @@ def test_event_loop_policy(self):
27162714
self.assertRaises(NotImplementedError, policy.get_event_loop)
27172715
self.assertRaises(NotImplementedError, policy.set_event_loop, object())
27182716
self.assertRaises(NotImplementedError, policy.new_event_loop)
2719-
self.assertRaises(NotImplementedError, policy.get_child_watcher)
2720-
self.assertRaises(NotImplementedError, policy.set_child_watcher,
2721-
object())
27222717

27232718
def test_get_event_loop(self):
27242719
policy = asyncio.DefaultEventLoopPolicy()
@@ -2836,9 +2831,8 @@ def setUp(self):
28362831
def tearDown(self):
28372832
try:
28382833
if sys.platform != 'win32':
2839-
with warnings.catch_warnings():
2840-
warnings.simplefilter('ignore', DeprecationWarning)
2841-
asyncio.set_child_watcher(None)
2834+
policy = asyncio.get_event_loop_policy()
2835+
policy._watcher = None
28422836

28432837
super().tearDown()
28442838
finally:

Lib/test/test_asyncio/test_subprocess.py

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -879,17 +879,13 @@ def setUp(self):
879879

880880
watcher = self._get_watcher()
881881
watcher.attach_loop(self.loop)
882-
with warnings.catch_warnings():
883-
warnings.simplefilter('ignore', DeprecationWarning)
884-
policy.set_child_watcher(watcher)
882+
policy._watcher = watcher
885883

886884
def tearDown(self):
887885
super().tearDown()
888886
policy = asyncio.get_event_loop_policy()
889-
with warnings.catch_warnings():
890-
warnings.simplefilter('ignore', DeprecationWarning)
891-
watcher = policy.get_child_watcher()
892-
policy.set_child_watcher(None)
887+
watcher = policy._watcher
888+
policy._watcher = None
893889
watcher.attach_loop(None)
894890
watcher.close()
895891

@@ -910,66 +906,6 @@ def _get_watcher(self):
910906
return unix_events.PidfdChildWatcher()
911907

912908

913-
class GenericWatcherTests(test_utils.TestCase):
914-
915-
def test_create_subprocess_fails_with_inactive_watcher(self):
916-
watcher = mock.create_autospec(asyncio.AbstractChildWatcher)
917-
watcher.is_active.return_value = False
918-
919-
async def execute():
920-
asyncio.set_child_watcher(watcher)
921-
922-
with self.assertRaises(RuntimeError):
923-
await subprocess.create_subprocess_exec(
924-
os_helper.FakePath(sys.executable), '-c', 'pass')
925-
926-
watcher.add_child_handler.assert_not_called()
927-
928-
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
929-
with warnings.catch_warnings():
930-
warnings.simplefilter('ignore', DeprecationWarning)
931-
self.assertIsNone(runner.run(execute()))
932-
self.assertListEqual(watcher.mock_calls, [
933-
mock.call.__enter__(),
934-
mock.call.is_active(),
935-
mock.call.__exit__(RuntimeError, mock.ANY, mock.ANY),
936-
], watcher.mock_calls)
937-
938-
939-
@unittest.skipUnless(
940-
unix_events.can_use_pidfd(),
941-
"operating system does not support pidfds",
942-
)
943-
def test_create_subprocess_with_pidfd(self):
944-
async def in_thread():
945-
proc = await asyncio.create_subprocess_exec(
946-
*PROGRAM_CAT,
947-
stdin=subprocess.PIPE,
948-
stdout=subprocess.PIPE,
949-
)
950-
stdout, stderr = await proc.communicate(b"some data")
951-
return proc.returncode, stdout
952-
953-
async def main():
954-
# asyncio.Runner did not call asyncio.set_event_loop()
955-
with warnings.catch_warnings():
956-
warnings.simplefilter('error', DeprecationWarning)
957-
# get_event_loop() raises DeprecationWarning if
958-
# set_event_loop() was never called and RuntimeError if
959-
# it was called at least once.
960-
with self.assertRaises((RuntimeError, DeprecationWarning)):
961-
asyncio.get_event_loop_policy().get_event_loop()
962-
return await asyncio.to_thread(asyncio.run, in_thread())
963-
with self.assertWarns(DeprecationWarning):
964-
asyncio.set_child_watcher(asyncio.PidfdChildWatcher())
965-
try:
966-
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner:
967-
returncode, stdout = runner.run(main())
968-
self.assertEqual(returncode, 0)
969-
self.assertEqual(stdout, b'some data')
970-
finally:
971-
with self.assertWarns(DeprecationWarning):
972-
asyncio.set_child_watcher(None)
973909
else:
974910
# Windows
975911
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase):

Lib/test/test_asyncio/test_unix_events.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,35 +1138,6 @@ def test_not_implemented(self):
11381138
NotImplementedError, watcher.__exit__, f, f, f)
11391139

11401140

1141-
class PolicyTests(unittest.TestCase):
1142-
1143-
def create_policy(self):
1144-
return asyncio.DefaultEventLoopPolicy()
1145-
1146-
@mock.patch('asyncio.unix_events.can_use_pidfd')
1147-
def test_get_default_child_watcher(self, m_can_use_pidfd):
1148-
m_can_use_pidfd.return_value = False
1149-
policy = self.create_policy()
1150-
self.assertIsNone(policy._watcher)
1151-
with self.assertWarns(DeprecationWarning):
1152-
watcher = policy.get_child_watcher()
1153-
self.assertIsInstance(watcher, asyncio.ThreadedChildWatcher)
1154-
1155-
self.assertIs(policy._watcher, watcher)
1156-
with self.assertWarns(DeprecationWarning):
1157-
self.assertIs(watcher, policy.get_child_watcher())
1158-
1159-
m_can_use_pidfd.return_value = True
1160-
policy = self.create_policy()
1161-
self.assertIsNone(policy._watcher)
1162-
with self.assertWarns(DeprecationWarning):
1163-
watcher = policy.get_child_watcher()
1164-
self.assertIsInstance(watcher, asyncio.PidfdChildWatcher)
1165-
1166-
self.assertIs(policy._watcher, watcher)
1167-
with self.assertWarns(DeprecationWarning):
1168-
self.assertIs(watcher, policy.get_child_watcher())
1169-
11701141
class TestFunctional(unittest.TestCase):
11711142

11721143
def setUp(self):

Lib/test/test_asyncio/utils.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,10 +550,8 @@ def close_loop(loop):
550550
policy = support.maybe_get_event_loop_policy()
551551
if policy is not None:
552552
try:
553-
with warnings.catch_warnings():
554-
warnings.simplefilter('ignore', DeprecationWarning)
555-
watcher = policy.get_child_watcher()
556-
except NotImplementedError:
553+
watcher = policy._watcher
554+
except AttributeError:
557555
# watcher is not implemented by EventLoopPolicy, e.g. Windows
558556
pass
559557
else:

0 commit comments

Comments
 (0)