Skip to content

Commit 4b5d623

Browse files
committed
Revert "[3.15] gh-153364: Make frame, coroutine, and task-waiter chain walks iterative and bounded (GH-153365) (#158813)"
This reverts commit 9e401cf.
1 parent 693eccc commit 4b5d623

6 files changed

Lines changed: 182 additions & 370 deletions

File tree

‎Lib/test/test_external_inspection.py‎

Lines changed: 34 additions & 257 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
42314008
if __name__ == "__main__":
42324009
unittest.main()

‎Misc/NEWS.d/next/Library/2026-07-08-22-18-04.gh-issue-153364.JBFHEg.rst‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)