| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | import unittest | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import textwrap | 
					
						
							|  |  |  | import importlib | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | import socket | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  | from test.support import os_helper, SHORT_TIMEOUT, busy_retry | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | from test.support.script_helper import make_script | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | from test.support.socket_helper import find_unused_port | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PROCESS_VM_READV_SUPPORTED = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | try: | 
					
						
							|  |  |  |     from _testexternalinspection import PROCESS_VM_READV_SUPPORTED | 
					
						
							|  |  |  |     from _testexternalinspection import get_stack_trace | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     from _testexternalinspection import get_async_stack_trace | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |     from _testexternalinspection import get_all_awaited_by | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | except ImportError: | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     raise unittest.SkipTest( | 
					
						
							|  |  |  |         "Test only runs when _testexternalinspection is available") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def _make_test_script(script_dir, script_basename, source): | 
					
						
							|  |  |  |     to_return = make_script(script_dir, script_basename, source) | 
					
						
							|  |  |  |     importlib.invalidate_caches() | 
					
						
							|  |  |  |     return to_return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | skip_if_not_supported = unittest.skipIf((sys.platform != "darwin" | 
					
						
							|  |  |  |                                          and sys.platform != "linux" | 
					
						
							|  |  |  |                                          and sys.platform != "win32"), | 
					
						
							|  |  |  |                                         "Test only runs on Linux, Windows and MacOS") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  | class TestGetStackTrace(unittest.TestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |     def test_remote_stack_trace(self): | 
					
						
							|  |  |  |         # Spawn a process with some realistic Python code | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							|  |  |  |             import time, sys, socket | 
					
						
							|  |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |             def bar(): | 
					
						
							|  |  |  |                 for x in range(100): | 
					
						
							|  |  |  |                     if x == 50: | 
					
						
							|  |  |  |                         baz() | 
					
						
							|  |  |  |             def baz(): | 
					
						
							|  |  |  |                 foo() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def foo(): | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |                 time.sleep(1000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             bar() | 
					
						
							|  |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         with os_helper.temp_dir() as work_dir: | 
					
						
							|  |  |  |             script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |             os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Create a socket server to communicate with the target process | 
					
						
							|  |  |  |             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |             server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |             server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |             server_socket.listen(1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |             script_name = _make_test_script(script_dir, 'script', script) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             client_socket = None | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 p = subprocess.Popen([sys.executable, script_name]) | 
					
						
							|  |  |  |                 client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                 server_socket.close() | 
					
						
							|  |  |  |                 response = client_socket.recv(1024) | 
					
						
							|  |  |  |                 self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |                 stack_trace = get_stack_trace(p.pid) | 
					
						
							|  |  |  |             except PermissionError: | 
					
						
							|  |  |  |                 self.skipTest("Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 if client_socket is not None: | 
					
						
							|  |  |  |                     client_socket.close() | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |                 p.kill() | 
					
						
							|  |  |  |                 p.terminate() | 
					
						
							|  |  |  |                 p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             expected_stack_trace = [ | 
					
						
							|  |  |  |                 'foo', | 
					
						
							|  |  |  |                 'baz', | 
					
						
							|  |  |  |                 'bar', | 
					
						
							|  |  |  |                 '<module>' | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             self.assertEqual(stack_trace, expected_stack_trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							|  |  |  |     def test_async_remote_stack_trace(self): | 
					
						
							|  |  |  |         # Spawn a process with some realistic Python code | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             import asyncio | 
					
						
							|  |  |  |             import time | 
					
						
							|  |  |  |             import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             import socket | 
					
						
							|  |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             def c5(): | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 time.sleep(10000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c4(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							|  |  |  |                 c5() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c3(): | 
					
						
							|  |  |  |                 await c4() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c2(): | 
					
						
							|  |  |  |                 await c3() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c1(task): | 
					
						
							|  |  |  |                 await task | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def main(): | 
					
						
							|  |  |  |                 async with asyncio.TaskGroup() as tg: | 
					
						
							|  |  |  |                     task = tg.create_task(c2(), name="c2_root") | 
					
						
							|  |  |  |                     tg.create_task(c1(task), name="sub_main_1") | 
					
						
							|  |  |  |                     tg.create_task(c1(task), name="sub_main_2") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def new_eager_loop(): | 
					
						
							|  |  |  |                 loop = asyncio.new_event_loop() | 
					
						
							|  |  |  |                 eager_task_factory = asyncio.create_eager_task_factory( | 
					
						
							|  |  |  |                     asyncio.Task) | 
					
						
							|  |  |  |                 loop.set_task_factory(eager_task_factory) | 
					
						
							|  |  |  |                 return loop | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             asyncio.run(main(), loop_factory={{TASK_FACTORY}}) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         for task_factory_variant in "asyncio.new_event_loop", "new_eager_loop": | 
					
						
							|  |  |  |             with ( | 
					
						
							|  |  |  |                 self.subTest(task_factory_variant=task_factory_variant), | 
					
						
							|  |  |  |                 os_helper.temp_dir() as work_dir, | 
					
						
							|  |  |  |             ): | 
					
						
							|  |  |  |                 script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |                 os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |                 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |                 server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |                 server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |                 server_socket.listen(1) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 script_name = _make_test_script( | 
					
						
							|  |  |  |                     script_dir, 'script', | 
					
						
							|  |  |  |                     script.format(TASK_FACTORY=task_factory_variant)) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 client_socket = None | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 try: | 
					
						
							|  |  |  |                     p = subprocess.Popen( | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                         [sys.executable, script_name] | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                     client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                     server_socket.close() | 
					
						
							|  |  |  |                     response = client_socket.recv(1024) | 
					
						
							|  |  |  |                     self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                     stack_trace = get_async_stack_trace(p.pid) | 
					
						
							|  |  |  |                 except PermissionError: | 
					
						
							|  |  |  |                     self.skipTest( | 
					
						
							|  |  |  |                         "Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |                 finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                     if client_socket is not None: | 
					
						
							|  |  |  |                         client_socket.close() | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                     p.kill() | 
					
						
							|  |  |  |                     p.terminate() | 
					
						
							|  |  |  |                     p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # sets are unordered, so we want to sort "awaited_by"s | 
					
						
							|  |  |  |                 stack_trace[2].sort(key=lambda x: x[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 root_task = "Task-1" | 
					
						
							|  |  |  |                 expected_stack_trace = [ | 
					
						
							|  |  |  |                     ["c5", "c4", "c3", "c2"], | 
					
						
							|  |  |  |                     "c2_root", | 
					
						
							|  |  |  |                     [ | 
					
						
							|  |  |  |                         [["main"], root_task, []], | 
					
						
							|  |  |  |                         [["c1"], "sub_main_1", [[["main"], root_task, []]]], | 
					
						
							|  |  |  |                         [["c1"], "sub_main_2", [[["main"], root_task, []]]], | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                 ] | 
					
						
							|  |  |  |                 self.assertEqual(stack_trace, expected_stack_trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							|  |  |  |     def test_asyncgen_remote_stack_trace(self): | 
					
						
							|  |  |  |         # Spawn a process with some realistic Python code | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             import asyncio | 
					
						
							|  |  |  |             import time | 
					
						
							|  |  |  |             import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             import socket | 
					
						
							|  |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             async def gen_nested_call(): | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 time.sleep(10000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def gen(): | 
					
						
							|  |  |  |                 for num in range(2): | 
					
						
							|  |  |  |                     yield num | 
					
						
							|  |  |  |                     if num == 1: | 
					
						
							|  |  |  |                         await gen_nested_call() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def main(): | 
					
						
							|  |  |  |                 async for el in gen(): | 
					
						
							|  |  |  |                     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             asyncio.run(main()) | 
					
						
							|  |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         with os_helper.temp_dir() as work_dir: | 
					
						
							|  |  |  |             script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |             os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             # Create a socket server to communicate with the target process | 
					
						
							|  |  |  |             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |             server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |             server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |             server_socket.listen(1) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             script_name = _make_test_script(script_dir, 'script', script) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             client_socket = None | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 p = subprocess.Popen([sys.executable, script_name]) | 
					
						
							|  |  |  |                 client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                 server_socket.close() | 
					
						
							|  |  |  |                 response = client_socket.recv(1024) | 
					
						
							|  |  |  |                 self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 stack_trace = get_async_stack_trace(p.pid) | 
					
						
							|  |  |  |             except PermissionError: | 
					
						
							|  |  |  |                 self.skipTest("Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 if client_socket is not None: | 
					
						
							|  |  |  |                     client_socket.close() | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 p.kill() | 
					
						
							|  |  |  |                 p.terminate() | 
					
						
							|  |  |  |                 p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # sets are unordered, so we want to sort "awaited_by"s | 
					
						
							|  |  |  |             stack_trace[2].sort(key=lambda x: x[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             expected_stack_trace = [ | 
					
						
							|  |  |  |                 ['gen_nested_call', 'gen', 'main'], 'Task-1', [] | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             self.assertEqual(stack_trace, expected_stack_trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							|  |  |  |     def test_async_gather_remote_stack_trace(self): | 
					
						
							|  |  |  |         # Spawn a process with some realistic Python code | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             import asyncio | 
					
						
							|  |  |  |             import time | 
					
						
							|  |  |  |             import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             import socket | 
					
						
							|  |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |             async def deep(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 time.sleep(10000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c1(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							|  |  |  |                 await deep() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c2(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def main(): | 
					
						
							|  |  |  |                 await asyncio.gather(c1(), c2()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             asyncio.run(main()) | 
					
						
							|  |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         with os_helper.temp_dir() as work_dir: | 
					
						
							|  |  |  |             script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |             os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             # Create a socket server to communicate with the target process | 
					
						
							|  |  |  |             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |             server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |             server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |             server_socket.listen(1) | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             script_name = _make_test_script(script_dir, 'script', script) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             client_socket = None | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 p = subprocess.Popen([sys.executable, script_name]) | 
					
						
							|  |  |  |                 client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                 server_socket.close() | 
					
						
							|  |  |  |                 response = client_socket.recv(1024) | 
					
						
							|  |  |  |                 self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 stack_trace = get_async_stack_trace(p.pid) | 
					
						
							|  |  |  |             except PermissionError: | 
					
						
							|  |  |  |                 self.skipTest( | 
					
						
							|  |  |  |                     "Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 if client_socket is not None: | 
					
						
							|  |  |  |                     client_socket.close() | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |                 p.kill() | 
					
						
							|  |  |  |                 p.terminate() | 
					
						
							|  |  |  |                 p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # sets are unordered, so we want to sort "awaited_by"s | 
					
						
							|  |  |  |             stack_trace[2].sort(key=lambda x: x[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             expected_stack_trace =  [ | 
					
						
							|  |  |  |                 ['deep', 'c1'], 'Task-2', [[['main'], 'Task-1', []]] | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             self.assertEqual(stack_trace, expected_stack_trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							|  |  |  |     def test_async_staggered_race_remote_stack_trace(self): | 
					
						
							|  |  |  |         # Spawn a process with some realistic Python code | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |             import asyncio.staggered | 
					
						
							|  |  |  |             import time | 
					
						
							|  |  |  |             import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             import socket | 
					
						
							|  |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             async def deep(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |                 time.sleep(10000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c1(): | 
					
						
							|  |  |  |                 await asyncio.sleep(0) | 
					
						
							|  |  |  |                 await deep() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def c2(): | 
					
						
							|  |  |  |                 await asyncio.sleep(10000) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def main(): | 
					
						
							|  |  |  |                 await asyncio.staggered.staggered_race( | 
					
						
							|  |  |  |                     [c1, c2], | 
					
						
							|  |  |  |                     delay=None, | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             asyncio.run(main()) | 
					
						
							|  |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         with os_helper.temp_dir() as work_dir: | 
					
						
							|  |  |  |             script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |             os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             # Create a socket server to communicate with the target process | 
					
						
							|  |  |  |             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |             server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |             server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |             server_socket.listen(1) | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |             script_name = _make_test_script(script_dir, 'script', script) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             client_socket = None | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 p = subprocess.Popen([sys.executable, script_name]) | 
					
						
							|  |  |  |                 client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                 server_socket.close() | 
					
						
							|  |  |  |                 response = client_socket.recv(1024) | 
					
						
							|  |  |  |                 self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |                 stack_trace = get_async_stack_trace(p.pid) | 
					
						
							|  |  |  |             except PermissionError: | 
					
						
							|  |  |  |                 self.skipTest( | 
					
						
							|  |  |  |                     "Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 if client_socket is not None: | 
					
						
							|  |  |  |                     client_socket.close() | 
					
						
							| 
									
										
										
										
											2025-01-26 15:44:16 +00:00
										 |  |  |                 p.kill() | 
					
						
							|  |  |  |                 p.terminate() | 
					
						
							|  |  |  |                 p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # sets are unordered, so we want to sort "awaited_by"s | 
					
						
							|  |  |  |             stack_trace[2].sort(key=lambda x: x[1]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             expected_stack_trace =  [ | 
					
						
							|  |  |  |                 ['deep', 'c1', 'run_one_coro'], 'Task-2', [[['main'], 'Task-1', []]] | 
					
						
							|  |  |  |             ] | 
					
						
							|  |  |  |             self.assertEqual(stack_trace, expected_stack_trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							|  |  |  |     def test_async_global_awaited_by(self): | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         port = find_unused_port() | 
					
						
							|  |  |  |         script = textwrap.dedent(f"""\
 | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             import asyncio | 
					
						
							|  |  |  |             import os | 
					
						
							|  |  |  |             import random | 
					
						
							|  |  |  |             import sys | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             import socket | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             from string import ascii_lowercase, digits | 
					
						
							|  |  |  |             from test.support import socket_helper, SHORT_TIMEOUT | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             HOST = '127.0.0.1' | 
					
						
							|  |  |  |             PORT = socket_helper.find_unused_port() | 
					
						
							|  |  |  |             connections = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             # Connect to the test process | 
					
						
							|  |  |  |             sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             sock.connect(('localhost', {port})) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             class EchoServerProtocol(asyncio.Protocol): | 
					
						
							|  |  |  |                 def connection_made(self, transport): | 
					
						
							|  |  |  |                     global connections | 
					
						
							|  |  |  |                     connections += 1 | 
					
						
							|  |  |  |                     self.transport = transport | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 def data_received(self, data): | 
					
						
							|  |  |  |                     self.transport.write(data) | 
					
						
							|  |  |  |                     self.transport.close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def echo_client(message): | 
					
						
							|  |  |  |                 reader, writer = await asyncio.open_connection(HOST, PORT) | 
					
						
							|  |  |  |                 writer.write(message.encode()) | 
					
						
							|  |  |  |                 await writer.drain() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 data = await reader.read(100) | 
					
						
							|  |  |  |                 assert message == data.decode() | 
					
						
							|  |  |  |                 writer.close() | 
					
						
							|  |  |  |                 await writer.wait_closed() | 
					
						
							|  |  |  |                 await asyncio.sleep(SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def echo_client_spam(server): | 
					
						
							|  |  |  |                 async with asyncio.TaskGroup() as tg: | 
					
						
							|  |  |  |                     while connections < 1000: | 
					
						
							|  |  |  |                         msg = list(ascii_lowercase + digits) | 
					
						
							|  |  |  |                         random.shuffle(msg) | 
					
						
							|  |  |  |                         tg.create_task(echo_client("".join(msg))) | 
					
						
							|  |  |  |                         await asyncio.sleep(0) | 
					
						
							|  |  |  |                     # at least a 1000 tasks created | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                     sock.sendall(b"ready") | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |                 # at this point all client tasks completed without assertion errors | 
					
						
							|  |  |  |                 # let's wrap up the test | 
					
						
							|  |  |  |                 server.close() | 
					
						
							|  |  |  |                 await server.wait_closed() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def main(): | 
					
						
							|  |  |  |                 loop = asyncio.get_running_loop() | 
					
						
							|  |  |  |                 server = await loop.create_server(EchoServerProtocol, HOST, PORT) | 
					
						
							|  |  |  |                 async with server: | 
					
						
							|  |  |  |                     async with asyncio.TaskGroup() as tg: | 
					
						
							|  |  |  |                         tg.create_task(server.serve_forever(), name="server task") | 
					
						
							|  |  |  |                         tg.create_task(echo_client_spam(server), name="echo client spam") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             asyncio.run(main()) | 
					
						
							|  |  |  |             """)
 | 
					
						
							|  |  |  |         stack_trace = None | 
					
						
							|  |  |  |         with os_helper.temp_dir() as work_dir: | 
					
						
							|  |  |  |             script_dir = os.path.join(work_dir, "script_pkg") | 
					
						
							|  |  |  |             os.mkdir(script_dir) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             # Create a socket server to communicate with the target process | 
					
						
							|  |  |  |             server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 
					
						
							|  |  |  |             server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | 
					
						
							|  |  |  |             server_socket.bind(('localhost', port)) | 
					
						
							|  |  |  |             server_socket.settimeout(SHORT_TIMEOUT) | 
					
						
							|  |  |  |             server_socket.listen(1) | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             script_name = _make_test_script(script_dir, 'script', script) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |             client_socket = None | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             try: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 p = subprocess.Popen([sys.executable, script_name]) | 
					
						
							|  |  |  |                 client_socket, _ = server_socket.accept() | 
					
						
							|  |  |  |                 server_socket.close() | 
					
						
							|  |  |  |                 response = client_socket.recv(1024) | 
					
						
							|  |  |  |                 self.assertEqual(response, b"ready") | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |                 for _ in busy_retry(SHORT_TIMEOUT): | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         all_awaited_by = get_all_awaited_by(p.pid) | 
					
						
							|  |  |  |                     except RuntimeError as re: | 
					
						
							|  |  |  |                         # This call reads a linked list in another process with | 
					
						
							|  |  |  |                         # no synchronization. That occasionally leads to invalid | 
					
						
							|  |  |  |                         # reads. Here we avoid making the test flaky. | 
					
						
							|  |  |  |                         msg = str(re) | 
					
						
							|  |  |  |                         if msg.startswith("Task list appears corrupted"): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         elif msg.startswith("Invalid linked list structure reading remote memory"): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         elif msg.startswith("Unknown error reading memory"): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         elif msg.startswith("Unhandled frame owner"): | 
					
						
							|  |  |  |                             continue | 
					
						
							|  |  |  |                         raise  # Unrecognized exception, safest not to ignore it | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  |                 # expected: a list of two elements: 1 thread, 1 interp | 
					
						
							|  |  |  |                 self.assertEqual(len(all_awaited_by), 2) | 
					
						
							|  |  |  |                 # expected: a tuple with the thread ID and the awaited_by list | 
					
						
							|  |  |  |                 self.assertEqual(len(all_awaited_by[0]), 2) | 
					
						
							|  |  |  |                 # expected: no tasks in the fallback per-interp task list | 
					
						
							|  |  |  |                 self.assertEqual(all_awaited_by[1], (0, [])) | 
					
						
							|  |  |  |                 entries = all_awaited_by[0][1] | 
					
						
							|  |  |  |                 # expected: at least 1000 pending tasks | 
					
						
							|  |  |  |                 self.assertGreaterEqual(len(entries), 1000) | 
					
						
							|  |  |  |                 # the first three tasks stem from the code structure | 
					
						
							|  |  |  |                 self.assertIn(('Task-1', []), entries) | 
					
						
							|  |  |  |                 self.assertIn(('server task', [[['main'], 'Task-1', []]]), entries) | 
					
						
							|  |  |  |                 self.assertIn(('echo client spam', [[['main'], 'Task-1', []]]), entries) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 expected_stack = [[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]] | 
					
						
							|  |  |  |                 tasks_with_stack = [task for task in entries if task[1] == expected_stack] | 
					
						
							|  |  |  |                 self.assertGreaterEqual(len(tasks_with_stack), 1000) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |                 # the final task will have some random number, but it should for | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 # sure be one of the echo client spam horde (In windows this is not true | 
					
						
							|  |  |  |                 # for some reason) | 
					
						
							|  |  |  |                 if sys.platform != "win32": | 
					
						
							|  |  |  |                     self.assertEqual([[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]], entries[-1][1]) | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |             except PermissionError: | 
					
						
							|  |  |  |                 self.skipTest( | 
					
						
							|  |  |  |                     "Insufficient permissions to read the stack trace") | 
					
						
							|  |  |  |             finally: | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |                 if client_socket is not None: | 
					
						
							|  |  |  |                     client_socket.close() | 
					
						
							| 
									
										
										
										
											2025-04-23 19:22:29 +02:00
										 |  |  |                 p.kill() | 
					
						
							|  |  |  |                 p.terminate() | 
					
						
							|  |  |  |                 p.wait(timeout=SHORT_TIMEOUT) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |     @skip_if_not_supported | 
					
						
							| 
									
										
										
										
											2025-01-22 08:25:29 -08:00
										 |  |  |     @unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, | 
					
						
							|  |  |  |                      "Test only runs on Linux with process_vm_readv support") | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |     def test_self_trace(self): | 
					
						
							|  |  |  |         stack_trace = get_stack_trace(os.getpid()) | 
					
						
							| 
									
										
										
										
											2025-04-25 14:12:16 +01:00
										 |  |  |         print(stack_trace) | 
					
						
							| 
									
										
										
										
											2024-02-28 10:17:34 +00:00
										 |  |  |         self.assertEqual(stack_trace[0], "test_self_trace") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     unittest.main() |