Skip to content

Commit 18b27ac

Browse files
authored
Merge branch 'main' into main
2 parents 5d65de7 + eb4c78d commit 18b27ac

36 files changed

+676
-92
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ jobs:
354354
with:
355355
persist-credentials: false
356356
- name: Build and test
357-
run: ./Android/android.py ci --fast-ci ${{ matrix.arch }}-linux-android
357+
run: python3 Platforms/Android ci --fast-ci ${{ matrix.arch }}-linux-android
358358

359359
build-ios:
360360
name: iOS

Lib/asyncio/__main__.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,29 @@ def interrupt(self) -> None:
162162
"ps", help="Display a table of all pending tasks in a process"
163163
)
164164
ps.add_argument("pid", type=int, help="Process ID to inspect")
165+
ps.add_argument(
166+
"--retries",
167+
type=int,
168+
default=3,
169+
help="Number of retries on transient attach errors",
170+
)
165171
pstree = subparsers.add_parser(
166172
"pstree", help="Display a tree of all pending tasks in a process"
167173
)
168174
pstree.add_argument("pid", type=int, help="Process ID to inspect")
175+
pstree.add_argument(
176+
"--retries",
177+
type=int,
178+
default=3,
179+
help="Number of retries on transient attach errors",
180+
)
169181
args = parser.parse_args()
170182
match args.command:
171183
case "ps":
172-
asyncio.tools.display_awaited_by_tasks_table(args.pid)
184+
asyncio.tools.display_awaited_by_tasks_table(args.pid, retries=args.retries)
173185
sys.exit(0)
174186
case "pstree":
175-
asyncio.tools.display_awaited_by_tasks_tree(args.pid)
187+
asyncio.tools.display_awaited_by_tasks_tree(args.pid, retries=args.retries)
176188
sys.exit(0)
177189
case None:
178190
pass # continue to the interactive shell

Lib/asyncio/tools.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -231,27 +231,38 @@ def exit_with_permission_help_text():
231231
print(
232232
"Error: The specified process cannot be attached to due to insufficient permissions.\n"
233233
"See the Python documentation for details on required privileges and troubleshooting:\n"
234-
"https://docs.python.org/3.14/howto/remote_debugging.html#permission-requirements\n"
234+
"https://docs.python.org/3/howto/remote_debugging.html#permission-requirements\n",
235+
file=sys.stderr,
235236
)
236237
sys.exit(1)
237238

238239

239-
def _get_awaited_by_tasks(pid: int) -> list:
240-
try:
241-
return get_all_awaited_by(pid)
242-
except RuntimeError as e:
243-
while e.__context__ is not None:
244-
e = e.__context__
245-
print(f"Error retrieving tasks: {e}")
246-
sys.exit(1)
247-
except PermissionError:
248-
exit_with_permission_help_text()
240+
_TRANSIENT_ERRORS = (RuntimeError, OSError, UnicodeDecodeError, MemoryError)
241+
242+
243+
def _get_awaited_by_tasks(pid: int, retries: int = 3) -> list:
244+
for attempt in range(retries + 1):
245+
try:
246+
return get_all_awaited_by(pid)
247+
except PermissionError:
248+
exit_with_permission_help_text()
249+
except ProcessLookupError:
250+
print(f"Error: process {pid} not found.", file=sys.stderr)
251+
sys.exit(1)
252+
except _TRANSIENT_ERRORS as e:
253+
if attempt < retries:
254+
continue
255+
if isinstance(e, RuntimeError):
256+
while e.__context__ is not None:
257+
e = e.__context__
258+
print(f"Error retrieving tasks: {e}", file=sys.stderr)
259+
sys.exit(1)
249260

250261

251-
def display_awaited_by_tasks_table(pid: int) -> None:
262+
def display_awaited_by_tasks_table(pid: int, retries: int = 3) -> None:
252263
"""Build and print a table of all pending tasks under `pid`."""
253264

