@@ -340,40 +340,6 @@ def _run_script_and_get_trace(
340340 finally :
341341 _cleanup_sockets (client_socket , server_socket )
342342
343- @contextmanager
344- def _target_process (self , script_body ):
345- """Context manager for running a target process with socket sync."""
346- port = find_unused_port ()
347- script = f"""\
348- import socket
349- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
350- sock.connect(('localhost', { port } ))
351- { textwrap .dedent (script_body )}
352- """
353-
354- with os_helper .temp_dir () as work_dir :
355- script_dir = os .path .join (work_dir , "script_pkg" )
356- os .mkdir (script_dir )
357-
358- server_socket = _create_server_socket (port )
359- script_name = _make_test_script (script_dir , "script" , script )
360- client_socket = None
361-
362- try :
363- with _managed_subprocess ([sys .executable , script_name ]) as p :
364- client_socket , _ = server_socket .accept ()
365- server_socket .close ()
366- server_socket = None
367-
368- def make_unwinder (cache_frames = True ):
369- return RemoteUnwinder (
370- p .pid , all_threads = True , cache_frames = cache_frames
371- )
372-
373- yield p , client_socket , make_unwinder
374- finally :
375- _cleanup_sockets (client_socket , server_socket )
376-
377343 def _find_frame_in_trace (self , stack_trace , predicate ):
378344 """
379345 Find a frame matching predicate in stack trace.
@@ -1539,71 +1505,6 @@ def matches_awaited_by_pattern(task):
15391505 finally :
15401506 _cleanup_sockets (client_socket , server_socket )
15411507
1542- @skip_if_not_supported
1543- @unittest .skipIf (
1544- sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
1545- "Test only runs on Linux with process_vm_readv support" ,
1546- )
1547- def test_async_awaited_by_skips_set_tombstones (self ):
1548- script_body = """\
1549- import asyncio
1550-
1551- class RemovedTask(asyncio.Task):
1552- def __hash__(self):
1553- return 0
1554-
1555- class RemainingTask(asyncio.Task):
1556- def __hash__(self):
1557- return 1
1558-
1559- async def main():
1560- victim = asyncio.current_task()
1561- victim.set_name("victim")
1562- removed = RemovedTask(
1563- asyncio.sleep(10_000), name="removed"
1564- )
1565- remaining = RemainingTask(
1566- asyncio.sleep(10_000), name="remaining"
1567- )
1568-
1569- asyncio.future_add_to_awaited_by(victim, removed)
1570- asyncio.future_add_to_awaited_by(victim, remaining)
1571-
1572- # Removing hash 0 leaves a dummy in slot 0 before the only
1573- # active entry in slot 1. It must not count toward the set's
1574- # used entries.
1575- asyncio.future_discard_from_awaited_by(victim, removed)
1576-
1577- sock.sendall(b"ready")
1578- sock.recv(16)
1579-
1580- asyncio.run(main())
1581- """
1582-
1583- with self ._target_process (script_body ) as (
1584- _ ,
1585- client_socket ,
1586- make_unwinder ,
1587- ):
1588- _wait_for_signal (client_socket , b"ready" )
1589-
1590- for method_name in (
1591- "get_async_stack_trace" ,
1592- "get_all_awaited_by" ,
1593- ):
1594- with self .subTest (method = method_name ):
1595- unwinder = make_unwinder (cache_frames = False )
1596- stack_trace = getattr (unwinder , method_name )()
1597- relationships = self ._get_awaited_by_relationships (
1598- stack_trace
1599- )
1600- self .assertEqual (
1601- relationships ["victim" ],
1602- {"remaining" },
1603- )
1604-
1605- client_socket .sendall (b"done" )
1606-
16071508 @skip_if_not_supported
16081509 @unittest .skipIf (
16091510 sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
@@ -3227,6 +3128,40 @@ class TestFrameCaching(RemoteInspectionTestBase):
32273128 All tests verify cache reuse via object identity checks (assertIs).
32283129 """
32293130
3131+ @contextmanager
3132+ def _target_process (self , script_body ):
3133+ """Context manager for running a target process with socket sync."""
3134+ port = find_unused_port ()
3135+ script = f"""\
3136+ import socket
3137+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3138+ sock.connect(('localhost', { port } ))
3139+ { textwrap .dedent (script_body )}
3140+ """
3141+
3142+ with os_helper .temp_dir () as work_dir :
3143+ script_dir = os .path .join (work_dir , "script_pkg" )
3144+ os .mkdir (script_dir )
3145+
3146+ server_socket = _create_server_socket (port )
3147+ script_name = _make_test_script (script_dir , "script" , script )
3148+ client_socket = None
3149+
3150+ try :
3151+ with _managed_subprocess ([sys .executable , script_name ]) as p :
3152+ client_socket , _ = server_socket .accept ()
3153+ server_socket .close ()
3154+ server_socket = None
3155+
3156+ def make_unwinder (cache_frames = True ):
3157+ return RemoteUnwinder (
3158+ p .pid , all_threads = True , cache_frames = cache_frames
3159+ )
3160+
3161+ yield p , client_socket , make_unwinder
3162+ finally :
3163+ _cleanup_sockets (client_socket , server_socket )
3164+
32303165 def _get_frames_with_retry (self , unwinder , required_funcs ):
32313166 """Get frames containing required_funcs, with retry for transient errors."""
32323167 for _ in range (MAX_TRIES ):
@@ -4070,163 +4005,5 @@ def test_get_stats_disabled_raises(self):
40704005 client_socket .sendall (b"done" )
40714006
40724007
4073- @requires_remote_subprocess_debugging ()
4074- class TestFrameChainLimits (RemoteInspectionTestBase ):
4075- """Frame chain walks abort instead of looping/overflowing on deep chains."""
4076-
4077- # Limits plus one, to exceed them (must match MAX_FRAME_CHAIN_DEPTH /
4078- # MAX_TASK_WAITER_WALK_TASKS from _remote_debugging.h)
4079- FRAME_CHAIN_DEPTH = 1024 + 512 + 1
4080- TASK_WAITER_WALK_TASKS = 2 ** 14 + 1
4081-
4082- def _assert_unwinder_limit_error (self , unwind , expected_substring ):
4083- """Call unwind() until it raises the frame chain limit error.
4084-
4085- unwind must construct the RemoteUnwinder and call it, so that
4086- transient RuntimeErrors from either step are retried; a successful
4087- call means the limit never triggered and fails immediately.
4088- """
4089- last_error = None
4090- for _ in busy_retry (SHORT_TIMEOUT , error = False ):
4091- try :
4092- unwind ()
4093- except TRANSIENT_ERRORS as e :
4094- if expected_substring in str (e ):
4095- return
4096- last_error = e
4097- continue
4098- self .fail (
4099- "frame chain limit did not trigger; call returned a result"
4100- )
4101- self .fail (
4102- f"frame chain limit never raised; last transient error: "
4103- f"{ last_error !r} "
4104- )
4105-
4106- @skip_if_not_supported
4107- @unittest .skipIf (
4108- sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
4109- "Test only runs on Linux with process_vm_readv support" ,
4110- )
4111- def test_get_stack_trace_deep_frame_chain_aborts (self ):
4112- """Test that a frame chain deeper than the limit aborts the
4113- synchronous stack walk instead of walking it indefinitely."""
4114- script_body = f"""\
4115- import sys
4116- sys.setrecursionlimit({ self .FRAME_CHAIN_DEPTH * 2 } )
4117-
4118- def recurse(n):
4119- if n <= 0:
4120- sock.sendall(b"ready")
4121- sock.recv(16)
4122- return
4123- recurse(n - 1)
4124-
4125- recurse({ self .FRAME_CHAIN_DEPTH } )
4126- """
4127- with self ._target_process (script_body ) as (p , client_socket , _ ):
4128- _wait_for_signal (client_socket , b"ready" )
4129- self ._assert_unwinder_limit_error (
4130- lambda : RemoteUnwinder (p .pid ).get_stack_trace (),
4131- "Too many stack frames" ,
4132- )
4133- client_socket .sendall (b"done" )
4134-
4135- @skip_if_not_supported
4136- @unittest .skipIf (
4137- sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
4138- "Test only runs on Linux with process_vm_readv support" ,
4139- )
4140- def test_get_async_stack_trace_deep_task_waiter_chain_aborts (self ):
4141- """Test that a task waiter chain deeper than the limit aborts
4142- the walk instead of overflowing the C stack."""
4143- script_body = f"""\
4144- import asyncio
4145-
4146- async def chain(n):
4147- if n <= 0:
4148- sock.sendall(b"ready")
4149- sock.recv(16)
4150- return
4151-
4152- task = asyncio.create_task(chain(n - 1))
4153- await task
4154-
4155- asyncio.run(chain({ self .TASK_WAITER_WALK_TASKS } ))
4156- """
4157- with self ._target_process (script_body ) as (p , client_socket , _ ):
4158- _wait_for_signal (client_socket , b"ready" )
4159- self ._assert_unwinder_limit_error (
4160- lambda : RemoteUnwinder (p .pid ).get_async_stack_trace (),
4161- "Too many task waiters" ,
4162- )
4163- client_socket .sendall (b"done" )
4164-
4165- @skip_if_not_supported
4166- @unittest .skipIf (
4167- sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
4168- "Test only runs on Linux with process_vm_readv support" ,
4169- )
4170- def test_get_async_stack_trace_deep_frame_chain_aborts (self ):
4171- """Test that a frame chain deeper than the limit aborts the async
4172- stack walk instead of walking it indefinitely."""
4173- script_body = f"""\
4174- import sys, asyncio
4175- sys.setrecursionlimit({ self .FRAME_CHAIN_DEPTH * 2 } )
4176-
4177- def recurse(n):
4178- if n <= 0:
4179- sock.sendall(b"ready")
4180- sock.recv(16)
4181- return
4182- recurse(n - 1)
4183-
4184- async def deep():
4185- recurse({ self .FRAME_CHAIN_DEPTH } )
4186-
4187- asyncio.run(deep())
4188- """
4189- with self ._target_process (script_body ) as (p , client_socket , _ ):
4190- _wait_for_signal (client_socket , b"ready" )
4191- self ._assert_unwinder_limit_error (
4192- lambda : RemoteUnwinder (p .pid ).get_async_stack_trace (),
4193- "Too many async stack frames" ,
4194- )
4195- client_socket .sendall (b"done" )
4196-
4197- @skip_if_not_supported
4198- @unittest .skipIf (
4199- sys .platform == "linux" and not PROCESS_VM_READV_SUPPORTED ,
4200- "Test only runs on Linux with process_vm_readv support" ,
4201- )
4202- def test_get_all_awaited_by_deep_coro_chain_aborts (self ):
4203- """Test that a coroutine await chain deeper than the limit aborts
4204- the walk instead of overflowing the C stack."""
4205- script_body = f"""\
4206- import sys, asyncio
4207- sys.setrecursionlimit({ self .FRAME_CHAIN_DEPTH * 2 } )
4208-
4209- async def chain(n):
4210- if n <= 0:
4211- await asyncio.sleep(10_000)
4212- return
4213- await chain(n - 1)
4214-
4215- async def main():
4216- task = asyncio.create_task(chain({ self .FRAME_CHAIN_DEPTH } ))
4217- await asyncio.sleep(0)
4218- sock.sendall(b"ready")
4219- await task
4220-
4221- asyncio.run(main())
4222- """
4223- with self ._target_process (script_body ) as (p , client_socket , _ ):
4224- _wait_for_signal (client_socket , b"ready" )
4225- self ._assert_unwinder_limit_error (
4226- lambda : RemoteUnwinder (p .pid ).get_all_awaited_by (),
4227- "Too many coroutine frames" ,
4228- )
4229-
4230-
42314008if __name__ == "__main__" :
42324009 unittest .main ()
0 commit comments