-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-94182: run the PidfdChildWatcher on the running loop #94184
Changes from 20 commits
e49365f
49cfa81
3a3d5e2
60dafa1
16f1c4e
c8ae89d
8229efc
6473022
02190f2
9607d8e
dad94d9
6e56155
1037078
4e69faf
740e0c2
7721ea1
f4f6fc5
cad5231
5647edb
102ba29
de21df0
d30824f
978c822
fbeb1a7
6fe4985
592fcb9
d27f174
871507f
5dfc542
a4fac11
fc2e400
65a2db6
d25f852
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -700,35 +700,78 @@ class SubprocessPidfdWatcherTests(SubprocessWatcherMixin, | |
test_utils.TestCase): | ||
Watcher = unix_events.PidfdChildWatcher | ||
|
||
else: | ||
# Windows | ||
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase): | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.loop = asyncio.ProactorEventLoop() | ||
self.set_event_loop(self.loop) | ||
class GenericWatcherTests(test_utils.TestCase): | ||
|
||
def test_create_subprocess_fails_with_inactive_watcher(self): | ||
watcher = mock.create_autospec( | ||
asyncio.AbstractChildWatcher, | ||
**{"__enter__.return_value.is_active.return_value": False} | ||
) | ||
|
||
class GenericWatcherTests: | ||
async def execute(): | ||
asyncio.set_child_watcher(watcher) | ||
|
||
def test_create_subprocess_fails_with_inactive_watcher(self): | ||
with self.assertRaises(RuntimeError): | ||
await subprocess.create_subprocess_exec( | ||
os_helper.FakePath(sys.executable), '-c', 'pass') | ||
|
||
async def execute(): | ||
watcher = mock.create_authspec(asyncio.AbstractChildWatcher) | ||
watcher.is_active.return_value = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this was patching the wrong attribute - asyncio calls |
||
asyncio.set_child_watcher(watcher) | ||
watcher.add_child_handler.assert_not_called() | ||
|
||
with self.assertRaises(RuntimeError): | ||
await subprocess.create_subprocess_exec( | ||
os_helper.FakePath(sys.executable), '-c', 'pass') | ||
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah well about that... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is extremely confusing asyncio behavior that I strongly disagree with https://github.com/python/cpython/pull/94593/files#r914528655 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. passing the loop factory disables the calls to set the |
||
self.assertIsNone(runner.run(execute())) | ||
self.assertListEqual(watcher.mock_calls, [ | ||
mock.call.__enter__(), | ||
mock.call.__enter__().is_active(), | ||
mock.call.__exit__(RuntimeError, mock.ANY, mock.ANY), | ||
]) | ||
|
||
watcher.add_child_handler.assert_not_called() | ||
|
||
self.assertIsNone(self.loop.run_until_complete(execute())) | ||
def has_pidfd_support(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this technically works, I would much rather see this made into a module level function. |
||
if not hasattr(os, 'pidfd_open'): | ||
return False | ||
try: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe separate different logical blocks with blank lines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm copying the existing code here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I move it to a module level flag I'll apply this fix |
||
os.close(os.pidfd_open(os.getpid())) | ||
except OSError: | ||
return False | ||
return True | ||
|
||
@unittest.skipUnless( | ||
has_pidfd_support(), | ||
"operating system does not support pidfds", | ||
) | ||
def test_create_subprocess_with_pidfd(self): | ||
async def in_thread(): | ||
proc = await asyncio.create_subprocess_exec( | ||
*PROGRAM_CAT, | ||
stdin=subprocess.PIPE, | ||
stdout=subprocess.PIPE, | ||
) | ||
stdout, stderr = await proc.communicate(b"some data") | ||
return proc.returncode, stdout | ||
|
||
async def main(): | ||
# asyncio.Runner did not call asyncio.set_event_loop() | ||
with self.assertRaises(RuntimeError): | ||
asyncio.get_event_loop_policy().get_event_loop() | ||
return await asyncio.to_thread(asyncio.run, in_thread()) | ||
|
||
asyncio.set_child_watcher(asyncio.PidfdChildWatcher()) | ||
try: | ||
with asyncio.Runner(loop_factory=asyncio.new_event_loop) as runner: | ||
returncode, stdout = runner.run(main()) | ||
self.assertEqual(returncode, 0) | ||
self.assertEqual(stdout, b'some data') | ||
finally: | ||
asyncio.set_child_watcher(None) | ||
else: | ||
# Windows | ||
class SubprocessProactorTests(SubprocessMixin, test_utils.TestCase): | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.loop = asyncio.ProactorEventLoop() | ||
self.set_event_loop(self.loop) | ||
|
||
if __name__ == '__main__': | ||
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unittest.main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
run the :class:`asyncio.PidfdChildWatcher` on the running loop | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe elaborate a little bit more? This is not very informative.
graingert marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why we can't pass the loop as an argument as well? (Though maybe that creates an unhealthy self-reference to the loop.)