254-
tasks = _get_awaited_by_tasks(pid)
265+
tasks = _get_awaited_by_tasks(pid, retries=retries)
255266
table = build_task_table(tasks)
256267
# Print the table in a simple tabular format
257268
print(
@@ -262,10 +273,10 @@ def display_awaited_by_tasks_table(pid: int) -> None:
262273
print(f"{row[0]:<10} {row[1]:<20} {row[2]:<20} {row[3]:<50} {row[4]:<50} {row[5]:<15} {row[6]:<15}")
263274

264275

265-
def display_awaited_by_tasks_tree(pid: int) -> None:
276+
def display_awaited_by_tasks_tree(pid: int, retries: int = 3) -> None:
266277
"""Build and print a tree of all pending tasks under `pid`."""
267278

268-
tasks = _get_awaited_by_tasks(pid)
279+
tasks = _get_awaited_by_tasks(pid, retries=retries)
269280
try:
270281
result = build_async_tree(tasks)
271282
except CycleFoundException as e:

Lib/configparser.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,15 @@ def __init__(self, source, *args):
315315

316316
def append(self, lineno, line):
317317
self.errors.append((lineno, line))
318-
self.message += '\n\t[line %2d]: %s' % (lineno, repr(line))
318+
self.message += f'\n\t[line {lineno:2d}]: {line!r}'
319319

320320
def combine(self, others):
321+
messages = [self.message]
321322
for other in others:
322-
for error in other.errors:
323-
self.append(*error)
323+
for lineno, line in other.errors:
324+
self.errors.append((lineno, line))
325+
messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
326+
self.message = "".join(messages)
324327
return self
325328

326329
@staticmethod

Lib/socket.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -649,18 +649,22 @@ def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
649649
# Authenticating avoids using a connection from something else
650650
# able to connect to {host}:{port} instead of us.
651651
# We expect only AF_INET and AF_INET6 families.
652-
try:
653-
if (
654-
ssock.getsockname() != csock.getpeername()
655-
or csock.getsockname() != ssock.getpeername()
656-
):
657-
raise ConnectionError("Unexpected peer connection")
658-
except:
659-
# getsockname() and getpeername() can fail
660-
# if either socket isn't connected.
661-
ssock.close()
662-
csock.close()
663-
raise
652+
#
653+
# Note that we skip this on WASI because on that platorm the client socket
654+
# may not have finished connecting by the time we've reached this point (gh-146139).
655+
if sys.platform != "wasi":
656+
try:
657+
if (
658+
ssock.getsockname() != csock.getpeername()
659+
or csock.getsockname() != ssock.getpeername()
660+
):
661+
raise ConnectionError("Unexpected peer connection")
662+
except:
663+
# getsockname() and getpeername() can fail
664+
# if either socket isn't connected.
665+
ssock.close()
666+
csock.close()
667+
raise
664668

665669
return (ssock, csock)
666670

Lib/test/test_configparser.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,19 @@ def test_error(self):
17291729
self.assertEqual(e1.message, e2.message)
17301730
self.assertEqual(repr(e1), repr(e2))
17311731

1732+
def test_combine_error_linear_complexity(self):
1733+
# Ensure that ParsingError.combine() has linear complexity.
1734+
# See https://github.com/python/cpython/issues/148370.
1735+
n = 50000
1736+
s = '[*]\n' + (err_line := '=\n') * n
1737+
p = configparser.ConfigParser(strict=False)
1738+
with self.assertRaises(configparser.ParsingError) as cm:
1739+
p.read_string(s)
1740+
errlines = cm.exception.message.splitlines()
1741+
self.assertEqual(len(errlines), n + 1)
1742+
self.assertStartsWith(errlines[0], "Source contains parsing errors: ")
1743+
self.assertEqual(errlines[42], f"\t[line {43:2d}]: {err_line!r}")
1744+
17321745
def test_nosectionerror(self):
17331746
import pickle
17341747
e1 = configparser.NoSectionError('section')
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The Android build tools have been moved to the Platforms folder.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`configparser`: prevent quadratic behavior when a :exc:`~configparser.ParsingError`
2+
is raised after a parser fails to parse multiple lines. Patch by Bénédikt Tran.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`asyncio` debugging tools (``python -m asyncio ps`` and ``pstree``)
2+
now retry automatically on transient errors that can occur when attaching
3+
to a process under active thread delegation. The number of retries can be
4+
controlled with the ``--retries`` flag. Patch by Bartosz Sławecki.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Hardened :mod:`!_remote_debugging` by validating remote debug offset tables
2+
before using them to size memory reads or interpret remote layouts.

0 commit comments

Comments
 (0)