|  | 
| 3 | 3 | import contextlib | 
| 4 | 4 | import errno | 
| 5 | 5 | import io | 
|  | 6 | +import multiprocessing | 
| 6 | 7 | import os | 
| 7 | 8 | import pathlib | 
| 8 | 9 | import signal | 
|  | 
| 15 | 16 | import warnings | 
| 16 | 17 | from test.support import os_helper | 
| 17 | 18 | from test.support import socket_helper | 
|  | 19 | +from test.support import wait_process | 
|  | 20 | +from test.support import hashlib_helper | 
| 18 | 21 | 
 | 
| 19 | 22 | if sys.platform == 'win32': | 
| 20 | 23 |     raise unittest.SkipTest('UNIX only') | 
| @@ -1867,5 +1870,100 @@ async def runner(): | 
| 1867 | 1870 |             wsock.close() | 
| 1868 | 1871 | 
 | 
| 1869 | 1872 | 
 | 
|  | 1873 | +@unittest.skipUnless(hasattr(os, 'fork'), 'requires os.fork()') | 
|  | 1874 | +class TestFork(unittest.IsolatedAsyncioTestCase): | 
|  | 1875 | + | 
|  | 1876 | +    async def test_fork_not_share_event_loop(self): | 
|  | 1877 | +        # The forked process should not share the event loop with the parent | 
|  | 1878 | +        loop = asyncio.get_running_loop() | 
|  | 1879 | +        r, w = os.pipe() | 
|  | 1880 | +        self.addCleanup(os.close, r) | 
|  | 1881 | +        self.addCleanup(os.close, w) | 
|  | 1882 | +        pid = os.fork() | 
|  | 1883 | +        if pid == 0: | 
|  | 1884 | +            # child | 
|  | 1885 | +            try: | 
|  | 1886 | +                loop = asyncio.get_event_loop_policy().get_event_loop() | 
|  | 1887 | +                os.write(w, str(id(loop)).encode()) | 
|  | 1888 | +            finally: | 
|  | 1889 | +                os._exit(0) | 
|  | 1890 | +        else: | 
|  | 1891 | +            # parent | 
|  | 1892 | +            child_loop = int(os.read(r, 100).decode()) | 
|  | 1893 | +            self.assertNotEqual(child_loop, id(loop)) | 
|  | 1894 | +            wait_process(pid, exitcode=0) | 
|  | 1895 | + | 
|  | 1896 | +    @hashlib_helper.requires_hashdigest('md5') | 
|  | 1897 | +    def test_fork_signal_handling(self): | 
|  | 1898 | +        # Sending signal to the forked process should not affect the parent | 
|  | 1899 | +        # process | 
|  | 1900 | +        ctx = multiprocessing.get_context('fork') | 
|  | 1901 | +        manager = ctx.Manager() | 
|  | 1902 | +        self.addCleanup(manager.shutdown) | 
|  | 1903 | +        child_started = manager.Event() | 
|  | 1904 | +        child_handled = manager.Event() | 
|  | 1905 | +        parent_handled = manager.Event() | 
|  | 1906 | + | 
|  | 1907 | +        def child_main(): | 
|  | 1908 | +            signal.signal(signal.SIGTERM, lambda *args: child_handled.set()) | 
|  | 1909 | +            child_started.set() | 
|  | 1910 | +            time.sleep(1) | 
|  | 1911 | + | 
|  | 1912 | +        async def main(): | 
|  | 1913 | +            loop = asyncio.get_running_loop() | 
|  | 1914 | +            loop.add_signal_handler(signal.SIGTERM, lambda *args: parent_handled.set()) | 
|  | 1915 | + | 
|  | 1916 | +            process = ctx.Process(target=child_main) | 
|  | 1917 | +            process.start() | 
|  | 1918 | +            child_started.wait() | 
|  | 1919 | +            os.kill(process.pid, signal.SIGTERM) | 
|  | 1920 | +            process.join() | 
|  | 1921 | + | 
|  | 1922 | +            async def func(): | 
|  | 1923 | +                await asyncio.sleep(0.1) | 
|  | 1924 | +                return 42 | 
|  | 1925 | + | 
|  | 1926 | +            # Test parent's loop is still functional | 
|  | 1927 | +            self.assertEqual(await asyncio.create_task(func()), 42) | 
|  | 1928 | + | 
|  | 1929 | +        asyncio.run(main()) | 
|  | 1930 | + | 
|  | 1931 | +        self.assertFalse(parent_handled.is_set()) | 
|  | 1932 | +        self.assertTrue(child_handled.is_set()) | 
|  | 1933 | + | 
|  | 1934 | +    @hashlib_helper.requires_hashdigest('md5') | 
|  | 1935 | +    def test_fork_asyncio_run(self): | 
|  | 1936 | +        ctx = multiprocessing.get_context('fork') | 
|  | 1937 | +        manager = ctx.Manager() | 
|  | 1938 | +        self.addCleanup(manager.shutdown) | 
|  | 1939 | +        result = manager.Value('i', 0) | 
|  | 1940 | + | 
|  | 1941 | +        async def child_main(): | 
|  | 1942 | +            await asyncio.sleep(0.1) | 
|  | 1943 | +            result.value = 42 | 
|  | 1944 | + | 
|  | 1945 | +        process = ctx.Process(target=lambda: asyncio.run(child_main())) | 
|  | 1946 | +        process.start() | 
|  | 1947 | +        process.join() | 
|  | 1948 | + | 
|  | 1949 | +        self.assertEqual(result.value, 42) | 
|  | 1950 | + | 
|  | 1951 | +    @hashlib_helper.requires_hashdigest('md5') | 
|  | 1952 | +    def test_fork_asyncio_subprocess(self): | 
|  | 1953 | +        ctx = multiprocessing.get_context('fork') | 
|  | 1954 | +        manager = ctx.Manager() | 
|  | 1955 | +        self.addCleanup(manager.shutdown) | 
|  | 1956 | +        result = manager.Value('i', 1) | 
|  | 1957 | + | 
|  | 1958 | +        async def child_main(): | 
|  | 1959 | +            proc = await asyncio.create_subprocess_exec(sys.executable, '-c', 'pass') | 
|  | 1960 | +            result.value = await proc.wait() | 
|  | 1961 | + | 
|  | 1962 | +        process = ctx.Process(target=lambda: asyncio.run(child_main())) | 
|  | 1963 | +        process.start() | 
|  | 1964 | +        process.join() | 
|  | 1965 | + | 
|  | 1966 | +        self.assertEqual(result.value, 0) | 
|  | 1967 | + | 
| 1870 | 1968 | if __name__ == '__main__': | 
| 1871 | 1969 |     unittest.main() | 
0 commit comments