Skip to content

Commit 9d2e1ea

Browse files
GH-120804: Remove PidfdChildWatcher, ThreadedChildWatcher and AbstractChildWatcher from asyncio APIs (#120893)
1 parent b6fa8fe commit 9d2e1ea

File tree

5 files changed

+39
-259
lines changed

5 files changed

+39
-259
lines changed

Lib/asyncio/unix_events.py

Lines changed: 23 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@
2828

2929
__all__ = (
3030
'SelectorEventLoop',
31-
'AbstractChildWatcher',
32-
'PidfdChildWatcher',
33-
'ThreadedChildWatcher',
3431
'DefaultEventLoopPolicy',
3532
'EventLoop',
3633
)
@@ -65,6 +62,10 @@ def __init__(self, selector=None):
6562
super().__init__(selector)
6663
self._signal_handlers = {}
6764
self._unix_server_sockets = {}
65+
if can_use_pidfd():
66+
self._watcher = _PidfdChildWatcher()
67+
else:
68+
self._watcher = _ThreadedChildWatcher()
6869

6970
def close(self):
7071
super().close()
@@ -197,33 +198,22 @@ def _make_write_pipe_transport(self, pipe, protocol, waiter=None,
197198
async def _make_subprocess_transport(self, protocol, args, shell,
198199
stdin, stdout, stderr, bufsize,
199200
extra=None, **kwargs):
200-
with warnings.catch_warnings():
201-
warnings.simplefilter('ignore', DeprecationWarning)
202-
watcher = events.get_event_loop_policy()._watcher
203-
204-
with watcher:
205-
if not watcher.is_active():
206-
# Check early.
207-
# Raising exception before process creation
208-
# prevents subprocess execution if the watcher
209-
# is not ready to handle it.
210-
raise RuntimeError("asyncio.get_child_watcher() is not activated, "
211-
"subprocess support is not installed.")
212-
waiter = self.create_future()
213-
transp = _UnixSubprocessTransport(self, protocol, args, shell,
214-
stdin, stdout, stderr, bufsize,
215-
waiter=waiter, extra=extra,
216-
**kwargs)
217-
watcher.add_child_handler(transp.get_pid(),
218-
self._child_watcher_callback, transp)
219-
try:
220-
await waiter
221-
except (SystemExit, KeyboardInterrupt):
222-
raise
223-
except BaseException:
224-
transp.close()
225-
await transp._wait()
226-
raise
201+
watcher = self._watcher
202+
waiter = self.create_future()
203+
transp = _UnixSubprocessTransport(self, protocol, args, shell,
204+
stdin, stdout, stderr, bufsize,
205+
waiter=waiter, extra=extra,
206+
**kwargs)
207+
watcher.add_child_handler(transp.get_pid(),
208+
self._child_watcher_callback, transp)
209+
try:
210+
await waiter
211+
except (SystemExit, KeyboardInterrupt):
212+
raise
213+
except BaseException:
214+
transp.close()
215+
await transp._wait()
216+
raise
227217

228218
return transp
229219

@@ -865,93 +855,7 @@ def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs):
865855
stdin_w.close()
866856

867857

868-
class AbstractChildWatcher:
869-
"""Abstract base class for monitoring child processes.
870-
871-
Objects derived from this class monitor a collection of subprocesses and
872-
report their termination or interruption by a signal.
873-
874-
New callbacks are registered with .add_child_handler(). Starting a new
875-
process must be done within a 'with' block to allow the watcher to suspend
876-
its activity until the new process if fully registered (this is needed to
877-
prevent a race condition in some implementations).
878-
879-
Example:
880-
with watcher:
881-
proc = subprocess.Popen("sleep 1")
882-
watcher.add_child_handler(proc.pid, callback)
883-
884-
Notes:
885-
Implementations of this class must be thread-safe.
886-
887-
Since child watcher objects may catch the SIGCHLD signal and call
888-
waitpid(-1), there should be only one active object per process.
889-
"""
890-
891-
def __init_subclass__(cls) -> None:
892-
if cls.__module__ != __name__:
893-
warnings._deprecated("AbstractChildWatcher",
894-
"{name!r} is deprecated as of Python 3.12 and will be "
895-
"removed in Python {remove}.",
896-
remove=(3, 14))
897-
898-
def add_child_handler(self, pid, callback, *args):
899-
"""Register a new child handler.
900-
901-
Arrange for callback(pid, returncode, *args) to be called when
902-
process 'pid' terminates. Specifying another callback for the same
903-
process replaces the previous handler.
904-
905-
Note: callback() must be thread-safe.
906-
"""
907-
raise NotImplementedError()
908-
909-
def remove_child_handler(self, pid):
910-
"""Removes the handler for process 'pid'.
911-
912-
The function returns True if the handler was successfully removed,
913-
False if there was nothing to remove."""
914-
915-
raise NotImplementedError()
916-
917-
def attach_loop(self, loop):
918-
"""Attach the watcher to an event loop.
919-
920-
If the watcher was previously attached to an event loop, then it is
921-
first detached before attaching to the new loop.
922-
923-
Note: loop may be None.
924-
"""
925-
raise NotImplementedError()
926-
927-
def close(self):
928-
"""Close the watcher.
929-
930-
This must be called to make sure that any underlying resource is freed.
931-
"""
932-
raise NotImplementedError()
933-
934-
def is_active(self):
935-
"""Return ``True`` if the watcher is active and is used by the event loop.
936-
937-
Return True if the watcher is installed and ready to handle process exit
938-
notifications.
939-
940-
"""
941-
raise NotImplementedError()
942-
943-
def __enter__(self):
944-
"""Enter the watcher's context and allow starting new processes
945-
946-
This function must return self"""
947-
raise NotImplementedError()
948-
949-
def __exit__(self, a, b, c):
950-
"""Exit the watcher's context"""
951-
raise NotImplementedError()
952-
953-
954-
class PidfdChildWatcher(AbstractChildWatcher):
858+
class _PidfdChildWatcher:
955859
"""Child watcher implementation using Linux's pid file descriptors.
956860
957861
This child watcher polls process file descriptors (pidfds) to await child
@@ -963,21 +867,9 @@ class PidfdChildWatcher(AbstractChildWatcher):
963867
recent (5.3+) kernels.
964868
"""
965869

966-
def __enter__(self):
967-
return self
968-
969-
def __exit__(self, exc_type, exc_value, exc_traceback):
970-
pass
971-
972870
def is_active(self):
973871
return True
974872

975-
def close(self):
976-
pass
977-
978-
def attach_loop(self, loop):
979-
pass
980-
981873
def add_child_handler(self, pid, callback, *args):
982874
loop = events.get_running_loop()
983875
pidfd = os.pidfd_open(pid)
@@ -1002,14 +894,7 @@ def _do_wait(self, pid, pidfd, callback, args):
1002894
os.close(pidfd)
1003895
callback(pid, returncode, *args)
1004896

1005-
def remove_child_handler(self, pid):
1006-
# asyncio never calls remove_child_handler() !!!
1007-
# The method is no-op but is implemented because
1008-
# abstract base classes require it.
1009-
return True
1010-
1011-
1012-
class ThreadedChildWatcher(AbstractChildWatcher):
897+
class _ThreadedChildWatcher:
1013898
"""Threaded child watcher implementation.
1014899
1015900
The watcher uses a thread per process
@@ -1029,15 +914,6 @@ def __init__(self):
1029914
def is_active(self):
1030915
return True
1031916

1032-
def close(self):
1033-
pass
1034-
1035-
def __enter__(self):
1036-
return self
1037-
1038-
def __exit__(self, exc_type, exc_val, exc_tb):
1039-
pass
1040-
1041917
def __del__(self, _warn=warnings.warn):
1042918
threads = [thread for thread in list(self._threads.values())
1043919
if thread.is_alive()]
@@ -1055,15 +931,6 @@ def add_child_handler(self, pid, callback, *args):
1055931
self._threads[pid] = thread
1056932
thread.start()
1057933

1058-
def remove_child_handler(self, pid):
1059-
# asyncio never calls remove_child_handler() !!!
1060-
# The method is no-op but is implemented because
1061-
# abstract base classes require it.
1062-
return True
1063-
1064-
def attach_loop(self, loop):
1065-
pass
1066-
1067934
def _do_waitpid(self, loop, expected_pid, callback, args):
1068935
assert expected_pid > 0
1069936

@@ -1103,29 +970,9 @@ def can_use_pidfd():
1103970

1104971

1105972
class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
1106-
"""UNIX event loop policy with a watcher for child processes."""
973+
"""UNIX event loop policy"""
1107974
_loop_factory = _UnixSelectorEventLoop
1108975

1109-
def __init__(self):
1110-
super().__init__()
1111-
if can_use_pidfd():
1112-
self._watcher = PidfdChildWatcher()
1113-
else:
1114-
self._watcher = ThreadedChildWatcher()
1115-
1116-
def set_event_loop(self, loop):
1117-
"""Set the event loop.
1118-
1119-
As a side effect, if a child watcher was set before, then calling
1120-
.set_event_loop() from the main thread will call .attach_loop(loop) on
1121-
the child watcher.
1122-
"""
1123-
1124-
super().set_event_loop(loop)
1125-
1126-
if (self._watcher is not None and
1127-
threading.current_thread() is threading.main_thread()):
1128-
self._watcher.attach_loop(loop)
1129976

1130977
SelectorEventLoop = _UnixSelectorEventLoop
1131978
DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy

Lib/test/test_asyncio/test_events.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,22 +2209,8 @@ def test_remove_fds_after_closing(self):
22092209
else:
22102210
import selectors
22112211

2212-
class UnixEventLoopTestsMixin(EventLoopTestsMixin):
2213-
def setUp(self):
2214-
super().setUp()
2215-
watcher = asyncio.ThreadedChildWatcher()
2216-
watcher.attach_loop(self.loop)
2217-
policy = asyncio.get_event_loop_policy()
2218-
policy._watcher = watcher
2219-
2220-
def tearDown(self):
2221-
policy = asyncio.get_event_loop_policy()
2222-
policy._watcher = None
2223-
super().tearDown()
2224-
2225-
22262212
if hasattr(selectors, 'KqueueSelector'):
2227-
class KqueueEventLoopTests(UnixEventLoopTestsMixin,
2213+
class KqueueEventLoopTests(EventLoopTestsMixin,
22282214
SubprocessTestsMixin,
22292215
test_utils.TestCase):
22302216

@@ -2249,23 +2235,23 @@ def test_write_pty(self):
22492235
super().test_write_pty()
22502236

22512237
if hasattr(selectors, 'EpollSelector'):
2252-
class EPollEventLoopTests(UnixEventLoopTestsMixin,
2238+
class EPollEventLoopTests(EventLoopTestsMixin,
22532239
SubprocessTestsMixin,
22542240
test_utils.TestCase):
22552241

22562242
def create_event_loop(self):
22572243
return asyncio.SelectorEventLoop(selectors.EpollSelector())
22582244

22592245
if hasattr(selectors, 'PollSelector'):
2260-
class PollEventLoopTests(UnixEventLoopTestsMixin,
2246+
class PollEventLoopTests(EventLoopTestsMixin,
22612247
SubprocessTestsMixin,
22622248
test_utils.TestCase):
22632249

22642250
def create_event_loop(self):
22652251
return asyncio.SelectorEventLoop(selectors.PollSelector())
22662252

22672253
# Should always exist.
2268-
class SelectEventLoopTests(UnixEventLoopTestsMixin,
2254+
class SelectEventLoopTests(EventLoopTestsMixin,
22692255
SubprocessTestsMixin,
22702256
test_utils.TestCase):
22712257

@@ -2830,10 +2816,6 @@ def setUp(self):
28302816

28312817
def tearDown(self):
28322818
try:
2833-
if sys.platform != 'win32':
2834-
policy = asyncio.get_event_loop_policy()
2835-
policy._watcher = None
2836-
28372819
super().tearDown()
28382820
finally:
28392821
self.loop.close()

Lib/test/test_asyncio/test_subprocess.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -869,31 +869,27 @@ async def main():
869869
# Unix
870870
class SubprocessWatcherMixin(SubprocessMixin):
871871

872-
Watcher = None
873-
874872
def setUp(self):
875873
super().setUp()
876874
policy = asyncio.get_event_loop_policy()
877875
self.loop = policy.new_event_loop()
878876
self.set_event_loop(self.loop)
879877

880-
watcher = self._get_watcher()
881-
watcher.attach_loop(self.loop)
882-
policy._watcher = watcher
878+
def test_watcher_implementation(self):
879+
loop = self.loop
880+
watcher = loop._watcher
881+
if unix_events.can_use_pidfd():
882+
self.assertIsInstance(watcher, unix_events._PidfdChildWatcher)
883+
else:
884+
self.assertIsInstance(watcher, unix_events._ThreadedChildWatcher)
883885

884-
def tearDown(self):
885-
super().tearDown()
886-
policy = asyncio.get_event_loop_policy()
887-
watcher = policy._watcher
888-
policy._watcher = None
889-
watcher.attach_loop(None)
890-
watcher.close()
891886

892887
class SubprocessThreadedWatcherTests(SubprocessWatcherMixin,
893888
test_utils.TestCase):
894-
895-
def _get_watcher(self):
896-
return unix_events.ThreadedChildWatcher()
889+
def setUp(self):
890+
# Force the use of the threaded child watcher
891+
unix_events.can_use_pidfd = mock.Mock(return_value=False)
892+
super().setUp()
897893

898894
@unittest.skipUnless(
899895
unix_events.can_use_pidfd(),
@@ -902,9 +898,7 @@ def _get_watcher(self):
902898
class SubprocessPidfdWatcherTests(SubprocessWatcherMixin,
903899
test_utils.TestCase):
904900

905-
def _get_watcher(self):
906-
return unix_events.PidfdChildWatcher()
907-
901+
pass
908902

909903
else:
910904
# Windows

0 commit comments

Comments
 (0)