Skip to content

Commit 3c535fc

Browse files
authored
Merge branch 'master' into dependabot/pip/pathspec-0.10.2
2 parents d262697 + b3a729d commit 3c535fc

22 files changed

Lines changed: 261 additions & 115 deletions

docs-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ docutils==0.17.1
2828
# via
2929
# sphinx
3030
# sphinx-rtd-theme
31-
exceptiongroup==1.0.1
31+
exceptiongroup==1.0.4
3232
# via -r docs-requirements.in
3333
idna==3.4
3434
# via

docs/source/reference-lowlevel.rst

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -510,25 +510,9 @@ Task API
510510

511511
.. attribute:: coro
512512

513-
This task's coroutine object. Example usage: extracting a stack
514-
trace::
515-
516-
import traceback
517-
518-
def walk_coro_stack(coro):
519-
while coro is not None:
520-
if hasattr(coro, "cr_frame"):
521-
# A real coroutine
522-
yield coro.cr_frame, coro.cr_frame.f_lineno
523-
coro = coro.cr_await
524-
else:
525-
# A generator decorated with @types.coroutine
526-
yield coro.gi_frame, coro.gi_frame.f_lineno
527-
coro = coro.gi_yieldfrom
528-
529-
def print_stack_for_task(task):
530-
ss = traceback.StackSummary.extract(walk_coro_stack(task.coro))
531-
print("".join(ss.format()))
513+
This task's coroutine object.
514+
515+
.. automethod:: iter_await_frames
532516

533517
.. attribute:: context
534518

newsfragments/1810.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`trio.socket.socket` now prints the address it tried to connect to upon failure.

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@
8181
install_requires=[
8282
"attrs >= 19.2.0", # for eq
8383
"sortedcontainers",
84-
"async_generator >= 1.9",
8584
"idna",
8685
"outcome",
8786
"sniffio",

test-requirements.in

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# For tests
22
pytest >= 5.0 # for faulthandler in core
33
pytest-cov >= 2.6.0
4+
async_generator >= 1.9
45
# ipython 7.x is the last major version supporting Python 3.7
56
ipython < 7.32 # for the IPython traceback integration tests
67
pyOpenSSL >= 22.0.0 # for the ssl + DTLS tests
@@ -26,9 +27,7 @@ typing-extensions; implementation_name == "cpython"
2627
cffi; os_name == "nt"
2728
attrs >= 19.2.0
2829
sortedcontainers
29-
async_generator >= 1.9
3030
idna
3131
outcome
3232
sniffio
3333
exceptiongroup >= 1.0.0rc9; python_version < "3.11"
34-

test-requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ mccabe==0.6.1
5858
# via
5959
# flake8
6060
# pylint
61-
mypy==0.982 ; implementation_name == "cpython"
61+
mypy==0.991 ; implementation_name == "cpython"
6262
# via -r test-requirements.in
6363
mypy-extensions==0.4.3 ; implementation_name == "cpython"
6464
# via
@@ -77,7 +77,7 @@ pexpect==4.8.0
7777
# via ipython
7878
pickleshare==0.7.5
7979
# via ipython
80-
platformdirs==2.5.3
80+
platformdirs==2.5.4
8181
# via
8282
# black
8383
# pylint

trio/_core/_ki.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
from __future__ import annotations
2+
13
import inspect
24
import signal
35
import sys
46
from functools import wraps
5-
import attr
7+
from typing import TYPE_CHECKING
68

7-
import async_generator
9+
import attr
810

911
from .._util import is_main_thread
1012

11-
if False:
13+
if TYPE_CHECKING:
1214
from typing import Any, TypeVar, Callable
1315

1416
F = TypeVar("F", bound=Callable[..., Any])
@@ -55,7 +57,7 @@
5557
#
5658
# If this raises a KeyboardInterrupt, it might be because the coroutine got
5759
# interrupted and has unwound... or it might be the KeyboardInterrupt
58-
# arrived just *after* 'send' returned, so the coroutine is still running
60+
# arrived just *after* 'send' returned, so the coroutine is still running,
5961
# but we just lost the message it sent. (And worse, in our actual task
6062
# runner, the send is hidden inside a utility function etc.)
6163
#
@@ -109,6 +111,14 @@ def currently_ki_protected():
109111
return ki_protection_enabled(sys._getframe())
110112

111113

114+
# This is to support the async_generator package necessary for aclosing on <3.10
115+
# functions decorated @async_generator are given this magic property that's a
116+
# reference to the object itself
117+
# see python-trio/async_generator/async_generator/_impl.py
118+
def legacy_isasyncgenfunction(obj):
119+
return getattr(obj, "_async_gen_function", None) == id(obj)
120+
121+
112122
def _ki_protection_decorator(enabled):
113123
def decorator(fn):
114124
# In some version of Python, isgeneratorfunction returns true for
@@ -141,7 +151,7 @@ def wrapper(*args, **kwargs):
141151
return gen
142152

143153
return wrapper
144-
elif async_generator.isasyncgenfunction(fn):
154+
elif inspect.isasyncgenfunction(fn) or legacy_isasyncgenfunction(fn):
145155

146156
@wraps(fn)
147157
def wrapper(*args, **kwargs):
@@ -163,10 +173,10 @@ def wrapper(*args, **kwargs):
163173
return decorator
164174

165175

166-
enable_ki_protection = _ki_protection_decorator(True) # type: Callable[[F], F]
176+
enable_ki_protection: Callable[[F], F] = _ki_protection_decorator(True)
167177
enable_ki_protection.__name__ = "enable_ki_protection"
168178

169-
disable_ki_protection = _ki_protection_decorator(False) # type: Callable[[F], F]
179+
disable_ki_protection: Callable[[F], F] = _ki_protection_decorator(False)
170180
disable_ki_protection.__name__ = "disable_ki_protection"
171181

172182

trio/_core/_run.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import select
55
import sys
66
import threading
7+
import gc
78
from collections import deque
89
from contextlib import contextmanager
910
import warnings
@@ -1181,6 +1182,53 @@ def child_nurseries(self):
11811182
"""
11821183
return list(self._child_nurseries)
11831184

1185+
def iter_await_frames(self):
1186+
"""Iterates recursively over the coroutine-like objects this
1187+
task is waiting on, yielding the frame and line number at each
1188+
frame.
1189+
1190+
This is similar to `traceback.walk_stack` in a synchronous
1191+
context. Note that `traceback.walk_stack` returns frames from
1192+
the bottom of the call stack to the top, while this function
1193+
starts from `Task.coro <trio.lowlevel.Task.coro>` and works it
1194+
way down.
1195+
1196+
Example usage: extracting a stack trace::
1197+
1198+
import traceback
1199+
1200+
def print_stack_for_task(task):
1201+
ss = traceback.StackSummary.extract(task.iter_await_frames())
1202+
print("".join(ss.format()))
1203+
1204+
"""
1205+
coro = self.coro
1206+
while coro is not None:
1207+
if hasattr(coro, "cr_frame"):
1208+
# A real coroutine
1209+
yield coro.cr_frame, coro.cr_frame.f_lineno
1210+
coro = coro.cr_await
1211+
elif hasattr(coro, "gi_frame"):
1212+
# A generator decorated with @types.coroutine
1213+
yield coro.gi_frame, coro.gi_frame.f_lineno
1214+
coro = coro.gi_yieldfrom
1215+
elif coro.__class__.__name__ in [
1216+
"async_generator_athrow",
1217+
"async_generator_asend",
1218+
]:
1219+
# cannot extract the generator directly, see https://github.com/python/cpython/issues/76991
1220+
# we can however use the gc to look through the object
1221+
for referent in gc.get_referents(coro):
1222+
if hasattr(referent, "ag_frame"):
1223+
yield referent.ag_frame, referent.ag_frame.f_lineno
1224+
coro = referent.ag_await
1225+
break
1226+
else:
1227+
# either cpython changed or we are running on an alternative python implementation
1228+
return
1229+
else:
1230+
return
1231+
11841232
################
11851233
# Cancellation
11861234
################

trio/_core/_thread_cache.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
import traceback
13
from threading import Thread, Lock
24
import outcome
35
from itertools import count
@@ -67,7 +69,11 @@ def _handle_job(self):
6769
# 'deliver' triggers a new job, it can be assigned to us
6870
# instead of spawning a new thread.
6971
self._thread_cache._idle_workers[self] = None
70-
deliver(result)
72+
try:
73+
deliver(result)
74+
except BaseException as e:
75+
print("Exception while delivering result of thread", file=sys.stderr)
76+
traceback.print_exception(type(e), e, e.__traceback__)
7177

7278
def _work(self):
7379
while True:

trio/_core/tests/test_asyncgen.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import sys
22
import weakref
33
import pytest
4+
import contextlib
45
from math import inf
56
from functools import partial
6-
from async_generator import aclosing
7+
78
from ... import _core
89
from .tutil import gc_collect_harder, buggy_pypy_asyncgens, restore_unraisablehook
910

1011

12+
@pytest.mark.skipif(sys.version_info < (3, 10), reason="no aclosing() in stdlib<3.10")
1113
def test_asyncgen_basics():
1214
collected = []
1315

@@ -46,7 +48,7 @@ async def async_main():
4648
assert collected.pop() == "abandoned"
4749

4850
# aclosing() ensures it's cleaned up at point of use
49-
async with aclosing(example("exhausted 1")) as aiter:
51+
async with contextlib.aclosing(example("exhausted 1")) as aiter:
5052
assert 42 == await aiter.asend(None)
5153
assert collected.pop() == "exhausted 1"
5254

@@ -58,7 +60,7 @@ async def async_main():
5860
gc_collect_harder()
5961

6062
# No problems saving the geniter when using either of these patterns
61-
async with aclosing(example("exhausted 3")) as aiter:
63+
async with contextlib.aclosing(example("exhausted 3")) as aiter:
6264
saved.append(aiter)
6365
assert 42 == await aiter.asend(None)
6466
assert collected.pop() == "exhausted 3"

0 commit comments

Comments
 (0)