diff options
author | Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> | 2022-11-24 09:10:27 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-24 09:10:27 +0530 |
commit | 0c1fbc17b48f56c6070d335ed291a24c91ed190a (patch) | |
tree | fd255e556f7245eaa7878483f2747681cbaabb83 /Lib/test/test_asyncio/test_unix_events.py | |
parent | 9dc08361bef67a331d1609c8629314c0ca5a79d5 (diff) | |
download | cpython-0c1fbc17b48f56c6070d335ed291a24c91ed190a.tar.gz cpython-0c1fbc17b48f56c6070d335ed291a24c91ed190a.zip |
GH-66285: fix forking in `asyncio` (#99539)
`asyncio` now does not shares event loop and signal wakeupfd in forked processes.
Diffstat (limited to 'Lib/test/test_asyncio/test_unix_events.py')
-rw-r--r-- | Lib/test/test_asyncio/test_unix_events.py | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py index 93e8611f184..4e1dab2f86b 100644 --- a/Lib/test/test_asyncio/test_unix_events.py +++ b/Lib/test/test_asyncio/test_unix_events.py @@ -11,10 +11,13 @@ import stat import sys import threading import unittest +import time from unittest import mock import warnings +import multiprocessing from test.support import os_helper from test.support import socket_helper +from test.support import wait_process if sys.platform == 'win32': raise unittest.SkipTest('UNIX only') @@ -1867,5 +1870,97 @@ class TestFunctional(unittest.TestCase): wsock.close() +@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()') +class TestFork(unittest.IsolatedAsyncioTestCase): + + async def test_fork_not_share_event_loop(self): + # The forked process should not share the event loop with the parent + loop = asyncio.get_running_loop() + r, w = os.pipe() + self.addCleanup(os.close, r) + self.addCleanup(os.close, w) + pid = os.fork() + if pid == 0: + # child + try: + loop = asyncio.get_event_loop_policy().get_event_loop() + os.write(w, str(id(loop)).encode()) + finally: + os._exit(0) + else: + # parent + child_loop = int(os.read(r, 100).decode()) + self.assertNotEqual(child_loop, id(loop)) + wait_process(pid, exitcode=0) + + def test_fork_signal_handling(self): + # Sending signal to the forked process should not affect the parent + # process + ctx = multiprocessing.get_context('fork') + manager = ctx.Manager() + self.addCleanup(manager.shutdown) + child_started = manager.Event() + child_handled = manager.Event() + parent_handled = manager.Event() + + def child_main(): + signal.signal(signal.SIGTERM, lambda *args: child_handled.set()) + child_started.set() + time.sleep(1) + + async def main(): + loop = asyncio.get_running_loop() + loop.add_signal_handler(signal.SIGTERM, lambda *args: parent_handled.set()) + + process = ctx.Process(target=child_main) + process.start() + child_started.wait() + os.kill(process.pid, signal.SIGTERM) + process.join() + + async def func(): + await asyncio.sleep(0.1) + return 42 + + # Test parent's loop is still functional + self.assertEqual(await asyncio.create_task(func()), 42) + + asyncio.run(main()) + + self.assertFalse(parent_handled.is_set()) + self.assertTrue(child_handled.is_set()) + + def test_fork_asyncio_run(self): + ctx = multiprocessing.get_context('fork') + manager = ctx.Manager() + self.addCleanup(manager.shutdown) + result = manager.Value('i', 0) + + async def child_main(): + await asyncio.sleep(0.1) + result.value = 42 + + process = ctx.Process(target=lambda: asyncio.run(child_main())) + process.start() + process.join() + + self.assertEqual(result.value, 42) + + def test_fork_asyncio_subprocess(self): + ctx = multiprocessing.get_context('fork') + manager = ctx.Manager() + self.addCleanup(manager.shutdown) + result = manager.Value('i', 1) + + async def child_main(): + proc = await asyncio.create_subprocess_exec(sys.executable, '-c', 'pass') + result.value = await proc.wait() + + process = ctx.Process(target=lambda: asyncio.run(child_main())) + process.start() + process.join() + + self.assertEqual(result.value, 0) + if __name__ == '__main__': unittest.main() |