mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
bpo-35621: Support running subprocesses in asyncio when loop is executed in non-main thread (GH-14344)
This commit is contained in:
parent
5cbbbd73a6
commit
0d671c04c3
7 changed files with 378 additions and 72 deletions
|
@ -633,6 +633,7 @@ class SubprocessMixin:
|
|||
|
||||
self.assertIsNone(self.loop.run_until_complete(execute()))
|
||||
|
||||
|
||||
if sys.platform != 'win32':
|
||||
# Unix
|
||||
class SubprocessWatcherMixin(SubprocessMixin):
|
||||
|
@ -648,7 +649,24 @@ if sys.platform != 'win32':
|
|||
watcher = self.Watcher()
|
||||
watcher.attach_loop(self.loop)
|
||||
policy.set_child_watcher(watcher)
|
||||
self.addCleanup(policy.set_child_watcher, None)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
watcher = policy.get_child_watcher()
|
||||
policy.set_child_watcher(None)
|
||||
watcher.attach_loop(None)
|
||||
watcher.close()
|
||||
|
||||
class SubprocessThreadedWatcherTests(SubprocessWatcherMixin,
|
||||
test_utils.TestCase):
|
||||
|
||||
Watcher = unix_events.ThreadedChildWatcher
|
||||
|
||||
class SubprocessMultiLoopWatcherTests(SubprocessWatcherMixin,
|
||||
test_utils.TestCase):
|
||||
|
||||
Watcher = unix_events.MultiLoopChildWatcher
|
||||
|
||||
class SubprocessSafeWatcherTests(SubprocessWatcherMixin,
|
||||
test_utils.TestCase):
|
||||
|
@ -670,5 +688,25 @@ else:
|
|||
self.set_event_loop(self.loop)
|
||||
|
||||
|
||||
class GenericWatcherTests:
|
||||
|
||||
def test_create_subprocess_fails_with_inactive_watcher(self):
|
||||
|
||||
async def execute():
|
||||
watcher = mock.create_authspec(asyncio.AbstractChildWatcher)
|
||||
watcher.is_active.return_value = False
|
||||
asyncio.set_child_watcher(watcher)
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
await subprocess.create_subprocess_exec(
|
||||
support.FakePath(sys.executable), '-c', 'pass')
|
||||
|
||||
watcher.add_child_handler.assert_not_called()
|
||||
|
||||
self.assertIsNone(self.loop.run_until_complete(execute()))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1082,6 +1082,8 @@ class AbstractChildWatcherTests(unittest.TestCase):
|
|||
NotImplementedError, watcher.attach_loop, f)
|
||||
self.assertRaises(
|
||||
NotImplementedError, watcher.close)
|
||||
self.assertRaises(
|
||||
NotImplementedError, watcher.is_active)
|
||||
self.assertRaises(
|
||||
NotImplementedError, watcher.__enter__)
|
||||
self.assertRaises(
|
||||
|
@ -1784,15 +1786,6 @@ class ChildWatcherTestsMixin:
|
|||
if isinstance(self.watcher, asyncio.FastChildWatcher):
|
||||
self.assertFalse(self.watcher._zombies)
|
||||
|
||||
@waitpid_mocks
|
||||
def test_add_child_handler_with_no_loop_attached(self, m):
|
||||
callback = mock.Mock()
|
||||
with self.create_watcher() as watcher:
|
||||
with self.assertRaisesRegex(
|
||||
RuntimeError,
|
||||
'the child watcher does not have a loop attached'):
|
||||
watcher.add_child_handler(100, callback)
|
||||
|
||||
|
||||
class SafeChildWatcherTests (ChildWatcherTestsMixin, test_utils.TestCase):
|
||||
def create_watcher(self):
|
||||
|
@ -1809,17 +1802,16 @@ class PolicyTests(unittest.TestCase):
|
|||
def create_policy(self):
|
||||
return asyncio.DefaultEventLoopPolicy()
|
||||
|
||||
def test_get_child_watcher(self):
|
||||
def test_get_default_child_watcher(self):
|
||||
policy = self.create_policy()
|
||||
self.assertIsNone(policy._watcher)
|
||||
|
||||
watcher = policy.get_child_watcher()
|
||||
self.assertIsInstance(watcher, asyncio.SafeChildWatcher)
|
||||
self.assertIsInstance(watcher, asyncio.ThreadedChildWatcher)
|
||||
|
||||
self.assertIs(policy._watcher, watcher)
|
||||
|
||||
self.assertIs(watcher, policy.get_child_watcher())
|
||||
self.assertIsNone(watcher._loop)
|
||||
|
||||
def test_get_child_watcher_after_set(self):
|
||||
policy = self.create_policy()
|
||||
|
@ -1829,18 +1821,6 @@ class PolicyTests(unittest.TestCase):
|
|||
self.assertIs(policy._watcher, watcher)
|
||||
self.assertIs(watcher, policy.get_child_watcher())
|
||||
|
||||
def test_get_child_watcher_with_mainloop_existing(self):
|
||||
policy = self.create_policy()
|
||||
loop = policy.get_event_loop()
|
||||
|
||||
self.assertIsNone(policy._watcher)
|
||||
watcher = policy.get_child_watcher()
|
||||
|
||||
self.assertIsInstance(watcher, asyncio.SafeChildWatcher)
|
||||
self.assertIs(watcher._loop, loop)
|
||||
|
||||
loop.close()
|
||||
|
||||
def test_get_child_watcher_thread(self):
|
||||
|
||||
def f():
|
||||
|
@ -1866,7 +1846,11 @@ class PolicyTests(unittest.TestCase):
|
|||
policy = self.create_policy()
|
||||
loop = policy.get_event_loop()
|
||||
|
||||
watcher = policy.get_child_watcher()
|
||||
# Explicitly setup SafeChildWatcher,
|
||||
# default ThreadedChildWatcher has no _loop property
|
||||
watcher = asyncio.SafeChildWatcher()
|
||||
policy.set_child_watcher(watcher)
|
||||
watcher.attach_loop(loop)
|
||||
|
||||
self.assertIs(watcher._loop, loop)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Utilities shared by tests."""
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import contextlib
|
||||
import io
|
||||
|
@ -512,6 +513,18 @@ class TestCase(unittest.TestCase):
|
|||
if executor is not None:
|
||||
executor.shutdown(wait=True)
|
||||
loop.close()
|
||||
policy = support.maybe_get_event_loop_policy()
|
||||
if policy is not None:
|
||||
try:
|
||||
watcher = policy.get_child_watcher()
|
||||
except NotImplementedError:
|
||||
# watcher is not implemented by EventLoopPolicy, e.g. Windows
|
||||
pass
|
||||
else:
|
||||
if isinstance(watcher, asyncio.ThreadedChildWatcher):
|
||||
threads = list(watcher._threads.values())
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
def set_event_loop(self, loop, *, cleanup=True):
|
||||
assert loop is not None
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue