@ajksunkang-aios/kgraph-linux-x64 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/bin/kgraph-launcher +15 -3
  2. package/lib/kgraph/scripts/build-bundle.sh +17 -4
  3. package/lib/site-packages/outcome/__init__.py +20 -0
  4. package/lib/site-packages/outcome/_impl.py +239 -0
  5. package/lib/site-packages/outcome/_util.py +33 -0
  6. package/lib/site-packages/outcome/_version.py +7 -0
  7. package/lib/site-packages/outcome/py.typed +0 -0
  8. package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
  9. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
  10. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
  11. package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
  12. package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
  13. package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
  14. package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
  15. package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
  16. package/lib/site-packages/sniffio/__init__.py +17 -0
  17. package/lib/site-packages/sniffio/_impl.py +95 -0
  18. package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
  19. package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
  20. package/lib/site-packages/sniffio/_version.py +3 -0
  21. package/lib/site-packages/sniffio/py.typed +0 -0
  22. package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
  23. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
  24. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
  25. package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
  26. package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
  27. package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
  28. package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
  29. package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
  30. package/lib/site-packages/sortedcontainers/__init__.py +74 -0
  31. package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
  32. package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
  33. package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
  34. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
  35. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
  36. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
  37. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
  38. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
  39. package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
  40. package/lib/site-packages/trio/__init__.py +133 -0
  41. package/lib/site-packages/trio/__main__.py +3 -0
  42. package/lib/site-packages/trio/_abc.py +714 -0
  43. package/lib/site-packages/trio/_channel.py +610 -0
  44. package/lib/site-packages/trio/_core/__init__.py +94 -0
  45. package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
  46. package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
  47. package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
  48. package/lib/site-packages/trio/_core/_exceptions.py +169 -0
  49. package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
  50. package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
  51. package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
  52. package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
  53. package/lib/site-packages/trio/_core/_generated_run.py +269 -0
  54. package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
  55. package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
  56. package/lib/site-packages/trio/_core/_io_common.py +31 -0
  57. package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
  58. package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
  59. package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
  60. package/lib/site-packages/trio/_core/_ki.py +271 -0
  61. package/lib/site-packages/trio/_core/_local.py +104 -0
  62. package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
  63. package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
  64. package/lib/site-packages/trio/_core/_run.py +3148 -0
  65. package/lib/site-packages/trio/_core/_run_context.py +15 -0
  66. package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
  67. package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
  68. package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
  69. package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
  70. package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
  71. package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
  72. package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
  73. package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
  74. package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
  75. package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
  76. package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
  77. package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
  78. package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
  79. package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
  80. package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
  81. package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
  82. package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
  83. package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
  84. package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
  85. package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
  86. package/lib/site-packages/trio/_core/_traps.py +318 -0
  87. package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
  88. package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
  89. package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
  90. package/lib/site-packages/trio/_deprecate.py +171 -0
  91. package/lib/site-packages/trio/_dtls.py +1380 -0
  92. package/lib/site-packages/trio/_file_io.py +513 -0
  93. package/lib/site-packages/trio/_highlevel_generic.py +125 -0
  94. package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
  95. package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
  96. package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
  97. package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
  98. package/lib/site-packages/trio/_highlevel_socket.py +423 -0
  99. package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
  100. package/lib/site-packages/trio/_path.py +289 -0
  101. package/lib/site-packages/trio/_repl.py +159 -0
  102. package/lib/site-packages/trio/_signals.py +185 -0
  103. package/lib/site-packages/trio/_socket.py +1326 -0
  104. package/lib/site-packages/trio/_ssl.py +964 -0
  105. package/lib/site-packages/trio/_subprocess.py +1178 -0
  106. package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
  107. package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
  108. package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
  109. package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
  110. package/lib/site-packages/trio/_sync.py +908 -0
  111. package/lib/site-packages/trio/_tests/__init__.py +0 -0
  112. package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
  113. package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
  114. package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
  115. package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
  116. package/lib/site-packages/trio/_tests/test_abc.py +72 -0
  117. package/lib/site-packages/trio/_tests/test_channel.py +750 -0
  118. package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
  119. package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
  120. package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
  121. package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
  122. package/lib/site-packages/trio/_tests/test_exports.py +626 -0
  123. package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
  124. package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
  125. package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
  126. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
  127. package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
  128. package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
  129. package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
  130. package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
  131. package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
  132. package/lib/site-packages/trio/_tests/test_path.py +279 -0
  133. package/lib/site-packages/trio/_tests/test_repl.py +428 -0
  134. package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
  135. package/lib/site-packages/trio/_tests/test_signals.py +186 -0
  136. package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
  137. package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
  138. package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
  139. package/lib/site-packages/trio/_tests/test_sync.py +735 -0
  140. package/lib/site-packages/trio/_tests/test_testing.py +682 -0
  141. package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
  142. package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
  143. package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
  144. package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
  145. package/lib/site-packages/trio/_tests/test_trio.py +8 -0
  146. package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
  147. package/lib/site-packages/trio/_tests/test_util.py +349 -0
  148. package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
  149. package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
  150. package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
  151. package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
  152. package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
  153. package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
  154. package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
  155. package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
  156. package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
  157. package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
  158. package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
  159. package/lib/site-packages/trio/_threads.py +610 -0
  160. package/lib/site-packages/trio/_timeouts.py +197 -0
  161. package/lib/site-packages/trio/_tools/__init__.py +0 -0
  162. package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
  163. package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
  164. package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
  165. package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
  166. package/lib/site-packages/trio/_unix_pipes.py +197 -0
  167. package/lib/site-packages/trio/_util.py +385 -0
  168. package/lib/site-packages/trio/_version.py +3 -0
  169. package/lib/site-packages/trio/_wait_for_object.py +67 -0
  170. package/lib/site-packages/trio/_windows_pipes.py +144 -0
  171. package/lib/site-packages/trio/abc.py +23 -0
  172. package/lib/site-packages/trio/from_thread.py +13 -0
  173. package/lib/site-packages/trio/lowlevel.py +95 -0
  174. package/lib/site-packages/trio/py.typed +0 -0
  175. package/lib/site-packages/trio/socket.py +602 -0
  176. package/lib/site-packages/trio/testing/__init__.py +58 -0
  177. package/lib/site-packages/trio/testing/_check_streams.py +570 -0
  178. package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
  179. package/lib/site-packages/trio/testing/_fake_net.py +584 -0
  180. package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
  181. package/lib/site-packages/trio/testing/_network.py +36 -0
  182. package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
  183. package/lib/site-packages/trio/testing/_sequencer.py +87 -0
  184. package/lib/site-packages/trio/testing/_trio_test.py +50 -0
  185. package/lib/site-packages/trio/to_thread.py +4 -0
  186. package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
  187. package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
  188. package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
  189. package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
  190. package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
  191. package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
  192. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
  193. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
  194. package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
  195. package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
  196. package/package.json +1 -1
@@ -0,0 +1,3148 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+ import functools
5
+ import gc
6
+ import itertools
7
+ import random
8
+ import select
9
+ import sys
10
+ import warnings
11
+ from collections import deque
12
+ from contextlib import AbstractAsyncContextManager, contextmanager, suppress
13
+ from contextvars import copy_context
14
+ from heapq import heapify, heappop, heappush
15
+ from math import inf, isnan
16
+ from time import perf_counter
17
+ from typing import (
18
+ TYPE_CHECKING,
19
+ Any,
20
+ Final,
21
+ NoReturn,
22
+ Protocol,
23
+ cast,
24
+ overload,
25
+ )
26
+
27
+ import attrs
28
+ import outcome
29
+ from outcome import Error, Outcome, Value, capture
30
+ from sniffio import thread_local as sniffio_library
31
+ from sortedcontainers import SortedDict
32
+
33
+ from .. import _core
34
+ from .._abc import Clock, Instrument
35
+ from .._deprecate import warn_deprecated
36
+ from .._util import NoPublicConstructor, coroutine_or_error, final
37
+ from ._asyncgens import AsyncGenerators
38
+ from ._concat_tb import concat_tb
39
+ from ._entry_queue import EntryQueue, TrioToken
40
+ from ._exceptions import (
41
+ Cancelled,
42
+ CancelReasonLiteral,
43
+ RunFinishedError,
44
+ TrioInternalError,
45
+ )
46
+ from ._instrumentation import Instruments
47
+ from ._ki import KIManager, enable_ki_protection
48
+ from ._parking_lot import GLOBAL_PARKING_LOT_BREAKER
49
+ from ._run_context import GLOBAL_RUN_CONTEXT as GLOBAL_RUN_CONTEXT
50
+ from ._thread_cache import start_thread_soon
51
+ from ._traps import (
52
+ Abort,
53
+ CancelShieldedCheckpoint,
54
+ PermanentlyDetachCoroutineObject,
55
+ WaitTaskRescheduled,
56
+ cancel_shielded_checkpoint,
57
+ wait_task_rescheduled,
58
+ )
59
+
60
+ if sys.version_info < (3, 11):
61
+ from exceptiongroup import BaseExceptionGroup
62
+
63
+
64
+ if TYPE_CHECKING:
65
+ import contextvars
66
+ import types
67
+ from collections.abc import (
68
+ Awaitable,
69
+ Callable,
70
+ Generator,
71
+ Iterator,
72
+ Sequence,
73
+ )
74
+ from types import TracebackType
75
+
76
+ # for some strange reason Sphinx works with outcome.Outcome, but not Outcome, in
77
+ # start_guest_run. Same with types.FrameType in iter_await_frames
78
+ import outcome
79
+ from typing_extensions import Self, TypeVar, TypeVarTuple, Unpack
80
+
81
+ PosArgT = TypeVarTuple("PosArgT")
82
+ StatusT = TypeVar("StatusT", default=None)
83
+ StatusT_contra = TypeVar("StatusT_contra", contravariant=True, default=None)
84
+ BaseExcT = TypeVar("BaseExcT", bound=BaseException)
85
+ else:
86
+ from typing import TypeVar
87
+
88
+ StatusT = TypeVar("StatusT")
89
+ StatusT_contra = TypeVar("StatusT_contra", contravariant=True)
90
+
91
+ RetT = TypeVar("RetT")
92
+
93
+
94
+ DEADLINE_HEAP_MIN_PRUNE_THRESHOLD: Final = 1000
95
+
96
+ # Passed as a sentinel
97
+ _NO_SEND: Final[Outcome[object]] = cast("Outcome[object]", object())
98
+
99
+ # Used to track if an exceptiongroup can be collapsed
100
+ NONSTRICT_EXCEPTIONGROUP_NOTE = 'This is a "loose" ExceptionGroup, and may be collapsed by Trio if it only contains one exception - typically after `Cancelled` has been stripped from it. Note this has consequences for exception handling, and strict_exception_groups=True is recommended.'
101
+
102
+
103
+ @final
104
+ class _NoStatus(metaclass=NoPublicConstructor):
105
+ """Sentinel for unset TaskStatus._value."""
106
+
107
+
108
+ # Decorator to mark methods public. This does nothing by itself, but
109
+ # trio/_tools/gen_exports.py looks for it.
110
+ def _public(fn: RetT) -> RetT:
111
+ return fn
112
+
113
+
114
+ # When running under Hypothesis, we want examples to be reproducible and
115
+ # shrinkable. We therefore register `_hypothesis_plugin_setup()` as a
116
+ # plugin, so that importing *Hypothesis* will make Trio's task
117
+ # scheduling loop deterministic. We have a test for that, of course.
118
+ # Before Hypothesis supported entry-point plugins this integration was
119
+ # handled by pytest-trio, but we want it to work in e.g. unittest too.
120
+ _ALLOW_DETERMINISTIC_SCHEDULING: Final = False
121
+ _r = random.Random()
122
+
123
+
124
+ # no cover because we don't check the hypothesis plugin works with hypothesis
125
+ def _hypothesis_plugin_setup() -> None: # pragma: no cover
126
+ from hypothesis import register_random
127
+
128
+ global _ALLOW_DETERMINISTIC_SCHEDULING
129
+ _ALLOW_DETERMINISTIC_SCHEDULING = True # type: ignore
130
+ register_random(_r)
131
+
132
+ # monkeypatch repr_callable to make repr's way better
133
+ # requires importing hypothesis (in the test file or in conftest.py)
134
+ try:
135
+ from hypothesis.internal.reflection import get_pretty_function_description
136
+
137
+ import trio.testing._raises_group
138
+
139
+ def repr_callable(fun: Callable[[BaseExcT], bool]) -> str:
140
+ # add quotes around the signature
141
+ return repr(get_pretty_function_description(fun))
142
+
143
+ trio.testing._raises_group.repr_callable = repr_callable
144
+ except ImportError:
145
+ pass
146
+
147
+
148
+ def _count_context_run_tb_frames() -> int:
149
+ """Count implementation dependent traceback frames from Context.run()
150
+
151
+ On CPython, Context.run() is implemented in C and doesn't show up in
152
+ tracebacks. On PyPy, it is implemented in Python and adds 1 frame to
153
+ tracebacks.
154
+
155
+ Returns:
156
+ int: Traceback frame count
157
+
158
+ """
159
+
160
+ def function_with_unique_name_xyzzy() -> NoReturn:
161
+ try:
162
+ 1 / 0 # noqa: B018 # We need a ZeroDivisionError to fire
163
+ except ZeroDivisionError:
164
+ raise
165
+ else: # pragma: no cover
166
+ raise TrioInternalError(
167
+ "A ZeroDivisionError should have been raised, but it wasn't.",
168
+ )
169
+
170
+ ctx = copy_context()
171
+ try:
172
+ ctx.run(function_with_unique_name_xyzzy)
173
+ except ZeroDivisionError as exc:
174
+ tb = exc.__traceback__
175
+ # Skip the frame where we caught it
176
+ tb = tb.tb_next # type: ignore[union-attr]
177
+ count = 0
178
+ while tb.tb_frame.f_code.co_name != "function_with_unique_name_xyzzy": # type: ignore[union-attr]
179
+ tb = tb.tb_next # type: ignore[union-attr]
180
+ count += 1
181
+ return count
182
+ else: # pragma: no cover
183
+ raise TrioInternalError(
184
+ f"The purpose of {function_with_unique_name_xyzzy.__name__} is "
185
+ "to raise a ZeroDivisionError, but it didn't.",
186
+ )
187
+
188
+
189
+ CONTEXT_RUN_TB_FRAMES: Final = _count_context_run_tb_frames()
190
+
191
+
192
+ @attrs.frozen
193
+ class SystemClock(Clock):
194
+ # Add a large random offset to our clock to ensure that if people
195
+ # accidentally call time.perf_counter() directly or start comparing clocks
196
+ # between different runs, then they'll notice the bug quickly:
197
+ offset: float = attrs.Factory(lambda: _r.uniform(10000, 200000))
198
+
199
+ def start_clock(self) -> None:
200
+ pass
201
+
202
+ # In cPython 3, on every platform except Windows, perf_counter is
203
+ # exactly the same as time.monotonic; and on Windows, it uses
204
+ # QueryPerformanceCounter instead of GetTickCount64.
205
+ def current_time(self) -> float:
206
+ return self.offset + perf_counter()
207
+
208
+ def deadline_to_sleep_time(self, deadline: float) -> float:
209
+ return deadline - self.current_time()
210
+
211
+
212
+ class IdlePrimedTypes(enum.Enum):
213
+ WAITING_FOR_IDLE = 1
214
+ AUTOJUMP_CLOCK = 2
215
+
216
+
217
+ ################################################################
218
+ # CancelScope and friends
219
+ ################################################################
220
+
221
+
222
+ def collapse_exception_group(
223
+ excgroup: BaseExceptionGroup[BaseException],
224
+ ) -> BaseException:
225
+ """Recursively collapse any single-exception groups into that single contained
226
+ exception.
227
+
228
+ """
229
+ exceptions = list(excgroup.exceptions)
230
+ modified = False
231
+ for i, exc in enumerate(exceptions):
232
+ if isinstance(exc, BaseExceptionGroup):
233
+ new_exc = collapse_exception_group(exc)
234
+ if new_exc is not exc:
235
+ modified = True
236
+ exceptions[i] = new_exc
237
+
238
+ if (
239
+ len(exceptions) == 1
240
+ and isinstance(excgroup, BaseExceptionGroup)
241
+ and NONSTRICT_EXCEPTIONGROUP_NOTE in getattr(excgroup, "__notes__", ())
242
+ ):
243
+ exceptions[0].__traceback__ = concat_tb(
244
+ excgroup.__traceback__,
245
+ exceptions[0].__traceback__,
246
+ )
247
+ return exceptions[0]
248
+ elif modified:
249
+ return excgroup.derive(exceptions)
250
+ else:
251
+ return excgroup
252
+
253
+
254
+ @attrs.define(eq=False)
255
+ class Deadlines:
256
+ """A container of deadlined cancel scopes.
257
+
258
+ Only contains scopes with non-infinite deadlines that are currently
259
+ attached to at least one task.
260
+
261
+ """
262
+
263
+ # Heap of (deadline, id(CancelScope), CancelScope)
264
+ _heap: list[tuple[float, int, CancelScope]] = attrs.Factory(list)
265
+ # Count of active deadlines (those that haven't been changed)
266
+ _active: int = 0
267
+
268
+ def add(self, deadline: float, cancel_scope: CancelScope) -> None:
269
+ heappush(self._heap, (deadline, id(cancel_scope), cancel_scope))
270
+ self._active += 1
271
+
272
+ def remove(self, deadline: float, cancel_scope: CancelScope) -> None:
273
+ self._active -= 1
274
+
275
+ def next_deadline(self) -> float:
276
+ while self._heap:
277
+ deadline, _, cancel_scope = self._heap[0]
278
+ if deadline == cancel_scope._registered_deadline:
279
+ return deadline
280
+ else:
281
+ # This entry is stale; discard it and try again
282
+ heappop(self._heap)
283
+ return inf
284
+
285
+ def _prune(self) -> None:
286
+ # In principle, it's possible for a cancel scope to toggle back and
287
+ # forth repeatedly between the same two deadlines, and end up with
288
+ # lots of stale entries that *look* like they're still active, because
289
+ # their deadline is correct, but in fact are redundant. So when
290
+ # pruning we have to eliminate entries with the wrong deadline, *and*
291
+ # eliminate duplicates.
292
+ seen = set()
293
+ pruned_heap = []
294
+ for deadline, tiebreaker, cancel_scope in self._heap:
295
+ if deadline == cancel_scope._registered_deadline:
296
+ if cancel_scope in seen:
297
+ continue
298
+ seen.add(cancel_scope)
299
+ pruned_heap.append((deadline, tiebreaker, cancel_scope))
300
+ # See test_cancel_scope_deadline_duplicates for a test that exercises
301
+ # this assert:
302
+ assert len(pruned_heap) == self._active
303
+ heapify(pruned_heap)
304
+ self._heap = pruned_heap
305
+
306
+ def expire(self, now: float) -> bool:
307
+ did_something = False
308
+ while self._heap and self._heap[0][0] <= now:
309
+ deadline, _, cancel_scope = heappop(self._heap)
310
+ if deadline == cancel_scope._registered_deadline:
311
+ did_something = True
312
+ # This implicitly calls self.remove(), so we don't need to
313
+ # decrement _active here
314
+ cancel_scope._cancel(CancelReason(source="deadline"))
315
+ # If we've accumulated too many stale entries, then prune the heap to
316
+ # keep it under control. (We only do this occasionally in a batch, to
317
+ # keep the amortized cost down)
318
+ if len(self._heap) > self._active * 2 + DEADLINE_HEAP_MIN_PRUNE_THRESHOLD:
319
+ self._prune()
320
+ return did_something
321
+
322
+
323
+ @attrs.define
324
+ class CancelReason:
325
+ """Attached to a :class:`CancelScope` upon cancellation with details of the source of the
326
+ cancellation, which is then used to construct the string in a :exc:`Cancelled`.
327
+ Users can pass a ``reason`` str to :meth:`CancelScope.cancel` to set it.
328
+
329
+ Not publicly exported or documented.
330
+ """
331
+
332
+ source: CancelReasonLiteral
333
+ source_task: str | None = None
334
+ reason: str | None = None
335
+
336
+
337
+ @attrs.define(eq=False)
338
+ class CancelStatus:
339
+ """Tracks the cancellation status for a contiguous extent
340
+ of code that will become cancelled, or not, as a unit.
341
+
342
+ Each task has at all times a single "active" CancelStatus whose
343
+ cancellation state determines whether checkpoints executed in that
344
+ task raise Cancelled. Each 'with CancelScope(...)' context is
345
+ associated with a particular CancelStatus. When a task enters
346
+ such a context, a CancelStatus is created which becomes the active
347
+ CancelStatus for that task; when the 'with' block is exited, the
348
+ active CancelStatus for that task goes back to whatever it was
349
+ before.
350
+
351
+ CancelStatus objects are arranged in a tree whose structure
352
+ mirrors the lexical nesting of the cancel scope contexts. When a
353
+ CancelStatus becomes cancelled, it notifies all of its direct
354
+ children, who become cancelled in turn (and continue propagating
355
+ the cancellation down the tree) unless they are shielded. (There
356
+ will be at most one such child except in the case of a
357
+ CancelStatus that immediately encloses a nursery.) At the leaves
358
+ of this tree are the tasks themselves, which get woken up to deliver
359
+ an abort when their direct parent CancelStatus becomes cancelled.
360
+
361
+ You can think of CancelStatus as being responsible for the
362
+ "plumbing" of cancellations as oppposed to CancelScope which is
363
+ responsible for the origination of them.
364
+
365
+ """
366
+
367
+ # Our associated cancel scope. Can be any object with attributes
368
+ # `deadline`, `shield`, and `cancel_called`, but in current usage
369
+ # is always a CancelScope object. Must not be None.
370
+ _scope: CancelScope = attrs.field(alias="scope")
371
+
372
+ # True iff the tasks in self._tasks should receive cancellations
373
+ # when they checkpoint. Always True when scope.cancel_called is True;
374
+ # may also be True due to a cancellation propagated from our
375
+ # parent. Unlike scope.cancel_called, this does not necessarily stay
376
+ # true once it becomes true. For example, we might become
377
+ # effectively cancelled due to the cancel scope two levels out
378
+ # becoming cancelled, but then the cancel scope one level out
379
+ # becomes shielded so we're not effectively cancelled anymore.
380
+ effectively_cancelled: bool = False
381
+
382
+ # The CancelStatus whose cancellations can propagate to us; we
383
+ # become effectively cancelled when they do, unless scope.shield
384
+ # is True. May be None (for the outermost CancelStatus in a call
385
+ # to trio.run(), briefly during TaskStatus.started(), or during
386
+ # recovery from misnesting of cancel scopes).
387
+ _parent: CancelStatus | None = attrs.field(default=None, repr=False, alias="parent")
388
+
389
+ # All of the CancelStatuses that have this CancelStatus as their parent.
390
+ _children: set[CancelStatus] = attrs.field(factory=set, init=False, repr=False)
391
+
392
+ # Tasks whose cancellation state is currently tied directly to
393
+ # the cancellation state of this CancelStatus object. Don't modify
394
+ # this directly; instead, use Task._activate_cancel_status().
395
+ # Invariant: all(task._cancel_status is self for task in self._tasks)
396
+ _tasks: set[Task] = attrs.field(factory=set, init=False, repr=False)
397
+
398
+ # Set to True on still-active cancel statuses that are children
399
+ # of a cancel status that's been closed. This is used to permit
400
+ # recovery from misnested cancel scopes (well, at least enough
401
+ # recovery to show a useful traceback).
402
+ abandoned_by_misnesting: bool = attrs.field(default=False, init=False, repr=False)
403
+
404
+ def __attrs_post_init__(self) -> None:
405
+ if self._parent is not None:
406
+ self._parent._children.add(self)
407
+ self.recalculate()
408
+
409
+ # parent/children/tasks accessors are used by TaskStatus.started()
410
+
411
+ @property
412
+ def parent(self) -> CancelStatus | None:
413
+ return self._parent
414
+
415
+ @parent.setter
416
+ def parent(self, parent: CancelStatus | None) -> None:
417
+ if self._parent is not None:
418
+ self._parent._children.remove(self)
419
+ self._parent = parent
420
+ if self._parent is not None:
421
+ self._parent._children.add(self)
422
+ self.recalculate()
423
+
424
+ @property
425
+ def children(self) -> frozenset[CancelStatus]:
426
+ return frozenset(self._children)
427
+
428
+ @property
429
+ def tasks(self) -> frozenset[Task]:
430
+ return frozenset(self._tasks)
431
+
432
+ def encloses(self, other: CancelStatus | None) -> bool:
433
+ """Returns true if this cancel status is a direct or indirect
434
+ parent of cancel status *other*, or if *other* is *self*.
435
+ """
436
+ while other is not None:
437
+ if other is self:
438
+ return True
439
+ other = other.parent
440
+ return False
441
+
442
+ def close(self) -> None:
443
+ self.parent = None # now we're not a child of self.parent anymore
444
+ if self._tasks or self._children:
445
+ # Cancel scopes weren't exited in opposite order of being
446
+ # entered. CancelScope._close() deals with raising an error
447
+ # if appropriate; our job is to leave things in a reasonable
448
+ # state for unwinding our dangling children. We choose to leave
449
+ # this part of the CancelStatus tree unlinked from everyone
450
+ # else, cancelled, and marked so that exiting a CancelScope
451
+ # within the abandoned subtree doesn't affect the active
452
+ # CancelStatus. Note that it's possible for us to get here
453
+ # without CancelScope._close() raising an error, if a
454
+ # nursery's cancel scope is closed within the nursery's
455
+ # nested child and no other cancel scopes are involved,
456
+ # but in that case task_exited() will deal with raising
457
+ # the error.
458
+ self._mark_abandoned()
459
+
460
+ # Since our CancelScope is about to forget about us, and we
461
+ # have no parent anymore, there's nothing left to call
462
+ # recalculate(). So, we can stay cancelled by setting
463
+ # effectively_cancelled and updating our children.
464
+ self.effectively_cancelled = True
465
+ for task in self._tasks:
466
+ task._attempt_delivery_of_any_pending_cancel()
467
+ for child in self._children:
468
+ child.recalculate()
469
+
470
+ @property
471
+ def parent_cancellation_is_visible_to_us(self) -> bool:
472
+ return (
473
+ self._parent is not None
474
+ and not self._scope.shield
475
+ and self._parent.effectively_cancelled
476
+ )
477
+
478
+ def recalculate(self) -> None:
479
+ # This does a depth-first traversal over this and descendent cancel
480
+ # statuses, to ensure their state is up-to-date. It's basically a
481
+ # recursive algorithm, but we use an explicit stack to avoid any
482
+ # issues with stack overflow.
483
+ todo = [self]
484
+ while todo:
485
+ current = todo.pop()
486
+ new_state = (
487
+ current._scope.cancel_called
488
+ or current.parent_cancellation_is_visible_to_us
489
+ )
490
+ if new_state != current.effectively_cancelled:
491
+ if (
492
+ current._scope._cancel_reason is None
493
+ and current.parent_cancellation_is_visible_to_us
494
+ ):
495
+ assert current._parent is not None
496
+ current._scope._cancel_reason = (
497
+ current._parent._scope._cancel_reason
498
+ )
499
+ current.effectively_cancelled = new_state
500
+ if new_state:
501
+ for task in current._tasks:
502
+ task._attempt_delivery_of_any_pending_cancel()
503
+ todo.extend(current._children)
504
+
505
+ def _mark_abandoned(self) -> None:
506
+ self.abandoned_by_misnesting = True
507
+ for child in self._children:
508
+ child._mark_abandoned()
509
+
510
+ def effective_deadline(self) -> float:
511
+ if self.effectively_cancelled:
512
+ return -inf
513
+ if self._parent is None or self._scope.shield:
514
+ return self._scope.deadline
515
+ return min(self._scope.deadline, self._parent.effective_deadline())
516
+
517
+
518
+ MISNESTING_ADVICE = """
519
+ This is probably a bug in your code, that has caused Trio's internal state to
520
+ become corrupted. We'll do our best to recover, but from now on there are
521
+ no guarantees.
522
+
523
+ Typically this is caused by one of the following:
524
+ - yielding within a generator or async generator that's opened a cancel
525
+ scope or nursery (unless the generator is a @contextmanager or
526
+ @asynccontextmanager); see https://github.com/python-trio/trio/issues/638
527
+ - manually calling __enter__ or __exit__ on a trio.CancelScope, or
528
+ __aenter__ or __aexit__ on the object returned by trio.open_nursery();
529
+ doing so correctly is difficult and you should use @[async]contextmanager
530
+ instead, or maybe [Async]ExitStack
531
+ - using [Async]ExitStack to interleave the entries/exits of cancel scopes
532
+ and/or nurseries in a way that couldn't be achieved by some nesting of
533
+ 'with' and 'async with' blocks
534
+ - using the low-level coroutine object protocol to execute some parts of
535
+ an async function in a different cancel scope/nursery context than
536
+ other parts
537
+ If you don't believe you're doing any of these things, please file a bug:
538
+ https://github.com/python-trio/trio/issues/new
539
+ """
540
+
541
+
542
+ @final
543
+ @attrs.define(eq=False, repr=False)
544
+ class CancelScope:
545
+ """A *cancellation scope*: the link between a unit of cancellable
546
+ work and Trio's cancellation system.
547
+
548
+ A :class:`CancelScope` becomes associated with some cancellable work
549
+ when it is used as a context manager surrounding that work::
550
+
551
+ cancel_scope = trio.CancelScope()
552
+ ...
553
+ with cancel_scope:
554
+ await long_running_operation()
555
+
556
+ Inside the ``with`` block, a cancellation of ``cancel_scope`` (via
557
+ a call to its :meth:`cancel` method or via the expiry of its
558
+ :attr:`deadline`) will immediately interrupt the
559
+ ``long_running_operation()`` by raising :exc:`Cancelled` at its
560
+ next :ref:`checkpoint <checkpoints>`.
561
+
562
+ The context manager ``__enter__`` returns the :class:`CancelScope`
563
+ object itself, so you can also write ``with trio.CancelScope() as
564
+ cancel_scope:``.
565
+
566
+ If a cancel scope becomes cancelled before entering its ``with`` block,
567
+ the :exc:`Cancelled` exception will be raised at the first
568
+ checkpoint inside the ``with`` block. This allows a
569
+ :class:`CancelScope` to be created in one :ref:`task <tasks>` and
570
+ passed to another, so that the first task can later cancel some work
571
+ inside the second.
572
+
573
+ Cancel scopes are not reusable or reentrant; that is, each cancel
574
+ scope can be used for at most one ``with`` block. (You'll get a
575
+ :exc:`RuntimeError` if you violate this rule.)
576
+
577
+ The :class:`CancelScope` constructor takes initial values for the
578
+ cancel scope's :attr:`deadline` and :attr:`shield` attributes; these
579
+ may be freely modified after construction, whether or not the scope
580
+ has been entered yet, and changes take immediate effect.
581
+ """
582
+
583
+ _cancel_status: CancelStatus | None = attrs.field(default=None, init=False)
584
+ _has_been_entered: bool = attrs.field(default=False, init=False)
585
+ _registered_deadline: float = attrs.field(default=inf, init=False)
586
+ _cancel_called: bool = attrs.field(default=False, init=False)
587
+ cancelled_caught: bool = attrs.field(default=False, init=False)
588
+
589
+ _cancel_reason: CancelReason | None = attrs.field(default=None, init=False)
590
+
591
+ # Constructor arguments:
592
+ _relative_deadline: float = attrs.field(
593
+ default=inf,
594
+ kw_only=True,
595
+ alias="relative_deadline",
596
+ )
597
+ _deadline: float = attrs.field(default=inf, kw_only=True, alias="deadline")
598
+ _shield: bool = attrs.field(default=False, kw_only=True, alias="shield")
599
+
600
+ def __attrs_post_init__(self) -> None:
601
+ if isnan(self._deadline):
602
+ raise ValueError("deadline must not be NaN")
603
+ if isnan(self._relative_deadline):
604
+ raise ValueError("relative deadline must not be NaN")
605
+ if self._relative_deadline < 0:
606
+ raise ValueError("timeout must be non-negative")
607
+ if self._relative_deadline != inf and self._deadline != inf:
608
+ raise ValueError(
609
+ "Cannot specify both a deadline and a relative deadline",
610
+ )
611
+
612
+ @enable_ki_protection
613
+ def __enter__(self) -> Self:
614
+ task = _core.current_task()
615
+ if self._has_been_entered:
616
+ raise RuntimeError(
617
+ "Each CancelScope may only be used for a single 'with' block",
618
+ )
619
+ self._has_been_entered = True
620
+
621
+ if self._relative_deadline != inf:
622
+ assert self._deadline == inf
623
+ self._deadline = current_time() + self._relative_deadline
624
+ self._relative_deadline = inf
625
+
626
+ if current_time() >= self._deadline:
627
+ self._cancel(CancelReason(source="deadline"))
628
+ with self._might_change_registered_deadline():
629
+ self._cancel_status = CancelStatus(scope=self, parent=task._cancel_status)
630
+ task._activate_cancel_status(self._cancel_status)
631
+ return self
632
+
633
+ def _close(self, exc: BaseException | None) -> BaseException | None:
634
+ if self._cancel_status is None:
635
+ new_exc = RuntimeError(
636
+ f"Cancel scope stack corrupted: attempted to exit {self!r} "
637
+ "which had already been exited",
638
+ )
639
+ new_exc.__context__ = exc
640
+ return new_exc
641
+ scope_task = current_task()
642
+ if scope_task._cancel_status is not self._cancel_status:
643
+ # Cancel scope misnesting: this cancel scope isn't the most
644
+ # recently opened by this task (that's still open). That is,
645
+ # our assumptions about context managers forming a stack
646
+ # have been violated. Try and make the best of it.
647
+ if self._cancel_status.abandoned_by_misnesting:
648
+ # We are an inner cancel scope that was still active when
649
+ # some outer scope was closed. The closure of that outer
650
+ # scope threw an error, so we don't need to throw another
651
+ # one; it would just confuse the traceback.
652
+ pass
653
+ elif not self._cancel_status.encloses(scope_task._cancel_status):
654
+ # This task isn't even indirectly contained within the
655
+ # cancel scope it's trying to close. Raise an error
656
+ # without changing any state.
657
+ new_exc = RuntimeError(
658
+ f"Cancel scope stack corrupted: attempted to exit {self!r} "
659
+ f"from unrelated {scope_task!r}\n{MISNESTING_ADVICE}",
660
+ )
661
+ new_exc.__context__ = exc
662
+ return new_exc
663
+ else:
664
+ # Otherwise, there's some inner cancel scope(s) that
665
+ # we're abandoning by closing this outer one.
666
+ # CancelStatus.close() will take care of the plumbing;
667
+ # we just need to make sure we don't let the error
668
+ # pass silently.
669
+ new_exc = RuntimeError(
670
+ f"Cancel scope stack corrupted: attempted to exit {self!r} "
671
+ f"in {scope_task!r} that's still within its child {scope_task._cancel_status._scope!r}\n{MISNESTING_ADVICE}",
672
+ )
673
+ new_exc.__context__ = exc
674
+ exc = new_exc
675
+ scope_task._activate_cancel_status(self._cancel_status.parent)
676
+ else:
677
+ scope_task._activate_cancel_status(self._cancel_status.parent)
678
+ if (
679
+ exc is not None
680
+ and self._cancel_status.effectively_cancelled
681
+ and not self._cancel_status.parent_cancellation_is_visible_to_us
682
+ ) or (
683
+ scope_task._cancel_status is not self._cancel_status
684
+ and self._cancel_status.abandoned_by_misnesting
685
+ ):
686
+ if isinstance(exc, Cancelled):
687
+ self.cancelled_caught = True
688
+ exc = None
689
+ elif isinstance(exc, BaseExceptionGroup):
690
+ matched, exc = exc.split(Cancelled)
691
+ if matched:
692
+ self.cancelled_caught = True
693
+
694
+ if exc:
695
+ exc = collapse_exception_group(exc)
696
+
697
+ self._cancel_status.close()
698
+ with self._might_change_registered_deadline():
699
+ self._cancel_status = None
700
+ return exc
701
+
702
+ @enable_ki_protection
703
+ def __exit__(
704
+ self,
705
+ etype: type[BaseException] | None,
706
+ exc: BaseException | None,
707
+ tb: TracebackType | None,
708
+ ) -> bool:
709
+ # NB: NurseryManager calls _close() directly rather than __exit__(),
710
+ # so __exit__() must be just _close() plus this logic for adapting
711
+ # the exception-filtering result to the context manager API.
712
+
713
+ # Tracebacks show the 'raise' line below out of context, so let's give
714
+ # this variable a name that makes sense out of context.
715
+ remaining_error_after_cancel_scope = self._close(exc)
716
+ if remaining_error_after_cancel_scope is None:
717
+ return True
718
+ elif remaining_error_after_cancel_scope is exc:
719
+ return False
720
+ else:
721
+ # Copied verbatim from the old MultiErrorCatcher. Python doesn't
722
+ # allow us to encapsulate this __context__ fixup.
723
+ old_context = remaining_error_after_cancel_scope.__context__
724
+ try:
725
+ raise remaining_error_after_cancel_scope
726
+ finally:
727
+ _, value, _ = sys.exc_info()
728
+ assert value is remaining_error_after_cancel_scope
729
+ value.__context__ = old_context
730
+ # delete references from locals to avoid creating cycles
731
+ # see test_cancel_scope_exit_doesnt_create_cyclic_garbage
732
+ # Note: still relevant
733
+ del remaining_error_after_cancel_scope, value, _, exc
734
+
735
+ def __repr__(self) -> str:
736
+ if self._cancel_status is not None:
737
+ binding = "active"
738
+ elif self._has_been_entered:
739
+ binding = "exited"
740
+ else:
741
+ binding = "unbound"
742
+
743
+ if self._cancel_called:
744
+ state = ", cancelled"
745
+ elif self._deadline == inf:
746
+ state = ""
747
+ else:
748
+ try:
749
+ now = current_time()
750
+ except RuntimeError: # must be called from async context
751
+ state = ""
752
+ else:
753
+ state = ", deadline is {:.2f} seconds {}".format(
754
+ abs(self._deadline - now),
755
+ "from now" if self._deadline >= now else "ago",
756
+ )
757
+
758
+ return f"<trio.CancelScope at {id(self):#x}, {binding}{state}>"
759
+
760
+ @contextmanager
761
+ @enable_ki_protection
762
+ def _might_change_registered_deadline(self) -> Iterator[None]:
763
+ try:
764
+ yield
765
+ finally:
766
+ old = self._registered_deadline
767
+ if self._cancel_status is None or self._cancel_called:
768
+ new = inf
769
+ else:
770
+ new = self._deadline
771
+ if old != new:
772
+ self._registered_deadline = new
773
+ runner = GLOBAL_RUN_CONTEXT.runner
774
+ if runner.is_guest:
775
+ old_next_deadline = runner.deadlines.next_deadline()
776
+ if old != inf:
777
+ runner.deadlines.remove(old, self)
778
+ if new != inf:
779
+ runner.deadlines.add(new, self)
780
+ if runner.is_guest:
781
+ new_next_deadline = runner.deadlines.next_deadline()
782
+ if old_next_deadline != new_next_deadline:
783
+ runner.force_guest_tick_asap()
784
+
785
+ @property
786
+ def deadline(self) -> float:
787
+ """Read-write, :class:`float`. An absolute time on the current
788
+ run's clock at which this scope will automatically become
789
+ cancelled. You can adjust the deadline by modifying this
790
+ attribute, e.g.::
791
+
792
+ # I need a little more time!
793
+ cancel_scope.deadline += 30
794
+
795
+ Note that for efficiency, the core run loop only checks for
796
+ expired deadlines every once in a while. This means that in
797
+ certain cases there may be a short delay between when the clock
798
+ says the deadline should have expired, and when checkpoints
799
+ start raising :exc:`~trio.Cancelled`. This is a very obscure
800
+ corner case that you're unlikely to notice, but we document it
801
+ for completeness. (If this *does* cause problems for you, of
802
+ course, then `we want to know!
803
+ <https://github.com/python-trio/trio/issues>`__)
804
+
805
+ Defaults to :data:`math.inf`, which means "no deadline", though
806
+ this can be overridden by the ``deadline=`` argument to
807
+ the :class:`~trio.CancelScope` constructor.
808
+ """
809
+ if self._relative_deadline != inf:
810
+ assert self._deadline == inf
811
+ warnings.warn(
812
+ DeprecationWarning(
813
+ "unentered relative cancel scope does not have an absolute deadline. Use `.relative_deadline`",
814
+ ),
815
+ stacklevel=2,
816
+ )
817
+ return current_time() + self._relative_deadline
818
+ return self._deadline
819
+
820
+ @deadline.setter
821
+ def deadline(self, new_deadline: float) -> None:
822
+ if isnan(new_deadline):
823
+ raise ValueError("deadline must not be NaN")
824
+ if self._relative_deadline != inf:
825
+ assert self._deadline == inf
826
+ warnings.warn(
827
+ DeprecationWarning(
828
+ "unentered relative cancel scope does not have an absolute deadline. Transforming into an absolute cancel scope. First set `.relative_deadline = math.inf` if you do want an absolute cancel scope.",
829
+ ),
830
+ stacklevel=2,
831
+ )
832
+ self._relative_deadline = inf
833
+ with self._might_change_registered_deadline():
834
+ self._deadline = float(new_deadline)
835
+
836
+ @property
837
+ def relative_deadline(self) -> float:
838
+ """Read-write, :class:`float`. The number of seconds remaining until this
839
+ scope's deadline, relative to the current time.
840
+
841
+ Defaults to :data:`math.inf` ("no deadline"). Must be non-negative.
842
+
843
+ When modified
844
+ Before entering: sets the deadline relative to when the scope enters.
845
+ After entering: sets a new deadline relative to the current time.
846
+
847
+ Raises:
848
+ RuntimeError: if trying to read or modify an unentered scope with an absolute deadline, i.e. when :attr:`is_relative` is ``False``.
849
+ """
850
+ if self._has_been_entered:
851
+ return self._deadline - current_time()
852
+ elif self._deadline != inf:
853
+ assert self._relative_deadline == inf
854
+ raise RuntimeError(
855
+ "unentered non-relative cancel scope does not have a relative deadline",
856
+ )
857
+ return self._relative_deadline
858
+
859
+ @relative_deadline.setter
860
+ def relative_deadline(self, new_relative_deadline: float) -> None:
861
+ if isnan(new_relative_deadline):
862
+ raise ValueError("relative deadline must not be NaN")
863
+ if new_relative_deadline < 0:
864
+ raise ValueError("relative deadline must be non-negative")
865
+ if self._has_been_entered:
866
+ with self._might_change_registered_deadline():
867
+ self._deadline = current_time() + float(new_relative_deadline)
868
+ elif self._deadline != inf:
869
+ assert self._relative_deadline == inf
870
+ raise RuntimeError(
871
+ "unentered non-relative cancel scope does not have a relative deadline",
872
+ )
873
+ else:
874
+ self._relative_deadline = new_relative_deadline
875
+
876
+ @property
877
+ def is_relative(self) -> bool | None:
878
+ """Returns None after entering. Returns False if both deadline and
879
+ relative_deadline are inf."""
880
+ assert not (self._deadline != inf and self._relative_deadline != inf)
881
+ if self._has_been_entered:
882
+ return None
883
+ return self._relative_deadline != inf
884
+
885
+ @property
886
+ def shield(self) -> bool:
887
+ """Read-write, :class:`bool`, default :data:`False`. So long as
888
+ this is set to :data:`True`, then the code inside this scope
889
+ will not receive :exc:`~trio.Cancelled` exceptions from scopes
890
+ that are outside this scope. They can still receive
891
+ :exc:`~trio.Cancelled` exceptions from (1) this scope, or (2)
892
+ scopes inside this scope. You can modify this attribute::
893
+
894
+ with trio.CancelScope() as cancel_scope:
895
+ cancel_scope.shield = True
896
+ # This cannot be interrupted by any means short of
897
+ # killing the process:
898
+ await sleep(10)
899
+
900
+ cancel_scope.shield = False
901
+ # Now this can be cancelled normally:
902
+ await sleep(10)
903
+
904
+ Defaults to :data:`False`, though this can be overridden by the
905
+ ``shield=`` argument to the :class:`~trio.CancelScope` constructor.
906
+ """
907
+ return self._shield
908
+
909
+ @shield.setter
910
+ @enable_ki_protection
911
+ def shield(self, new_value: bool) -> None:
912
+ if not isinstance(new_value, bool):
913
+ raise TypeError("shield must be a bool")
914
+ self._shield = new_value
915
+ if self._cancel_status is not None:
916
+ self._cancel_status.recalculate()
917
+
918
+ @enable_ki_protection
919
+ def _cancel(self, cancel_reason: CancelReason | None) -> None:
920
+ """Internal sources of cancellation should use this instead of :meth:`cancel`
921
+ in order to set a more detailed :class:`CancelReason`
922
+ Helper or high-level functions can use `cancel`.
923
+ """
924
+ if self._cancel_called:
925
+ return
926
+
927
+ if self._cancel_reason is None:
928
+ self._cancel_reason = cancel_reason
929
+
930
+ with self._might_change_registered_deadline():
931
+ self._cancel_called = True
932
+
933
+ if self._cancel_status is not None:
934
+ self._cancel_status.recalculate()
935
+
936
+ @enable_ki_protection
937
+ def cancel(self, reason: str | None = None) -> None:
938
+ """Cancels this scope immediately.
939
+
940
+ The optional ``reason`` argument accepts a string, which will be attached to
941
+ any resulting :exc:`Cancelled` exception to help you understand where that
942
+ cancellation is coming from and why it happened.
943
+
944
+ This method is idempotent, i.e., if the scope was already
945
+ cancelled then this method silently does nothing.
946
+ """
947
+ try:
948
+ current_task = repr(_core.current_task())
949
+ except RuntimeError:
950
+ current_task = None
951
+ self._cancel(
952
+ CancelReason(reason=reason, source="explicit", source_task=current_task)
953
+ )
954
+
955
+ @property
956
+ def cancel_called(self) -> bool:
957
+ """Readonly :class:`bool`. Records whether cancellation has been
958
+ requested for this scope, either by an explicit call to
959
+ :meth:`cancel` or by the deadline expiring.
960
+
961
+ This attribute being True does *not* necessarily mean that the
962
+ code within the scope has been, or will be, affected by the
963
+ cancellation. For example, if :meth:`cancel` was called after
964
+ the last checkpoint in the ``with`` block, when it's too late to
965
+ deliver a :exc:`~trio.Cancelled` exception, then this attribute
966
+ will still be True.
967
+
968
+ This attribute is mostly useful for debugging and introspection.
969
+ If you want to know whether or not a chunk of code was actually
970
+ cancelled, then :attr:`cancelled_caught` is usually more
971
+ appropriate.
972
+ """
973
+ if ( # noqa: SIM102 # collapsible-if but this way is nicer
974
+ self._cancel_status is not None or not self._has_been_entered
975
+ ):
976
+ # Scope is active or not yet entered: make sure cancel_called
977
+ # is true if the deadline has passed. This shouldn't
978
+ # be able to actually change behavior, since we check for
979
+ # deadline expiry on scope entry and at every checkpoint,
980
+ # but it makes the value returned by cancel_called more
981
+ # closely match expectations.
982
+ if not self._cancel_called and current_time() >= self._deadline:
983
+ self._cancel(CancelReason(source="deadline"))
984
+ return self._cancel_called
985
+
986
+
987
+ ################################################################
988
+ # Nursery and friends
989
+ ################################################################
990
+
991
+
992
+ class TaskStatus(Protocol[StatusT_contra]):
993
+ """The interface provided by :meth:`Nursery.start()` to the spawned task.
994
+
995
+ This is provided via the ``task_status`` keyword-only parameter.
996
+ """
997
+
998
+ @overload
999
+ def started(self: TaskStatus[None]) -> None: ...
1000
+
1001
+ @overload
1002
+ def started(self, value: StatusT_contra) -> None: ...
1003
+
1004
+ def started(self, value: StatusT_contra | None = None) -> None:
1005
+ """Tasks call this method to indicate that they have initialized.
1006
+
1007
+ See `nursery.start() <trio.Nursery.start>` for more information.
1008
+ """
1009
+
1010
+
1011
+ # This code needs to be read alongside the code from Nursery.start to make
1012
+ # sense.
1013
+ @attrs.define(eq=False, repr=False, slots=False)
1014
+ class _TaskStatus(TaskStatus[StatusT]):
1015
+ _old_nursery: Nursery
1016
+ _new_nursery: Nursery
1017
+ # NoStatus is a sentinel.
1018
+ _value: StatusT | type[_NoStatus] = _NoStatus
1019
+
1020
+ def __repr__(self) -> str:
1021
+ return f"<Task status object at {id(self):#x}>"
1022
+
1023
+ @overload
1024
+ def started(self: _TaskStatus[None]) -> None: ...
1025
+
1026
+ @overload
1027
+ def started(self: _TaskStatus[StatusT], value: StatusT) -> None: ...
1028
+
1029
+ def started(self, value: StatusT | None = None) -> None:
1030
+ if self._value is not _NoStatus:
1031
+ raise RuntimeError("called 'started' twice on the same task status")
1032
+ self._value = cast("StatusT", value) # If None, StatusT == None
1033
+
1034
+ # If the old nursery is cancelled, then quietly quit now; the child
1035
+ # will eventually exit on its own, and we don't want to risk moving
1036
+ # children that might have propagating Cancelled exceptions into
1037
+ # a place with no cancelled cancel scopes to catch them.
1038
+ assert self._old_nursery._cancel_status is not None
1039
+ if self._old_nursery._cancel_status.effectively_cancelled:
1040
+ return
1041
+
1042
+ # Can't be closed, b/c we checked in start() and then _pending_starts
1043
+ # should keep it open.
1044
+ assert not self._new_nursery._closed
1045
+
1046
+ # Move tasks from the old nursery to the new
1047
+ tasks = self._old_nursery._children
1048
+ self._old_nursery._children = set()
1049
+ for task in tasks:
1050
+ task._parent_nursery = self._new_nursery
1051
+ task._eventual_parent_nursery = None
1052
+ self._new_nursery._children.add(task)
1053
+
1054
+ # Move all children of the old nursery's cancel status object
1055
+ # to be underneath the new nursery instead. This includes both
1056
+ # tasks and child cancel status objects.
1057
+ # NB: If the new nursery is cancelled, reparenting a cancel
1058
+ # status to be underneath it can invoke an abort_fn, which might
1059
+ # do something evil like cancel the old nursery. We thus break
1060
+ # everything off from the old nursery before we start attaching
1061
+ # anything to the new.
1062
+ cancel_status_children = self._old_nursery._cancel_status.children
1063
+ cancel_status_tasks = set(self._old_nursery._cancel_status.tasks)
1064
+ cancel_status_tasks.discard(self._old_nursery._parent_task)
1065
+ for cancel_status in cancel_status_children:
1066
+ cancel_status.parent = None
1067
+ for task in cancel_status_tasks:
1068
+ task._activate_cancel_status(None)
1069
+ for cancel_status in cancel_status_children:
1070
+ cancel_status.parent = self._new_nursery._cancel_status
1071
+ for task in cancel_status_tasks:
1072
+ task._activate_cancel_status(self._new_nursery._cancel_status)
1073
+
1074
+ # That should have removed all the children from the old nursery
1075
+ assert not self._old_nursery._children
1076
+
1077
+ # And finally, poke the old nursery so it notices that all its
1078
+ # children have disappeared and can exit.
1079
+ self._old_nursery._check_nursery_closed()
1080
+
1081
+
1082
+ @attrs.define(slots=False)
1083
+ class NurseryManager:
1084
+ """Nursery context manager.
1085
+
1086
+ Note we explicitly avoid @asynccontextmanager and @async_generator
1087
+ since they add a lot of extraneous stack frames to exceptions, as
1088
+ well as cause problematic behavior with handling of StopIteration
1089
+ and StopAsyncIteration.
1090
+
1091
+ """
1092
+
1093
+ strict_exception_groups: bool = True
1094
+
1095
+ @enable_ki_protection
1096
+ async def __aenter__(self) -> Nursery:
1097
+ self._scope = CancelScope()
1098
+ self._scope.__enter__()
1099
+ self._nursery = Nursery._create(
1100
+ current_task(),
1101
+ self._scope,
1102
+ self.strict_exception_groups,
1103
+ )
1104
+ return self._nursery
1105
+
1106
+ @enable_ki_protection
1107
+ async def __aexit__(
1108
+ self,
1109
+ etype: type[BaseException] | None,
1110
+ exc: BaseException | None,
1111
+ tb: TracebackType | None,
1112
+ ) -> bool:
1113
+ new_exc = await self._nursery._nested_child_finished(exc)
1114
+ # Tracebacks show the 'raise' line below out of context, so let's give
1115
+ # this variable a name that makes sense out of context.
1116
+ combined_error_from_nursery = self._scope._close(new_exc)
1117
+ if combined_error_from_nursery is None:
1118
+ return True
1119
+ elif combined_error_from_nursery is exc:
1120
+ return False
1121
+ else:
1122
+ # Copied verbatim from the old MultiErrorCatcher. Python doesn't
1123
+ # allow us to encapsulate this __context__ fixup.
1124
+ old_context = combined_error_from_nursery.__context__
1125
+ try:
1126
+ raise combined_error_from_nursery
1127
+ finally:
1128
+ _, value, _ = sys.exc_info()
1129
+ assert value is combined_error_from_nursery
1130
+ value.__context__ = old_context
1131
+ # delete references from locals to avoid creating cycles
1132
+ # see test_cancel_scope_exit_doesnt_create_cyclic_garbage
1133
+ del _, combined_error_from_nursery, value, new_exc
1134
+
1135
+ # make sure these raise errors in static analysis if called
1136
+ if not TYPE_CHECKING:
1137
+
1138
+ def __enter__(self) -> NoReturn:
1139
+ raise RuntimeError(
1140
+ "use 'async with open_nursery(...)', not 'with open_nursery(...)'",
1141
+ )
1142
+
1143
+ def __exit__(
1144
+ self,
1145
+ exc_type: type[BaseException] | None,
1146
+ exc_value: BaseException | None,
1147
+ traceback: TracebackType | None,
1148
+ ) -> NoReturn: # pragma: no cover
1149
+ raise AssertionError("Never called, but should be defined")
1150
+
1151
+
1152
+ def open_nursery(
1153
+ strict_exception_groups: bool | None = None,
1154
+ ) -> AbstractAsyncContextManager[Nursery]:
1155
+ """Returns an async context manager which must be used to create a
1156
+ new `Nursery`.
1157
+
1158
+ It does not block on entry; on exit it blocks until all child tasks
1159
+ have exited. If no child tasks are running on exit, it will insert a
1160
+ schedule point (but no cancellation point) - equivalent to
1161
+ :func:`trio.lowlevel.cancel_shielded_checkpoint`. This means a nursery
1162
+ is never the source of a cancellation exception, it only propagates it
1163
+ from sub-tasks.
1164
+
1165
+ Args:
1166
+ strict_exception_groups (bool): Unless set to False, even a single raised exception
1167
+ will be wrapped in an exception group. If not specified, uses the value passed
1168
+ to :func:`run`, which defaults to true. Setting it to False will be deprecated
1169
+ and ultimately removed in a future version of Trio.
1170
+
1171
+ """
1172
+ # only warn if explicitly set to falsy, not if we get it from the global context.
1173
+ if strict_exception_groups is not None and not strict_exception_groups:
1174
+ warn_deprecated(
1175
+ "open_nursery(strict_exception_groups=False)",
1176
+ version="0.25.0",
1177
+ issue=2929,
1178
+ instead=(
1179
+ "the default value of True and rewrite exception handlers to handle ExceptionGroups. "
1180
+ "See https://trio.readthedocs.io/en/stable/reference-core.html#designing-for-multiple-errors"
1181
+ ),
1182
+ use_triodeprecationwarning=True,
1183
+ )
1184
+
1185
+ if strict_exception_groups is None:
1186
+ strict_exception_groups = GLOBAL_RUN_CONTEXT.runner.strict_exception_groups
1187
+
1188
+ return NurseryManager(strict_exception_groups=strict_exception_groups)
1189
+
1190
+
1191
+ @final
1192
+ class Nursery(metaclass=NoPublicConstructor):
1193
+ """A context which may be used to spawn (or cancel) child tasks.
1194
+
1195
+ Not constructed directly, use `open_nursery` instead.
1196
+
1197
+ The nursery will remain open until all child tasks have completed,
1198
+ or until it is cancelled, at which point it will cancel all its
1199
+ remaining child tasks and close.
1200
+
1201
+ Nurseries ensure the absence of orphaned Tasks, since all running
1202
+ tasks will belong to an open Nursery.
1203
+
1204
+ Attributes:
1205
+ cancel_scope:
1206
+ Creating a nursery also implicitly creates a cancellation scope,
1207
+ which is exposed as the :attr:`cancel_scope` attribute. This is
1208
+ used internally to implement the logic where if an error occurs
1209
+ then ``__aexit__`` cancels all children, but you can use it for
1210
+ other things, e.g. if you want to explicitly cancel all children
1211
+ in response to some external event.
1212
+ """
1213
+
1214
+ def __init__(
1215
+ self,
1216
+ parent_task: Task,
1217
+ cancel_scope: CancelScope,
1218
+ strict_exception_groups: bool,
1219
+ ) -> None:
1220
+ self._parent_task = parent_task
1221
+ self._strict_exception_groups = strict_exception_groups
1222
+ parent_task._child_nurseries.append(self)
1223
+ # the cancel status that children inherit - we take a snapshot, so it
1224
+ # won't be affected by any changes in the parent.
1225
+ self._cancel_status = parent_task._cancel_status
1226
+ # the cancel scope that directly surrounds us; used for cancelling all
1227
+ # children.
1228
+ self.cancel_scope = cancel_scope
1229
+ assert self.cancel_scope._cancel_status is self._cancel_status
1230
+ self._children: set[Task] = set()
1231
+ self._pending_excs: list[BaseException] = []
1232
+ # The "nested child" is how this code refers to the contents of the
1233
+ # nursery's 'async with' block, which acts like a child Task in all
1234
+ # the ways we can make it.
1235
+ self._nested_child_running = True
1236
+ self._parent_waiting_in_aexit = False
1237
+ self._pending_starts = 0
1238
+ self._closed = False
1239
+
1240
+ @property
1241
+ def child_tasks(self) -> frozenset[Task]:
1242
+ """(`frozenset`): Contains all the child :class:`~trio.lowlevel.Task`
1243
+ objects which are still running."""
1244
+ return frozenset(self._children)
1245
+
1246
+ @property
1247
+ def parent_task(self) -> Task:
1248
+ "(`~trio.lowlevel.Task`): The Task that opened this nursery."
1249
+ return self._parent_task
1250
+
1251
+ def _add_exc(self, exc: BaseException, reason: CancelReason | None) -> None:
1252
+ self._pending_excs.append(exc)
1253
+ self.cancel_scope._cancel(reason)
1254
+
1255
+ def _check_nursery_closed(self) -> None:
1256
+ if not any([self._nested_child_running, self._children, self._pending_starts]):
1257
+ self._closed = True
1258
+ if self._parent_waiting_in_aexit:
1259
+ self._parent_waiting_in_aexit = False
1260
+ GLOBAL_RUN_CONTEXT.runner.reschedule(self._parent_task)
1261
+
1262
+ def _child_finished(
1263
+ self,
1264
+ task: Task,
1265
+ outcome: Outcome[object],
1266
+ ) -> None:
1267
+ self._children.remove(task)
1268
+ if self._closed and not hasattr(self, "_pending_excs"):
1269
+ # We're abandoned by misnested nurseries, the result of the task is lost.
1270
+ return
1271
+ if isinstance(outcome, Error):
1272
+ self._add_exc(
1273
+ outcome.error,
1274
+ CancelReason(
1275
+ source="nursery",
1276
+ source_task=repr(task),
1277
+ reason=f"child task raised exception {outcome.error!r}",
1278
+ ),
1279
+ )
1280
+ self._check_nursery_closed()
1281
+
1282
+ async def _nested_child_finished(
1283
+ self,
1284
+ nested_child_exc: BaseException | None,
1285
+ ) -> BaseException | None:
1286
+ # Returns ExceptionGroup instance (or any exception if the nursery is in loose mode
1287
+ # and there is just one contained exception) if there are pending exceptions
1288
+ if nested_child_exc is not None:
1289
+ self._add_exc(
1290
+ nested_child_exc,
1291
+ reason=CancelReason(
1292
+ source="nursery",
1293
+ source_task=repr(self._parent_task),
1294
+ reason=f"Code block inside nursery contextmanager raised exception {nested_child_exc!r}",
1295
+ ),
1296
+ )
1297
+ self._nested_child_running = False
1298
+ self._check_nursery_closed()
1299
+
1300
+ if not self._closed:
1301
+ # If we have a KeyboardInterrupt injected, we want to save it in
1302
+ # the nursery's final exceptions list. But if it's just a
1303
+ # Cancelled, then we don't -- see gh-1457.
1304
+ def aborted(raise_cancel: _core.RaiseCancelT) -> Abort:
1305
+ exn = capture(raise_cancel).error
1306
+ if not isinstance(exn, Cancelled):
1307
+ self._add_exc(
1308
+ exn,
1309
+ CancelReason(
1310
+ source="KeyboardInterrupt",
1311
+ source_task=repr(self._parent_task),
1312
+ ),
1313
+ )
1314
+ # see test_cancel_scope_exit_doesnt_create_cyclic_garbage
1315
+ del exn # prevent cyclic garbage creation
1316
+ return Abort.FAILED
1317
+
1318
+ self._parent_waiting_in_aexit = True
1319
+ await wait_task_rescheduled(aborted)
1320
+ else:
1321
+ # Nothing to wait for, so execute a schedule point, but don't
1322
+ # allow us to be cancelled, just like the other branch. We
1323
+ # still need to catch and store non-Cancelled exceptions.
1324
+ try:
1325
+ await cancel_shielded_checkpoint()
1326
+ except BaseException as exc:
1327
+ # there's no children to cancel, so don't need to supply cancel reason
1328
+ self._add_exc(exc, reason=None)
1329
+
1330
+ popped = self._parent_task._child_nurseries.pop()
1331
+ assert popped is self, "Nursery misnesting detected!"
1332
+ if self._pending_excs:
1333
+ try:
1334
+ if not self._strict_exception_groups and len(self._pending_excs) == 1:
1335
+ return self._pending_excs[0]
1336
+ exception = BaseExceptionGroup(
1337
+ "Exceptions from Trio nursery",
1338
+ self._pending_excs,
1339
+ )
1340
+ if not self._strict_exception_groups:
1341
+ exception.add_note(NONSTRICT_EXCEPTIONGROUP_NOTE)
1342
+ return exception
1343
+ finally:
1344
+ # avoid a garbage cycle
1345
+ # (see test_locals_destroyed_promptly_on_cancel)
1346
+ del self._pending_excs
1347
+ return None
1348
+
1349
+ def start_soon(
1350
+ self,
1351
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]],
1352
+ *args: Unpack[PosArgT],
1353
+ name: object = None,
1354
+ ) -> None:
1355
+ """Creates a child task, scheduling ``await async_fn(*args)``.
1356
+
1357
+ If you want to run a function and immediately wait for its result,
1358
+ then you don't need a nursery; just use ``await async_fn(*args)``.
1359
+ If you want to wait for the task to initialize itself before
1360
+ continuing, see :meth:`start`, the other fundamental method for
1361
+ creating concurrent tasks in Trio.
1362
+
1363
+ Note that this is *not* an async function and you don't use await
1364
+ when calling it. It sets up the new task, but then returns
1365
+ immediately, *before* the new task has a chance to do anything.
1366
+ New tasks may start running in any order, and at any checkpoint the
1367
+ scheduler chooses - at latest when the nursery is waiting to exit.
1368
+
1369
+ It's possible to pass a nursery object into another task, which
1370
+ allows that task to start new child tasks in the first task's
1371
+ nursery.
1372
+
1373
+ The child task inherits its parent nursery's cancel scopes.
1374
+
1375
+ Args:
1376
+ async_fn: An async callable.
1377
+ args: Positional arguments for ``async_fn``. If you want
1378
+ to pass keyword arguments, use
1379
+ :func:`functools.partial`.
1380
+ name: The name for this task. Only used for
1381
+ debugging/introspection
1382
+ (e.g. ``repr(task_obj)``). If this isn't a string,
1383
+ :meth:`start_soon` will try to make it one. A
1384
+ common use case is if you're wrapping a function
1385
+ before spawning a new task, you might pass the
1386
+ original function as the ``name=`` to make
1387
+ debugging easier.
1388
+
1389
+ Raises:
1390
+ RuntimeError: If this nursery is no longer open
1391
+ (i.e. its ``async with`` block has
1392
+ exited).
1393
+ """
1394
+ GLOBAL_RUN_CONTEXT.runner.spawn_impl(async_fn, args, self, name)
1395
+
1396
+ # Typing changes blocked by https://github.com/python/mypy/pull/17512
1397
+ async def start( # type: ignore[explicit-any]
1398
+ self,
1399
+ async_fn: Callable[..., Awaitable[object]],
1400
+ *args: object,
1401
+ name: object = None,
1402
+ ) -> Any:
1403
+ r"""Creates and initializes a child task.
1404
+
1405
+ Like :meth:`start_soon`, but blocks until the new task has
1406
+ finished initializing itself, and optionally returns some
1407
+ information from it.
1408
+
1409
+ The ``async_fn`` must accept a ``task_status`` keyword argument,
1410
+ and it must make sure that it (or someone) eventually calls
1411
+ :meth:`task_status.started() <TaskStatus.started>`.
1412
+
1413
+ The conventional way to define ``async_fn`` is like::
1414
+
1415
+ async def async_fn(arg1, arg2, *, task_status=trio.TASK_STATUS_IGNORED):
1416
+ ... # Caller is blocked waiting for this code to run
1417
+ task_status.started()
1418
+ ... # This async code can be interleaved with the caller
1419
+
1420
+ :attr:`trio.TASK_STATUS_IGNORED` is a special global object with
1421
+ a do-nothing ``started`` method. This way your function supports
1422
+ being called either like ``await nursery.start(async_fn, arg1,
1423
+ arg2)`` or directly like ``await async_fn(arg1, arg2)``, and
1424
+ either way it can call :meth:`task_status.started() <TaskStatus.started>`
1425
+ without worrying about which mode it's in. Defining your function like
1426
+ this will make it obvious to readers that it supports being used
1427
+ in both modes.
1428
+
1429
+ Before the child calls :meth:`task_status.started() <TaskStatus.started>`,
1430
+ it's effectively run underneath the call to :meth:`start`: if it
1431
+ raises an exception then that exception is reported by
1432
+ :meth:`start`, and does *not* propagate out of the nursery. If
1433
+ :meth:`start` is cancelled, then the child task is also
1434
+ cancelled.
1435
+
1436
+ When the child calls :meth:`task_status.started() <TaskStatus.started>`,
1437
+ it's moved out from underneath :meth:`start` and into the given nursery.
1438
+
1439
+ If the child task passes a value to :meth:`task_status.started(value) <TaskStatus.started>`,
1440
+ then :meth:`start` returns this value. Otherwise, it returns ``None``.
1441
+ """
1442
+ if self._closed:
1443
+ raise RuntimeError("Nursery is closed to new arrivals")
1444
+ try:
1445
+ self._pending_starts += 1
1446
+ # wrap internal nursery in try-except to unroll any exceptiongroups
1447
+ # to avoid wrapping pre-started() exceptions in an extra ExceptionGroup.
1448
+ # See #2611.
1449
+ try:
1450
+ # set strict_exception_groups = True to make sure we always unwrap
1451
+ # *this* nursery's exceptiongroup
1452
+ async with open_nursery(strict_exception_groups=True) as old_nursery:
1453
+ task_status: _TaskStatus[object | None] = _TaskStatus(
1454
+ old_nursery,
1455
+ self,
1456
+ )
1457
+ thunk = functools.partial(async_fn, task_status=task_status)
1458
+ task = GLOBAL_RUN_CONTEXT.runner.spawn_impl(
1459
+ thunk,
1460
+ args,
1461
+ old_nursery,
1462
+ name,
1463
+ )
1464
+ task._eventual_parent_nursery = self
1465
+ # Wait for either TaskStatus.started or an exception to
1466
+ # cancel this nursery:
1467
+ except BaseExceptionGroup as exc:
1468
+ if len(exc.exceptions) == 1:
1469
+ raise exc.exceptions[0] from None
1470
+ raise TrioInternalError(
1471
+ "Internal nursery should not have multiple tasks. This can be "
1472
+ 'caused by the user managing to access the "old" nursery in '
1473
+ "`task_status` and spawning tasks in it.",
1474
+ ) from exc
1475
+
1476
+ # If we get here, then the child either got reparented or exited
1477
+ # normally. The complicated logic is all in TaskStatus.started().
1478
+ # (Any exceptions propagate directly out of the above.)
1479
+ if task_status._value is _NoStatus:
1480
+ raise RuntimeError("child exited without calling task_status.started()")
1481
+ return task_status._value
1482
+ finally:
1483
+ self._pending_starts -= 1
1484
+ self._check_nursery_closed()
1485
+
1486
+ def __del__(self) -> None:
1487
+ assert not self._children
1488
+
1489
+
1490
+ ################################################################
1491
+ # Task and friends
1492
+ ################################################################
1493
+
1494
+
1495
+ @final
1496
+ @attrs.define(eq=False, repr=False)
1497
+ class Task(metaclass=NoPublicConstructor): # type: ignore[explicit-any]
1498
+ _parent_nursery: Nursery | None
1499
+ coro: types.CoroutineType[Any, Outcome[object], Any] # type: ignore[explicit-any]
1500
+ _runner: Runner
1501
+ name: str
1502
+ context: contextvars.Context
1503
+ _counter: int = attrs.field(init=False, factory=itertools.count().__next__)
1504
+ _ki_protected: bool
1505
+
1506
+ # Invariant:
1507
+ # - for unscheduled tasks, _next_send_fn and _next_send are both None
1508
+ # - for scheduled tasks, _next_send_fn(_next_send) resumes the task;
1509
+ # usually _next_send_fn is self.coro.send and _next_send is an
1510
+ # Outcome. When recovering from a foreign await, _next_send_fn is
1511
+ # self.coro.throw and _next_send is an exception. _next_send_fn
1512
+ # will effectively be at the top of every task's call stack, so
1513
+ # it should be written in C if you don't want to pollute Trio
1514
+ # tracebacks with extraneous frames.
1515
+ # - for scheduled tasks, custom_sleep_data is None
1516
+ # Tasks start out unscheduled.
1517
+ _next_send_fn: Callable[[Any], object] | None = None # type: ignore[explicit-any]
1518
+ _next_send: Outcome[Any] | BaseException | None = None # type: ignore[explicit-any]
1519
+ _abort_func: Callable[[_core.RaiseCancelT], Abort] | None = None
1520
+ custom_sleep_data: Any = None # type: ignore[explicit-any]
1521
+
1522
+ # For introspection and nursery.start()
1523
+ _child_nurseries: list[Nursery] = attrs.Factory(list)
1524
+ _eventual_parent_nursery: Nursery | None = None
1525
+
1526
+ # these are counts of how many cancel/schedule points this task has
1527
+ # executed, for assert{_no,}_checkpoints
1528
+ # XX maybe these should be exposed as part of a statistics() method?
1529
+ _cancel_points: int = 0
1530
+ _schedule_points: int = 0
1531
+
1532
+ def __repr__(self) -> str:
1533
+ return f"<Task {self.name!r} at {id(self):#x}>"
1534
+
1535
+ @property
1536
+ def parent_nursery(self) -> Nursery | None:
1537
+ """The nursery this task is inside (or None if this is the "init"
1538
+ task).
1539
+
1540
+ Example use case: drawing a visualization of the task tree in a
1541
+ debugger.
1542
+
1543
+ """
1544
+ return self._parent_nursery
1545
+
1546
+ @property
1547
+ def eventual_parent_nursery(self) -> Nursery | None:
1548
+ """The nursery this task will be inside after it calls
1549
+ ``task_status.started()``.
1550
+
1551
+ If this task has already called ``started()``, or if it was not
1552
+ spawned using `nursery.start() <trio.Nursery.start>`, then
1553
+ its `eventual_parent_nursery` is ``None``.
1554
+
1555
+ """
1556
+ return self._eventual_parent_nursery
1557
+
1558
+ @property
1559
+ def child_nurseries(self) -> list[Nursery]:
1560
+ """The nurseries this task contains.
1561
+
1562
+ This is a list, with outer nurseries before inner nurseries.
1563
+
1564
+ """
1565
+ return list(self._child_nurseries)
1566
+
1567
+ def iter_await_frames(self) -> Iterator[tuple[types.FrameType, int]]:
1568
+ """Iterates recursively over the coroutine-like objects this
1569
+ task is waiting on, yielding the frame and line number at each
1570
+ frame.
1571
+
1572
+ This is similar to `traceback.walk_stack` in a synchronous
1573
+ context. Note that `traceback.walk_stack` returns frames from
1574
+ the bottom of the call stack to the top, while this function
1575
+ starts from `Task.coro <trio.lowlevel.Task.coro>` and works it
1576
+ way down.
1577
+
1578
+ Example usage: extracting a stack trace::
1579
+
1580
+ import traceback
1581
+
1582
+ def print_stack_for_task(task):
1583
+ ss = traceback.StackSummary.extract(task.iter_await_frames())
1584
+ print("".join(ss.format()))
1585
+
1586
+ """
1587
+ # Ignore static typing as we're doing lots of dynamic introspection
1588
+ coro: Any = self.coro # type: ignore[explicit-any]
1589
+ while coro is not None:
1590
+ if hasattr(coro, "cr_frame"):
1591
+ # A real coroutine
1592
+ if cr_frame := coro.cr_frame: # None if the task has finished
1593
+ yield cr_frame, cr_frame.f_lineno
1594
+ coro = coro.cr_await
1595
+ elif hasattr(coro, "gi_frame"):
1596
+ # A generator decorated with @types.coroutine
1597
+ if gi_frame := coro.gi_frame: # pragma: no branch
1598
+ yield gi_frame, gi_frame.f_lineno # pragma: no cover
1599
+ coro = coro.gi_yieldfrom
1600
+ elif coro.__class__.__name__ in [
1601
+ "async_generator_athrow",
1602
+ "async_generator_asend",
1603
+ ]:
1604
+ # cannot extract the generator directly, see https://github.com/python/cpython/issues/76991
1605
+ # we can however use the gc to look through the object
1606
+ for referent in gc.get_referents(coro):
1607
+ if hasattr(referent, "ag_frame"): # pragma: no branch
1608
+ yield referent.ag_frame, referent.ag_frame.f_lineno
1609
+ coro = referent.ag_await
1610
+ break
1611
+ else: # pragma: no cover
1612
+ # either cpython changed or we are running on an alternative python implementation
1613
+ return
1614
+ else: # pragma: no cover
1615
+ return
1616
+
1617
+ ################
1618
+ # Cancellation
1619
+ ################
1620
+
1621
+ # The CancelStatus object that is currently active for this task.
1622
+ # Don't change this directly; instead, use _activate_cancel_status().
1623
+ # This can be None, but only in the init task.
1624
+ _cancel_status: CancelStatus = attrs.field(default=None, repr=False)
1625
+
1626
+ def _activate_cancel_status(self, cancel_status: CancelStatus | None) -> None:
1627
+ if self._cancel_status is not None:
1628
+ self._cancel_status._tasks.remove(self)
1629
+ self._cancel_status = cancel_status # type: ignore[assignment]
1630
+ if self._cancel_status is not None:
1631
+ self._cancel_status._tasks.add(self)
1632
+ if self._cancel_status.effectively_cancelled:
1633
+ self._attempt_delivery_of_any_pending_cancel()
1634
+
1635
+ def _attempt_abort(self, raise_cancel: _core.RaiseCancelT) -> None:
1636
+ # Either the abort succeeds, in which case we will reschedule the
1637
+ # task, or else it fails, in which case it will worry about
1638
+ # rescheduling itself (hopefully eventually calling reraise to raise
1639
+ # the given exception, but not necessarily).
1640
+
1641
+ # This is only called by the functions immediately below, which both check
1642
+ # `self.abort_func is not None`.
1643
+ assert self._abort_func is not None, "FATAL INTERNAL ERROR"
1644
+
1645
+ success = self._abort_func(raise_cancel)
1646
+ if type(success) is not Abort:
1647
+ raise TrioInternalError("abort function must return Abort enum")
1648
+ # We only attempt to abort once per blocking call, regardless of
1649
+ # whether we succeeded or failed.
1650
+ self._abort_func = None
1651
+ if success is Abort.SUCCEEDED:
1652
+ self._runner.reschedule(self, capture(raise_cancel))
1653
+
1654
+ def _attempt_delivery_of_any_pending_cancel(self) -> None:
1655
+ if self._abort_func is None:
1656
+ return
1657
+ if not self._cancel_status.effectively_cancelled:
1658
+ return
1659
+
1660
+ reason = self._cancel_status._scope._cancel_reason
1661
+
1662
+ def raise_cancel() -> NoReturn:
1663
+ if reason is None:
1664
+ raise Cancelled._create(source="unknown", reason="misnesting")
1665
+ else:
1666
+ raise Cancelled._create(
1667
+ source=reason.source,
1668
+ reason=reason.reason,
1669
+ source_task=reason.source_task,
1670
+ )
1671
+
1672
+ self._attempt_abort(raise_cancel)
1673
+
1674
+ def _attempt_delivery_of_pending_ki(self) -> None:
1675
+ assert self._runner.ki_pending
1676
+ if self._abort_func is None:
1677
+ return
1678
+
1679
+ def raise_cancel() -> NoReturn:
1680
+ self._runner.ki_pending = False
1681
+ raise KeyboardInterrupt
1682
+
1683
+ self._attempt_abort(raise_cancel)
1684
+
1685
+
1686
+ ################################################################
1687
+ # The central Runner object
1688
+ ################################################################
1689
+
1690
+
1691
+ @attrs.frozen
1692
+ class RunStatistics:
1693
+ """An object containing run-loop-level debugging information.
1694
+
1695
+ Currently, the following fields are defined:
1696
+
1697
+ * ``tasks_living`` (int): The number of tasks that have been spawned
1698
+ and not yet exited.
1699
+ * ``tasks_runnable`` (int): The number of tasks that are currently
1700
+ queued on the run queue (as opposed to blocked waiting for something
1701
+ to happen).
1702
+ * ``seconds_to_next_deadline`` (float): The time until the next
1703
+ pending cancel scope deadline. May be negative if the deadline has
1704
+ expired but we haven't yet processed cancellations. May be
1705
+ :data:`~math.inf` if there are no pending deadlines.
1706
+ * ``run_sync_soon_queue_size`` (int): The number of
1707
+ unprocessed callbacks queued via
1708
+ :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
1709
+ * ``io_statistics`` (object): Some statistics from Trio's I/O
1710
+ backend. This always has an attribute ``backend`` which is a string
1711
+ naming which operating-system-specific I/O backend is in use; the
1712
+ other attributes vary between backends.
1713
+ """
1714
+
1715
+ tasks_living: int
1716
+ tasks_runnable: int
1717
+ seconds_to_next_deadline: float
1718
+ io_statistics: IOStatistics
1719
+ run_sync_soon_queue_size: int
1720
+
1721
+
1722
+ # This holds all the state that gets trampolined back and forth between
1723
+ # callbacks when we're running in guest mode.
1724
+ #
1725
+ # It has to be a separate object from Runner, and Runner *cannot* hold
1726
+ # references to it (directly or indirectly)!
1727
+ #
1728
+ # The idea is that we want a chance to detect if our host loop quits and stops
1729
+ # driving us forward. We detect that by unrolled_run_gen being garbage
1730
+ # collected, and hitting its 'except GeneratorExit:' block. So this only
1731
+ # happens if unrolled_run_gen is GCed.
1732
+ #
1733
+ # The Runner state is referenced from the global GLOBAL_RUN_CONTEXT. The only
1734
+ # way it gets *un*referenced is by unrolled_run_gen completing, e.g. by being
1735
+ # GCed. But if Runner has a direct or indirect reference to it, and the host
1736
+ # loop has abandoned it, then this will never happen!
1737
+ #
1738
+ # So this object can reference Runner, but Runner can't reference it. The only
1739
+ # references to it are the "in flight" callback chain on the host loop /
1740
+ # worker thread.
1741
+
1742
+
1743
+ @attrs.define(eq=False)
1744
+ class GuestState: # type: ignore[explicit-any]
1745
+ runner: Runner
1746
+ run_sync_soon_threadsafe: Callable[[Callable[[], object]], object]
1747
+ run_sync_soon_not_threadsafe: Callable[[Callable[[], object]], object]
1748
+ done_callback: Callable[[Outcome[Any]], object] # type: ignore[explicit-any]
1749
+ unrolled_run_gen: Generator[float, EventResult, None]
1750
+ unrolled_run_next_send: Outcome[Any] = attrs.Factory(lambda: Value(None)) # type: ignore[explicit-any]
1751
+
1752
+ def guest_tick(self) -> None:
1753
+ prev_library, sniffio_library.name = sniffio_library.name, "trio"
1754
+ try:
1755
+ timeout = self.unrolled_run_next_send.send(self.unrolled_run_gen)
1756
+ except StopIteration:
1757
+ assert self.runner.main_task_outcome is not None
1758
+ self.done_callback(self.runner.main_task_outcome)
1759
+ return
1760
+ except TrioInternalError as exc:
1761
+ self.done_callback(Error(exc))
1762
+ return
1763
+ finally:
1764
+ sniffio_library.name = prev_library
1765
+
1766
+ # Optimization: try to skip going into the thread if we can avoid it
1767
+ events_outcome: Value[EventResult] | Error = capture(
1768
+ self.runner.io_manager.get_events,
1769
+ 0,
1770
+ )
1771
+ if timeout <= 0 or isinstance(events_outcome, Error) or events_outcome.value:
1772
+ # No need to go into the thread
1773
+ self.unrolled_run_next_send = events_outcome
1774
+ self.runner.guest_tick_scheduled = True
1775
+ self.run_sync_soon_not_threadsafe(self.guest_tick)
1776
+ else:
1777
+ # Need to go into the thread and call get_events() there
1778
+ self.runner.guest_tick_scheduled = False
1779
+
1780
+ def get_events() -> EventResult:
1781
+ return self.runner.io_manager.get_events(timeout)
1782
+
1783
+ def deliver(events_outcome: Outcome[EventResult]) -> None:
1784
+ def in_main_thread() -> None:
1785
+ self.unrolled_run_next_send = events_outcome
1786
+ self.runner.guest_tick_scheduled = True
1787
+ self.guest_tick()
1788
+
1789
+ self.run_sync_soon_threadsafe(in_main_thread)
1790
+
1791
+ start_thread_soon(get_events, deliver)
1792
+
1793
+
1794
+ @attrs.define(eq=False)
1795
+ class Runner: # type: ignore[explicit-any]
1796
+ clock: Clock
1797
+ instruments: Instruments
1798
+ io_manager: TheIOManager
1799
+ ki_manager: KIManager
1800
+ strict_exception_groups: bool
1801
+
1802
+ # Run-local values, see _local.py
1803
+ _locals: dict[_core.RunVar[Any], object] = attrs.Factory(dict) # type: ignore[explicit-any]
1804
+
1805
+ runq: deque[Task] = attrs.Factory(deque)
1806
+ tasks: set[Task] = attrs.Factory(set)
1807
+
1808
+ deadlines: Deadlines = attrs.Factory(Deadlines)
1809
+
1810
+ init_task: Task | None = None
1811
+ system_nursery: Nursery | None = None
1812
+ system_context: contextvars.Context = attrs.field(kw_only=True)
1813
+ main_task: Task | None = None
1814
+ main_task_outcome: Outcome[object] | None = None
1815
+
1816
+ entry_queue: EntryQueue = attrs.Factory(EntryQueue)
1817
+ trio_token: TrioToken | None = None
1818
+ asyncgens: AsyncGenerators = attrs.Factory(AsyncGenerators)
1819
+
1820
+ # If everything goes idle for this long, we call clock._autojump()
1821
+ clock_autojump_threshold: float = inf
1822
+
1823
+ # Guest mode stuff
1824
+ is_guest: bool = False
1825
+ guest_tick_scheduled: bool = False
1826
+
1827
+ def force_guest_tick_asap(self) -> None:
1828
+ if self.guest_tick_scheduled:
1829
+ return
1830
+ self.guest_tick_scheduled = True
1831
+ self.io_manager.force_wakeup()
1832
+
1833
+ def close(self) -> None:
1834
+ self.io_manager.close()
1835
+ self.entry_queue.close()
1836
+ self.asyncgens.close()
1837
+ if "after_run" in self.instruments:
1838
+ self.instruments.call("after_run")
1839
+ # This is where KI protection gets disabled, so we do it last
1840
+ self.ki_manager.close()
1841
+
1842
+ @_public
1843
+ def current_statistics(self) -> RunStatistics:
1844
+ """Returns ``RunStatistics``, which contains run-loop-level debugging information.
1845
+
1846
+ Currently, the following fields are defined:
1847
+
1848
+ * ``tasks_living`` (int): The number of tasks that have been spawned
1849
+ and not yet exited.
1850
+ * ``tasks_runnable`` (int): The number of tasks that are currently
1851
+ queued on the run queue (as opposed to blocked waiting for something
1852
+ to happen).
1853
+ * ``seconds_to_next_deadline`` (float): The time until the next
1854
+ pending cancel scope deadline. May be negative if the deadline has
1855
+ expired but we haven't yet processed cancellations. May be
1856
+ :data:`~math.inf` if there are no pending deadlines.
1857
+ * ``run_sync_soon_queue_size`` (int): The number of
1858
+ unprocessed callbacks queued via
1859
+ :meth:`trio.lowlevel.TrioToken.run_sync_soon`.
1860
+ * ``io_statistics`` (object): Some statistics from Trio's I/O
1861
+ backend. This always has an attribute ``backend`` which is a string
1862
+ naming which operating-system-specific I/O backend is in use; the
1863
+ other attributes vary between backends.
1864
+
1865
+ """
1866
+ seconds_to_next_deadline = self.deadlines.next_deadline() - self.current_time()
1867
+ return RunStatistics(
1868
+ tasks_living=len(self.tasks),
1869
+ tasks_runnable=len(self.runq),
1870
+ seconds_to_next_deadline=seconds_to_next_deadline,
1871
+ io_statistics=self.io_manager.statistics(),
1872
+ run_sync_soon_queue_size=self.entry_queue.size(),
1873
+ )
1874
+
1875
+ @_public
1876
+ def current_time(self) -> float:
1877
+ """Returns the current time according to Trio's internal clock.
1878
+
1879
+ Returns:
1880
+ float: The current time.
1881
+
1882
+ Raises:
1883
+ RuntimeError: if not inside a call to :func:`trio.run`.
1884
+
1885
+ """
1886
+ return self.clock.current_time()
1887
+
1888
+ @_public
1889
+ def current_clock(self) -> Clock:
1890
+ """Returns the current :class:`~trio.abc.Clock`."""
1891
+ return self.clock
1892
+
1893
+ @_public
1894
+ def current_root_task(self) -> Task | None:
1895
+ """Returns the current root :class:`Task`.
1896
+
1897
+ This is the task that is the ultimate parent of all other tasks.
1898
+
1899
+ """
1900
+ return self.init_task
1901
+
1902
+ ################
1903
+ # Core task handling primitives
1904
+ ################
1905
+
1906
+ @_public
1907
+ def reschedule(
1908
+ self, task: Task, next_send: outcome.Outcome[object] = _NO_SEND
1909
+ ) -> None:
1910
+ """Reschedule the given task with the given
1911
+ :class:`outcome.Outcome`.
1912
+
1913
+ See :func:`wait_task_rescheduled` for the gory details.
1914
+
1915
+ There must be exactly one call to :func:`reschedule` for every call to
1916
+ :func:`wait_task_rescheduled`. (And when counting, keep in mind that
1917
+ returning :data:`Abort.SUCCEEDED` from an abort callback is equivalent
1918
+ to calling :func:`reschedule` once.)
1919
+
1920
+ Args:
1921
+ task (trio.lowlevel.Task): the task to be rescheduled. Must be blocked
1922
+ in a call to :func:`wait_task_rescheduled`.
1923
+ next_send (outcome.Outcome): the value (or error) to return (or
1924
+ raise) from :func:`wait_task_rescheduled`.
1925
+
1926
+ """
1927
+ if next_send is _NO_SEND:
1928
+ next_send = Value(None)
1929
+
1930
+ assert task._runner is self
1931
+ assert task._next_send_fn is None
1932
+ task._next_send_fn = task.coro.send
1933
+ task._next_send = next_send
1934
+ task._abort_func = None
1935
+ task.custom_sleep_data = None
1936
+ if not self.runq and self.is_guest:
1937
+ self.force_guest_tick_asap()
1938
+ self.runq.append(task)
1939
+ if "task_scheduled" in self.instruments:
1940
+ self.instruments.call("task_scheduled", task)
1941
+
1942
+ def spawn_impl(
1943
+ self,
1944
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]],
1945
+ args: tuple[Unpack[PosArgT]],
1946
+ nursery: Nursery | None,
1947
+ name: object,
1948
+ *,
1949
+ system_task: bool = False,
1950
+ context: contextvars.Context | None = None,
1951
+ ) -> Task:
1952
+ ######
1953
+ # Make sure the nursery is in working order
1954
+ ######
1955
+
1956
+ # This sorta feels like it should be a method on nursery, except it
1957
+ # has to handle nursery=None for init. And it touches the internals of
1958
+ # all kinds of objects.
1959
+ if nursery is not None and nursery._closed:
1960
+ raise RuntimeError("Nursery is closed to new arrivals")
1961
+ if nursery is None:
1962
+ assert self.init_task is None
1963
+
1964
+ ######
1965
+ # Propagate contextvars
1966
+ ######
1967
+ if context is None:
1968
+ context = self.system_context.copy() if system_task else copy_context()
1969
+
1970
+ ######
1971
+ # Call the function and get the coroutine object, while giving helpful
1972
+ # errors for common mistakes.
1973
+ ######
1974
+ # TypeVarTuple passed into ParamSpec function confuses Mypy.
1975
+ coro = context.run(coroutine_or_error, async_fn, *args) # type: ignore[arg-type]
1976
+
1977
+ if name is None:
1978
+ name = async_fn
1979
+ if isinstance(name, functools.partial):
1980
+ name = name.func
1981
+ if not isinstance(name, str):
1982
+ try:
1983
+ name = f"{name.__module__}.{name.__qualname__}" # type: ignore[attr-defined]
1984
+ except AttributeError:
1985
+ name = repr(name)
1986
+
1987
+ # very old Cython versions (<0.29.24) has the attribute, but with a value of None
1988
+ if getattr(coro, "cr_frame", None) is None:
1989
+ # This async function is implemented in C or Cython
1990
+ async def python_wrapper(orig_coro: Awaitable[RetT]) -> RetT:
1991
+ return await orig_coro
1992
+
1993
+ coro = python_wrapper(coro)
1994
+ assert coro.cr_frame is not None, "Coroutine frame should exist" # type: ignore[attr-defined]
1995
+
1996
+ ######
1997
+ # Set up the Task object
1998
+ ######
1999
+ task = Task._create(
2000
+ coro=coro,
2001
+ parent_nursery=nursery,
2002
+ runner=self,
2003
+ name=name,
2004
+ context=context,
2005
+ ki_protected=system_task,
2006
+ )
2007
+
2008
+ self.tasks.add(task)
2009
+ if nursery is not None:
2010
+ nursery._children.add(task)
2011
+ task._activate_cancel_status(nursery._cancel_status)
2012
+
2013
+ if "task_spawned" in self.instruments:
2014
+ self.instruments.call("task_spawned", task)
2015
+ # Special case: normally next_send should be an Outcome, but for the
2016
+ # very first send we have to send a literal unboxed None.
2017
+ self.reschedule(task, None) # type: ignore[arg-type]
2018
+ return task
2019
+
2020
+ def task_exited(self, task: Task, outcome: Outcome[object]) -> None:
2021
+ if task._child_nurseries:
2022
+ for nursery in task._child_nurseries:
2023
+ nursery.cancel_scope._cancel(
2024
+ CancelReason(
2025
+ source="nursery",
2026
+ reason="Parent Task exited prematurely, abandoning this nursery without exiting it properly.",
2027
+ source_task=repr(task),
2028
+ )
2029
+ )
2030
+ nursery._closed = True
2031
+
2032
+ # break parking lots associated with the exiting task
2033
+ if task in GLOBAL_PARKING_LOT_BREAKER:
2034
+ for lot in GLOBAL_PARKING_LOT_BREAKER[task]:
2035
+ lot.break_lot(task)
2036
+ del GLOBAL_PARKING_LOT_BREAKER[task]
2037
+
2038
+ if (
2039
+ task._cancel_status is not None
2040
+ and task._cancel_status.abandoned_by_misnesting
2041
+ and task._cancel_status.parent is None
2042
+ ) or task._child_nurseries:
2043
+ reason = "Nursery" if task._child_nurseries else "Cancel scope"
2044
+ # The cancel scope surrounding this task's nursery was closed
2045
+ # before the task exited. Force the task to exit with an error,
2046
+ # since the error might not have been caught elsewhere. See the
2047
+ # comments in CancelStatus.close().
2048
+ try:
2049
+ # Raise this, rather than just constructing it, to get a
2050
+ # traceback frame included
2051
+ raise RuntimeError(
2052
+ f"{reason} stack corrupted: {reason} surrounding "
2053
+ f"{task!r} was closed before the task exited\n{MISNESTING_ADVICE}",
2054
+ )
2055
+ except RuntimeError as new_exc:
2056
+ if isinstance(outcome, Error):
2057
+ new_exc.__context__ = outcome.error
2058
+ outcome = Error(new_exc)
2059
+
2060
+ task._activate_cancel_status(None)
2061
+ self.tasks.remove(task)
2062
+ if task is self.init_task:
2063
+ # If the init task crashed, then something is very wrong and we
2064
+ # let the error propagate. (It'll eventually be wrapped in a
2065
+ # TrioInternalError.)
2066
+ outcome.unwrap()
2067
+ # the init task should be the last task to exit. If not, then
2068
+ # something is very wrong.
2069
+ if self.tasks: # pragma: no cover
2070
+ raise TrioInternalError
2071
+ else:
2072
+ if task is self.main_task:
2073
+ self.main_task_outcome = outcome
2074
+ outcome = Value(None)
2075
+ assert task._parent_nursery is not None, task
2076
+ task._parent_nursery._child_finished(task, outcome)
2077
+
2078
+ if "task_exited" in self.instruments:
2079
+ self.instruments.call("task_exited", task)
2080
+
2081
+ ################
2082
+ # System tasks and init
2083
+ ################
2084
+
2085
+ @_public
2086
+ def spawn_system_task(
2087
+ self,
2088
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]],
2089
+ *args: Unpack[PosArgT],
2090
+ name: object = None,
2091
+ context: contextvars.Context | None = None,
2092
+ ) -> Task:
2093
+ """Spawn a "system" task.
2094
+
2095
+ System tasks have a few differences from regular tasks:
2096
+
2097
+ * They don't need an explicit nursery; instead they go into the
2098
+ internal "system nursery".
2099
+
2100
+ * If a system task raises an exception, then it's converted into a
2101
+ :exc:`~trio.TrioInternalError` and *all* tasks are cancelled. If you
2102
+ write a system task, you should be careful to make sure it doesn't
2103
+ crash.
2104
+
2105
+ * System tasks are automatically cancelled when the main task exits.
2106
+
2107
+ * By default, system tasks have :exc:`KeyboardInterrupt` protection
2108
+ *enabled*. If you want your task to be interruptible by control-C,
2109
+ then you need to use :func:`disable_ki_protection` explicitly (and
2110
+ come up with some plan for what to do with a
2111
+ :exc:`KeyboardInterrupt`, given that system tasks aren't allowed to
2112
+ raise exceptions).
2113
+
2114
+ * System tasks do not inherit context variables from their creator.
2115
+
2116
+ Towards the end of a call to :meth:`trio.run`, after the main
2117
+ task and all system tasks have exited, the system nursery
2118
+ becomes closed. At this point, new calls to
2119
+ :func:`spawn_system_task` will raise ``RuntimeError("Nursery
2120
+ is closed to new arrivals")`` instead of creating a system
2121
+ task. It's possible to encounter this state either in
2122
+ a ``finally`` block in an async generator, or in a callback
2123
+ passed to :meth:`TrioToken.run_sync_soon` at the right moment.
2124
+
2125
+ Args:
2126
+ async_fn: An async callable.
2127
+ args: Positional arguments for ``async_fn``. If you want to pass
2128
+ keyword arguments, use :func:`functools.partial`.
2129
+ name: The name for this task. Only used for debugging/introspection
2130
+ (e.g. ``repr(task_obj)``). If this isn't a string,
2131
+ :func:`spawn_system_task` will try to make it one. A common use
2132
+ case is if you're wrapping a function before spawning a new
2133
+ task, you might pass the original function as the ``name=`` to
2134
+ make debugging easier.
2135
+ context: An optional ``contextvars.Context`` object with context variables
2136
+ to use for this task. You would normally get a copy of the current
2137
+ context with ``context = contextvars.copy_context()`` and then you would
2138
+ pass that ``context`` object here.
2139
+
2140
+ Returns:
2141
+ Task: the newly spawned task
2142
+
2143
+ """
2144
+ return self.spawn_impl(
2145
+ async_fn,
2146
+ args,
2147
+ self.system_nursery,
2148
+ name,
2149
+ system_task=True,
2150
+ context=context,
2151
+ )
2152
+
2153
+ async def init(
2154
+ self,
2155
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]],
2156
+ args: tuple[Unpack[PosArgT]],
2157
+ ) -> None:
2158
+ # run_sync_soon task runs here:
2159
+ async with open_nursery() as run_sync_soon_nursery:
2160
+ # All other system tasks run here:
2161
+ async with open_nursery() as self.system_nursery:
2162
+ # Only the main task runs here:
2163
+ async with open_nursery() as main_task_nursery:
2164
+ try:
2165
+ self.main_task = self.spawn_impl(
2166
+ async_fn,
2167
+ args,
2168
+ main_task_nursery,
2169
+ None,
2170
+ )
2171
+ except BaseException as exc:
2172
+ self.main_task_outcome = Error(exc)
2173
+ return
2174
+ self.spawn_impl(
2175
+ self.entry_queue.task,
2176
+ (),
2177
+ run_sync_soon_nursery,
2178
+ "<TrioToken.run_sync_soon task>",
2179
+ system_task=True,
2180
+ )
2181
+
2182
+ # Main task is done; start shutting down system tasks
2183
+ self.system_nursery.cancel_scope._cancel(
2184
+ CancelReason(
2185
+ source="shutdown",
2186
+ reason="main task done, shutting down system tasks",
2187
+ source_task=repr(self.init_task),
2188
+ )
2189
+ )
2190
+
2191
+ # System nursery is closed; finalize remaining async generators
2192
+ await self.asyncgens.finalize_remaining(self)
2193
+
2194
+ # There are no more asyncgens, which means no more user-provided
2195
+ # code except possibly run_sync_soon callbacks. It's finally safe
2196
+ # to stop the run_sync_soon task and exit run().
2197
+ run_sync_soon_nursery.cancel_scope._cancel(
2198
+ CancelReason(
2199
+ source="shutdown",
2200
+ reason="main task done, shutting down run_sync_soon callbacks",
2201
+ source_task=repr(self.init_task),
2202
+ )
2203
+ )
2204
+
2205
+ ################
2206
+ # Outside context problems
2207
+ ################
2208
+
2209
+ @_public
2210
+ def current_trio_token(self) -> TrioToken:
2211
+ """Retrieve the :class:`TrioToken` for the current call to
2212
+ :func:`trio.run`.
2213
+
2214
+ """
2215
+ if self.trio_token is None:
2216
+ self.trio_token = TrioToken._create(self.entry_queue)
2217
+ return self.trio_token
2218
+
2219
+ ################
2220
+ # KI handling
2221
+ ################
2222
+
2223
+ ki_pending: bool = False
2224
+
2225
+ # deliver_ki is broke. Maybe move all the actual logic and state into
2226
+ # RunToken, and we'll only have one instance per runner? But then we can't
2227
+ # have a public constructor. Eh, but current_run_token() returning a
2228
+ # unique object per run feels pretty nice. Maybe let's just go for it. And
2229
+ # keep the class public so people can isinstance() it if they want.
2230
+
2231
+ # This gets called from signal context
2232
+ def deliver_ki(self) -> None:
2233
+ self.ki_pending = True
2234
+ with suppress(RunFinishedError):
2235
+ self.entry_queue.run_sync_soon(self._deliver_ki_cb)
2236
+
2237
+ def _deliver_ki_cb(self) -> None:
2238
+ if not self.ki_pending:
2239
+ return
2240
+ # Can't happen because main_task and run_sync_soon_task are created at
2241
+ # the same time -- so even if KI arrives before main_task is created,
2242
+ # we won't get here until afterwards.
2243
+ assert self.main_task is not None
2244
+ if self.main_task_outcome is not None:
2245
+ # We're already in the process of exiting -- leave ki_pending set
2246
+ # and we'll check it again on our way out of run().
2247
+ return
2248
+ self.main_task._attempt_delivery_of_pending_ki()
2249
+
2250
+ ################
2251
+ # Quiescing
2252
+ ################
2253
+
2254
+ # sortedcontainers doesn't have types, and is reportedly very hard to type:
2255
+ # https://github.com/grantjenks/python-sortedcontainers/issues/68
2256
+ waiting_for_idle: Any = attrs.Factory(SortedDict) # type: ignore[explicit-any]
2257
+
2258
+ @_public
2259
+ async def wait_all_tasks_blocked(self, cushion: float = 0.0) -> None:
2260
+ """Block until there are no runnable tasks.
2261
+
2262
+ This is useful in testing code when you want to give other tasks a
2263
+ chance to "settle down". The calling task is blocked, and doesn't wake
2264
+ up until all other tasks are also blocked for at least ``cushion``
2265
+ seconds. (Setting a non-zero ``cushion`` is intended to handle cases
2266
+ like two tasks talking to each other over a local socket, where we
2267
+ want to ignore the potential brief moment between a send and receive
2268
+ when all tasks are blocked.)
2269
+
2270
+ Note that ``cushion`` is measured in *real* time, not the Trio clock
2271
+ time.
2272
+
2273
+ If there are multiple tasks blocked in :func:`wait_all_tasks_blocked`,
2274
+ then the one with the shortest ``cushion`` is the one woken (and
2275
+ this task becoming unblocked resets the timers for the remaining
2276
+ tasks). If there are multiple tasks that have exactly the same
2277
+ ``cushion``, then all are woken.
2278
+
2279
+ You should also consider :class:`trio.testing.Sequencer`, which
2280
+ provides a more explicit way to control execution ordering within a
2281
+ test, and will often produce more readable tests.
2282
+
2283
+ Example:
2284
+ Here's an example of one way to test that Trio's locks are fair: we
2285
+ take the lock in the parent, start a child, wait for the child to be
2286
+ blocked waiting for the lock (!), and then check that we can't
2287
+ release and immediately re-acquire the lock::
2288
+
2289
+ async def lock_taker(lock):
2290
+ await lock.acquire()
2291
+ lock.release()
2292
+
2293
+ async def test_lock_fairness():
2294
+ lock = trio.Lock()
2295
+ await lock.acquire()
2296
+ async with trio.open_nursery() as nursery:
2297
+ nursery.start_soon(lock_taker, lock)
2298
+ # child hasn't run yet, we have the lock
2299
+ assert lock.locked()
2300
+ assert lock._owner is trio.lowlevel.current_task()
2301
+ await trio.testing.wait_all_tasks_blocked()
2302
+ # now the child has run and is blocked on lock.acquire(), we
2303
+ # still have the lock
2304
+ assert lock.locked()
2305
+ assert lock._owner is trio.lowlevel.current_task()
2306
+ lock.release()
2307
+ try:
2308
+ # The child has a prior claim, so we can't have it
2309
+ lock.acquire_nowait()
2310
+ except trio.WouldBlock:
2311
+ assert lock._owner is not trio.lowlevel.current_task()
2312
+ print("PASS")
2313
+ else:
2314
+ print("FAIL")
2315
+
2316
+ """
2317
+ task = current_task()
2318
+ key = (cushion, id(task))
2319
+ self.waiting_for_idle[key] = task
2320
+
2321
+ def abort(_: _core.RaiseCancelT) -> Abort:
2322
+ del self.waiting_for_idle[key]
2323
+ return Abort.SUCCEEDED
2324
+
2325
+ await wait_task_rescheduled(abort)
2326
+
2327
+
2328
+ ################################################################
2329
+ # run
2330
+ ################################################################
2331
+ #
2332
+ # Trio's core task scheduler and coroutine runner is in 'unrolled_run'. It's
2333
+ # called that because it has an unusual feature: it's actually a generator.
2334
+ # Whenever it needs to fetch IO events from the OS, it yields, and waits for
2335
+ # its caller to send the IO events back in. So the loop is "unrolled" into a
2336
+ # sequence of generator send() calls.
2337
+ #
2338
+ # The reason for this unusual design is to support two different modes of
2339
+ # operation, where the IO is handled differently.
2340
+ #
2341
+ # In normal mode using trio.run, the scheduler and IO run in the same thread:
2342
+ #
2343
+ # Main thread:
2344
+ #
2345
+ # +---------------------------+
2346
+ # | Run tasks |
2347
+ # | (unrolled_run) |
2348
+ # +---------------------------+
2349
+ # | Block waiting for I/O |
2350
+ # | (io_manager.get_events) |
2351
+ # +---------------------------+
2352
+ # | Run tasks |
2353
+ # | (unrolled_run) |
2354
+ # +---------------------------+
2355
+ # | Block waiting for I/O |
2356
+ # | (io_manager.get_events) |
2357
+ # +---------------------------+
2358
+ # :
2359
+ #
2360
+ #
2361
+ # In guest mode using trio.lowlevel.start_guest_run, the scheduler runs on the
2362
+ # main thread as a host loop callback, but blocking for IO gets pushed into a
2363
+ # worker thread:
2364
+ #
2365
+ # Main thread executing host loop: Trio I/O thread:
2366
+ #
2367
+ # +---------------------------+
2368
+ # | Run Trio tasks |
2369
+ # | (unrolled_run) |
2370
+ # +---------------------------+ --------------+
2371
+ # v
2372
+ # +---------------------------+ +----------------------------+
2373
+ # | Host loop does whatever | | Block waiting for Trio I/O |
2374
+ # | it wants | | (io_manager.get_events) |
2375
+ # +---------------------------+ +----------------------------+
2376
+ # |
2377
+ # +---------------------------+ <-------------+
2378
+ # | Run Trio tasks |
2379
+ # | (unrolled_run) |
2380
+ # +---------------------------+ --------------+
2381
+ # v
2382
+ # +---------------------------+ +----------------------------+
2383
+ # | Host loop does whatever | | Block waiting for Trio I/O |
2384
+ # | it wants | | (io_manager.get_events) |
2385
+ # +---------------------------+ +----------------------------+
2386
+ # : :
2387
+ #
2388
+ # Most of Trio's internals don't need to care about this difference. The main
2389
+ # complication it creates is that in guest mode, we might need to wake up not
2390
+ # just due to OS-reported IO events, but also because of code running on the
2391
+ # host loop calling reschedule() or changing task deadlines. Search for
2392
+ # 'is_guest' to see the special cases we need to handle this.
2393
+
2394
+
2395
+ def setup_runner(
2396
+ clock: Clock | None,
2397
+ instruments: Sequence[Instrument],
2398
+ restrict_keyboard_interrupt_to_checkpoints: bool,
2399
+ strict_exception_groups: bool,
2400
+ ) -> Runner:
2401
+ """Create a Runner object and install it as the GLOBAL_RUN_CONTEXT."""
2402
+ # It wouldn't be *hard* to support nested calls to run(), but I can't
2403
+ # think of a single good reason for it, so let's be conservative for
2404
+ # now:
2405
+ if in_trio_run():
2406
+ raise RuntimeError("Attempted to call run() from inside a run()")
2407
+
2408
+ if clock is None:
2409
+ clock = SystemClock()
2410
+ instrument_group = Instruments(instruments)
2411
+ io_manager = TheIOManager()
2412
+ system_context = copy_context()
2413
+ ki_manager = KIManager()
2414
+
2415
+ runner = Runner(
2416
+ clock=clock,
2417
+ instruments=instrument_group,
2418
+ io_manager=io_manager,
2419
+ system_context=system_context,
2420
+ ki_manager=ki_manager,
2421
+ strict_exception_groups=strict_exception_groups,
2422
+ )
2423
+ runner.asyncgens.install_hooks(runner)
2424
+
2425
+ # This is where KI protection gets enabled, so we want to do it early - in
2426
+ # particular before we start modifying global state like GLOBAL_RUN_CONTEXT
2427
+ ki_manager.install(runner.deliver_ki, restrict_keyboard_interrupt_to_checkpoints)
2428
+
2429
+ GLOBAL_RUN_CONTEXT.runner = runner
2430
+ return runner
2431
+
2432
+
2433
+ def run(
2434
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[RetT]],
2435
+ *args: Unpack[PosArgT],
2436
+ clock: Clock | None = None,
2437
+ instruments: Sequence[Instrument] = (),
2438
+ restrict_keyboard_interrupt_to_checkpoints: bool = False,
2439
+ strict_exception_groups: bool = True,
2440
+ ) -> RetT:
2441
+ """Run a Trio-flavored async function, and return the result.
2442
+
2443
+ Calling::
2444
+
2445
+ run(async_fn, *args)
2446
+
2447
+ is the equivalent of::
2448
+
2449
+ await async_fn(*args)
2450
+
2451
+ except that :func:`run` can (and must) be called from a synchronous
2452
+ context.
2453
+
2454
+ This is Trio's main entry point. Almost every other function in Trio
2455
+ requires that you be inside a call to :func:`run`.
2456
+
2457
+ Args:
2458
+ async_fn: An async function.
2459
+
2460
+ args: Positional arguments to be passed to *async_fn*. If you need to
2461
+ pass keyword arguments, then use :func:`functools.partial`.
2462
+
2463
+ clock: ``None`` to use the default system-specific monotonic clock;
2464
+ otherwise, an object implementing the :class:`trio.abc.Clock`
2465
+ interface, like (for example) a :class:`trio.testing.MockClock`
2466
+ instance.
2467
+
2468
+ instruments (list of :class:`trio.abc.Instrument` objects): Any
2469
+ instrumentation you want to apply to this run. This can also be
2470
+ modified during the run; see :ref:`instrumentation`.
2471
+
2472
+ restrict_keyboard_interrupt_to_checkpoints (bool): What happens if the
2473
+ user hits control-C while :func:`run` is running? If this argument
2474
+ is False (the default), then you get the standard Python behavior: a
2475
+ :exc:`KeyboardInterrupt` exception will immediately interrupt
2476
+ whatever task is running (or if no task is running, then Trio will
2477
+ wake up a task to be interrupted). Alternatively, if you set this
2478
+ argument to True, then :exc:`KeyboardInterrupt` delivery will be
2479
+ delayed: it will be *only* be raised at :ref:`checkpoints
2480
+ <checkpoints>`, like a :exc:`Cancelled` exception.
2481
+
2482
+ The default behavior is nice because it means that even if you
2483
+ accidentally write an infinite loop that never executes any
2484
+ checkpoints, then you can still break out of it using control-C.
2485
+ The alternative behavior is nice if you're paranoid about a
2486
+ :exc:`KeyboardInterrupt` at just the wrong place leaving your
2487
+ program in an inconsistent state, because it means that you only
2488
+ have to worry about :exc:`KeyboardInterrupt` at the exact same
2489
+ places where you already have to worry about :exc:`Cancelled`.
2490
+
2491
+ This setting has no effect if your program has registered a custom
2492
+ SIGINT handler, or if :func:`run` is called from anywhere but the
2493
+ main thread (this is a Python limitation), or if you use
2494
+ :func:`open_signal_receiver` to catch SIGINT.
2495
+
2496
+ strict_exception_groups (bool): Unless set to False, nurseries will always wrap
2497
+ even a single raised exception in an exception group. This can be overridden
2498
+ on the level of individual nurseries. Setting it to False will be deprecated
2499
+ and ultimately removed in a future version of Trio.
2500
+
2501
+ Returns:
2502
+ Whatever ``async_fn`` returns.
2503
+
2504
+ Raises:
2505
+ TrioInternalError: if an unexpected error is encountered inside Trio's
2506
+ internal machinery. This is a bug and you should `let us know
2507
+ <https://github.com/python-trio/trio/issues>`__.
2508
+
2509
+ Anything else: if ``async_fn`` raises an exception, then :func:`run`
2510
+ propagates it.
2511
+
2512
+ """
2513
+ if strict_exception_groups is not None and not strict_exception_groups:
2514
+ warn_deprecated(
2515
+ "trio.run(..., strict_exception_groups=False)",
2516
+ version="0.25.0",
2517
+ issue=2929,
2518
+ instead=(
2519
+ "the default value of True and rewrite exception handlers to handle ExceptionGroups. "
2520
+ "See https://trio.readthedocs.io/en/stable/reference-core.html#designing-for-multiple-errors"
2521
+ ),
2522
+ use_triodeprecationwarning=True,
2523
+ )
2524
+
2525
+ __tracebackhide__ = True
2526
+
2527
+ runner = setup_runner(
2528
+ clock,
2529
+ instruments,
2530
+ restrict_keyboard_interrupt_to_checkpoints,
2531
+ strict_exception_groups,
2532
+ )
2533
+
2534
+ prev_library, sniffio_library.name = sniffio_library.name, "trio"
2535
+ try:
2536
+ gen = unrolled_run(runner, async_fn, args)
2537
+ # Need to send None in the first time.
2538
+ next_send: EventResult = None # type: ignore[assignment]
2539
+ while True:
2540
+ try:
2541
+ timeout = gen.send(next_send)
2542
+ except StopIteration:
2543
+ break
2544
+ next_send = runner.io_manager.get_events(timeout)
2545
+ finally:
2546
+ sniffio_library.name = prev_library
2547
+ # Inlined copy of runner.main_task_outcome.unwrap() to avoid
2548
+ # cluttering every single Trio traceback with an extra frame.
2549
+ if isinstance(runner.main_task_outcome, Value):
2550
+ return cast("RetT", runner.main_task_outcome.value)
2551
+ elif isinstance(runner.main_task_outcome, Error):
2552
+ raise runner.main_task_outcome.error
2553
+ else: # pragma: no cover
2554
+ raise AssertionError(runner.main_task_outcome)
2555
+
2556
+
2557
+ def start_guest_run( # type: ignore[explicit-any]
2558
+ async_fn: Callable[..., Awaitable[RetT]],
2559
+ *args: object,
2560
+ run_sync_soon_threadsafe: Callable[[Callable[[], object]], object],
2561
+ done_callback: Callable[[outcome.Outcome[RetT]], object],
2562
+ run_sync_soon_not_threadsafe: (
2563
+ Callable[[Callable[[], object]], object] | None
2564
+ ) = None,
2565
+ host_uses_signal_set_wakeup_fd: bool = False,
2566
+ clock: Clock | None = None,
2567
+ instruments: Sequence[Instrument] = (),
2568
+ restrict_keyboard_interrupt_to_checkpoints: bool = False,
2569
+ strict_exception_groups: bool = True,
2570
+ ) -> None:
2571
+ """Start a "guest" run of Trio on top of some other "host" event loop.
2572
+
2573
+ Each host loop can only have one guest run at a time.
2574
+
2575
+ You should always let the Trio run finish before stopping the host loop;
2576
+ if not, it may leave Trio's internal data structures in an inconsistent
2577
+ state. You might be able to get away with it if you immediately exit the
2578
+ program, but it's safest not to go there in the first place.
2579
+
2580
+ Generally, the best way to do this is wrap this in a function that starts
2581
+ the host loop and then immediately starts the guest run, and then shuts
2582
+ down the host when the guest run completes.
2583
+
2584
+ Once :func:`start_guest_run` returns successfully, the guest run
2585
+ has been set up enough that you can invoke sync-colored Trio
2586
+ functions such as :func:`~trio.current_time`, :func:`spawn_system_task`,
2587
+ and :func:`current_trio_token`. If a `~trio.TrioInternalError` occurs
2588
+ during this early setup of the guest run, it will be raised out of
2589
+ :func:`start_guest_run`. All other errors, including all errors
2590
+ raised by the *async_fn*, will be delivered to your
2591
+ *done_callback* at some point after :func:`start_guest_run` returns
2592
+ successfully.
2593
+
2594
+ Args:
2595
+
2596
+ run_sync_soon_threadsafe: An arbitrary callable, which will be passed a
2597
+ function as its sole argument::
2598
+
2599
+ def my_run_sync_soon_threadsafe(fn):
2600
+ ...
2601
+
2602
+ This callable should schedule ``fn()`` to be run by the host on its
2603
+ next pass through its loop. **Must support being called from
2604
+ arbitrary threads.**
2605
+
2606
+ done_callback: An arbitrary callable::
2607
+
2608
+ def my_done_callback(run_outcome):
2609
+ ...
2610
+
2611
+ When the Trio run has finished, Trio will invoke this callback to let
2612
+ you know. The argument is an `outcome.Outcome`, reporting what would
2613
+ have been returned or raised by `trio.run`. This function can do
2614
+ anything you want, but commonly you'll want it to shut down the
2615
+ host loop, unwrap the outcome, etc.
2616
+
2617
+ run_sync_soon_not_threadsafe: Like ``run_sync_soon_threadsafe``, but
2618
+ will only be called from inside the host loop's main thread.
2619
+ Optional, but if your host loop allows you to implement this more
2620
+ efficiently than ``run_sync_soon_threadsafe`` then passing it will
2621
+ make things a bit faster.
2622
+
2623
+ host_uses_signal_set_wakeup_fd (bool): Pass `True` if your host loop
2624
+ uses `signal.set_wakeup_fd`, and `False` otherwise. For more details,
2625
+ see :ref:`guest-run-implementation`.
2626
+
2627
+ For the meaning of other arguments, see `trio.run`.
2628
+
2629
+ """
2630
+ if strict_exception_groups is not None and not strict_exception_groups:
2631
+ warn_deprecated(
2632
+ "trio.start_guest_run(..., strict_exception_groups=False)",
2633
+ version="0.25.0",
2634
+ issue=2929,
2635
+ instead=(
2636
+ "the default value of True and rewrite exception handlers to handle ExceptionGroups. "
2637
+ "See https://trio.readthedocs.io/en/stable/reference-core.html#designing-for-multiple-errors"
2638
+ ),
2639
+ use_triodeprecationwarning=True,
2640
+ )
2641
+
2642
+ runner = setup_runner(
2643
+ clock,
2644
+ instruments,
2645
+ restrict_keyboard_interrupt_to_checkpoints,
2646
+ strict_exception_groups,
2647
+ )
2648
+ runner.is_guest = True
2649
+ runner.guest_tick_scheduled = True
2650
+
2651
+ if run_sync_soon_not_threadsafe is None:
2652
+ run_sync_soon_not_threadsafe = run_sync_soon_threadsafe
2653
+
2654
+ guest_state = GuestState(
2655
+ runner=runner,
2656
+ run_sync_soon_threadsafe=run_sync_soon_threadsafe,
2657
+ run_sync_soon_not_threadsafe=run_sync_soon_not_threadsafe,
2658
+ done_callback=done_callback,
2659
+ unrolled_run_gen=unrolled_run(
2660
+ runner,
2661
+ async_fn,
2662
+ args,
2663
+ host_uses_signal_set_wakeup_fd=host_uses_signal_set_wakeup_fd,
2664
+ ),
2665
+ )
2666
+
2667
+ # Run a few ticks of the guest run synchronously, so that by the
2668
+ # time we return, the system nursery exists and callers can use
2669
+ # spawn_system_task. We don't actually run any user code during
2670
+ # this time, so it shouldn't be possible to get an exception here,
2671
+ # except for a TrioInternalError.
2672
+ next_send = cast(
2673
+ "EventResult",
2674
+ None,
2675
+ ) # First iteration must be `None`, every iteration after that is EventResult
2676
+ for _tick in range(5): # expected need is 2 iterations + leave some wiggle room
2677
+ if runner.system_nursery is not None:
2678
+ # We're initialized enough to switch to async guest ticks
2679
+ break
2680
+ try:
2681
+ timeout = guest_state.unrolled_run_gen.send(next_send)
2682
+ except StopIteration: # pragma: no cover
2683
+ raise TrioInternalError(
2684
+ "Guest runner exited before system nursery was initialized",
2685
+ ) from None
2686
+ if timeout != 0: # pragma: no cover
2687
+ guest_state.unrolled_run_gen.throw(
2688
+ TrioInternalError(
2689
+ "Guest runner blocked before system nursery was initialized",
2690
+ ),
2691
+ )
2692
+ # next_send should be the return value of
2693
+ # IOManager.get_events() if no I/O was waiting, which is
2694
+ # platform-dependent. We don't actually check for I/O during
2695
+ # this init phase because no one should be expecting any yet.
2696
+ if sys.platform == "win32":
2697
+ next_send = 0
2698
+ else:
2699
+ next_send = []
2700
+ else: # pragma: no cover
2701
+ guest_state.unrolled_run_gen.throw(
2702
+ TrioInternalError(
2703
+ "Guest runner yielded too many times before "
2704
+ "system nursery was initialized",
2705
+ ),
2706
+ )
2707
+
2708
+ guest_state.unrolled_run_next_send = Value(next_send)
2709
+ run_sync_soon_not_threadsafe(guest_state.guest_tick)
2710
+
2711
+
2712
+ # 24 hours is arbitrary, but it avoids issues like people setting timeouts of
2713
+ # 10**20 and then getting integer overflows in the underlying system calls.
2714
+ _MAX_TIMEOUT: Final = 24 * 60 * 60
2715
+
2716
+
2717
+ # Weird quirk: this is written as a generator in order to support "guest
2718
+ # mode", where our core event loop gets unrolled into a series of callbacks on
2719
+ # the host loop. If you're doing a regular trio.run then this gets run
2720
+ # straight through.
2721
+ @enable_ki_protection
2722
+ def unrolled_run(
2723
+ runner: Runner,
2724
+ async_fn: Callable[[Unpack[PosArgT]], Awaitable[object]],
2725
+ args: tuple[Unpack[PosArgT]],
2726
+ host_uses_signal_set_wakeup_fd: bool = False,
2727
+ ) -> Generator[float, EventResult, None]:
2728
+ __tracebackhide__ = True
2729
+
2730
+ try:
2731
+ if not host_uses_signal_set_wakeup_fd:
2732
+ runner.entry_queue.wakeup.wakeup_on_signals()
2733
+
2734
+ if "before_run" in runner.instruments:
2735
+ runner.instruments.call("before_run")
2736
+ runner.clock.start_clock()
2737
+ runner.init_task = runner.spawn_impl(
2738
+ runner.init,
2739
+ (async_fn, args),
2740
+ None,
2741
+ "<init>",
2742
+ system_task=True,
2743
+ )
2744
+
2745
+ # You know how people talk about "event loops"? This 'while' loop right
2746
+ # here is our event loop:
2747
+ while runner.tasks:
2748
+ if runner.runq:
2749
+ timeout: float = 0
2750
+ else:
2751
+ deadline = runner.deadlines.next_deadline()
2752
+ timeout = runner.clock.deadline_to_sleep_time(deadline)
2753
+ timeout = min(max(0, timeout), _MAX_TIMEOUT)
2754
+
2755
+ idle_primed = None
2756
+ if runner.waiting_for_idle:
2757
+ cushion, _ = runner.waiting_for_idle.keys()[0]
2758
+ if cushion < timeout:
2759
+ timeout = cushion
2760
+ idle_primed = IdlePrimedTypes.WAITING_FOR_IDLE
2761
+ # We use 'elif' here because if there are tasks in
2762
+ # wait_all_tasks_blocked, then those tasks will wake up without
2763
+ # jumping the clock, so we don't need to autojump.
2764
+ elif runner.clock_autojump_threshold < timeout:
2765
+ timeout = runner.clock_autojump_threshold
2766
+ idle_primed = IdlePrimedTypes.AUTOJUMP_CLOCK
2767
+
2768
+ if "before_io_wait" in runner.instruments:
2769
+ runner.instruments.call("before_io_wait", timeout)
2770
+
2771
+ # Driver will call io_manager.get_events(timeout) and pass it back
2772
+ # in through the yield
2773
+ events = yield timeout
2774
+ runner.io_manager.process_events(events)
2775
+
2776
+ if "after_io_wait" in runner.instruments:
2777
+ runner.instruments.call("after_io_wait", timeout)
2778
+
2779
+ # Process cancellations due to deadline expiry
2780
+ now = runner.clock.current_time()
2781
+ if runner.deadlines.expire(now):
2782
+ idle_primed = None
2783
+
2784
+ # idle_primed != None means: if the IO wait hit the timeout, and
2785
+ # still nothing is happening, then we should start waking up
2786
+ # wait_all_tasks_blocked tasks or autojump the clock. But there
2787
+ # are some subtleties in defining "nothing is happening".
2788
+ #
2789
+ # 'not runner.runq' means that no tasks are currently runnable.
2790
+ # 'not events' means that the last IO wait call hit its full
2791
+ # timeout. These are very similar, and if idle_primed != None and
2792
+ # we're running in regular mode then they always go together. But,
2793
+ # in *guest* mode, they can happen independently, even when
2794
+ # idle_primed=True:
2795
+ #
2796
+ # - runner.runq=empty and events=True: the host loop adjusted a
2797
+ # deadline and that forced an IO wakeup before the timeout expired,
2798
+ # even though no actual tasks were scheduled.
2799
+ #
2800
+ # - runner.runq=nonempty and events=False: the IO wait hit its
2801
+ # timeout, but then some code in the host thread rescheduled a task
2802
+ # before we got here.
2803
+ #
2804
+ # So we need to check both.
2805
+ if idle_primed is not None and not runner.runq and not events:
2806
+ if idle_primed is IdlePrimedTypes.WAITING_FOR_IDLE:
2807
+ while runner.waiting_for_idle:
2808
+ key, task = runner.waiting_for_idle.peekitem(0)
2809
+ if key[0] == cushion:
2810
+ del runner.waiting_for_idle[key]
2811
+ runner.reschedule(task)
2812
+ else:
2813
+ break
2814
+ else:
2815
+ assert idle_primed is IdlePrimedTypes.AUTOJUMP_CLOCK
2816
+ assert isinstance(runner.clock, _core.MockClock)
2817
+ runner.clock._autojump()
2818
+
2819
+ # Process all runnable tasks, but only the ones that are already
2820
+ # runnable now. Anything that becomes runnable during this cycle
2821
+ # needs to wait until the next pass. This avoids various
2822
+ # starvation issues by ensuring that there's never an unbounded
2823
+ # delay between successive checks for I/O.
2824
+ #
2825
+ # Also, we randomize the order of each batch to avoid assumptions
2826
+ # about scheduling order sneaking in. In the long run, I suspect
2827
+ # we'll either (a) use strict FIFO ordering and document that for
2828
+ # predictability/determinism, or (b) implement a more
2829
+ # sophisticated scheduler (e.g. some variant of fair queueing),
2830
+ # for better behavior under load. For now, this is the worst of
2831
+ # both worlds - but it keeps our options open. (If we do decide to
2832
+ # go all in on deterministic scheduling, then there are other
2833
+ # things that will probably need to change too, like the deadlines
2834
+ # tie-breaker and the non-deterministic ordering of
2835
+ # task._notify_queues.)
2836
+ batch = list(runner.runq)
2837
+ runner.runq.clear()
2838
+ if _ALLOW_DETERMINISTIC_SCHEDULING:
2839
+ # We're running under Hypothesis, and pytest-trio has patched
2840
+ # this in to make the scheduler deterministic and avoid flaky
2841
+ # tests. It's not worth the (small) performance cost in normal
2842
+ # operation, since we'll shuffle the list and _r is only
2843
+ # seeded for tests.
2844
+ batch.sort(key=lambda t: t._counter)
2845
+ _r.shuffle(batch)
2846
+ else:
2847
+ # 50% chance of reversing the batch, this way each task
2848
+ # can appear before/after any other task.
2849
+ if _r.random() < 0.5:
2850
+ batch.reverse()
2851
+ while batch:
2852
+ task = batch.pop()
2853
+ GLOBAL_RUN_CONTEXT.task = task
2854
+
2855
+ if "before_task_step" in runner.instruments:
2856
+ runner.instruments.call("before_task_step", task)
2857
+
2858
+ next_send_fn = task._next_send_fn
2859
+ next_send = task._next_send
2860
+ task._next_send_fn = task._next_send = None
2861
+ final_outcome: Outcome[object] | None = None
2862
+
2863
+ assert next_send_fn is not None
2864
+
2865
+ try:
2866
+ # We used to unwrap the Outcome object here and send/throw
2867
+ # its contents in directly, but it turns out that .throw()
2868
+ # is buggy on CPython (all versions at time of writing):
2869
+ # https://bugs.python.org/issue29587
2870
+ # https://bugs.python.org/issue29590
2871
+ # https://bugs.python.org/issue40694
2872
+ # https://github.com/python/cpython/issues/108668
2873
+ # So now we send in the Outcome object and unwrap it on the
2874
+ # other side.
2875
+ msg = task.context.run(next_send_fn, next_send)
2876
+ except StopIteration as stop_iteration:
2877
+ final_outcome = Value(stop_iteration.value)
2878
+ except BaseException as task_exc:
2879
+ # Store for later, removing uninteresting top frames: 1
2880
+ # frame we always remove, because it's this function
2881
+ # catching it, and then in addition we remove however many
2882
+ # more Context.run adds.
2883
+ tb = task_exc.__traceback__
2884
+ for _ in range(1 + CONTEXT_RUN_TB_FRAMES):
2885
+ if tb is not None: # pragma: no branch
2886
+ tb = tb.tb_next
2887
+ final_outcome = Error(task_exc.with_traceback(tb))
2888
+ # Remove local refs so that e.g. cancelled coroutine locals
2889
+ # are not kept alive by this frame until another exception
2890
+ # comes along.
2891
+ del tb
2892
+
2893
+ if final_outcome is not None:
2894
+ # We can't call this directly inside the except: blocks
2895
+ # above, because then the exceptions end up attaching
2896
+ # themselves to other exceptions as __context__ in
2897
+ # unwanted ways.
2898
+ runner.task_exited(task, final_outcome)
2899
+ # final_outcome may contain a traceback ref. It's not as
2900
+ # crucial compared to the above, but this will allow more
2901
+ # prompt release of resources in coroutine locals.
2902
+ final_outcome = None
2903
+ else:
2904
+ task._schedule_points += 1
2905
+ if msg is CancelShieldedCheckpoint:
2906
+ runner.reschedule(task)
2907
+ elif type(msg) is WaitTaskRescheduled:
2908
+ task._cancel_points += 1
2909
+ task._abort_func = msg.abort_func
2910
+ # KI is "outside" all cancel scopes, so check for it
2911
+ # before checking for regular cancellation:
2912
+ if runner.ki_pending and task is runner.main_task:
2913
+ task._attempt_delivery_of_pending_ki()
2914
+ task._attempt_delivery_of_any_pending_cancel()
2915
+ elif type(msg) is PermanentlyDetachCoroutineObject:
2916
+ # Pretend the task just exited with the given outcome
2917
+ runner.task_exited(task, msg.final_outcome)
2918
+ else:
2919
+ exc = TypeError(
2920
+ f"trio.run received unrecognized yield message {msg!r}. "
2921
+ "Are you trying to use a library written for some "
2922
+ "other framework like asyncio? That won't work "
2923
+ "without some kind of compatibility shim.",
2924
+ )
2925
+ # The foreign library probably doesn't adhere to our
2926
+ # protocol of unwrapping whatever outcome gets sent in.
2927
+ # Instead, we'll arrange to throw `exc` in directly,
2928
+ # which works for at least asyncio and curio.
2929
+ runner.reschedule(task, exc) # type: ignore[arg-type]
2930
+ task._next_send_fn = task.coro.throw
2931
+ # prevent long-lived reference
2932
+ # TODO: develop test for this deletion
2933
+ del msg
2934
+
2935
+ if "after_task_step" in runner.instruments:
2936
+ runner.instruments.call("after_task_step", task)
2937
+ del GLOBAL_RUN_CONTEXT.task
2938
+ # prevent long-lived references
2939
+ # TODO: develop test for this deletion
2940
+ del task, next_send, next_send_fn
2941
+
2942
+ except GeneratorExit:
2943
+ # The run-loop generator has been garbage collected without finishing
2944
+ warnings.warn(
2945
+ RuntimeWarning(
2946
+ "Trio guest run got abandoned without properly finishing... "
2947
+ "weird stuff might happen",
2948
+ ),
2949
+ stacklevel=1,
2950
+ )
2951
+ except TrioInternalError:
2952
+ raise
2953
+ except BaseException as exc:
2954
+ raise TrioInternalError("internal error in Trio - please file a bug!") from exc
2955
+ finally:
2956
+ runner.close()
2957
+ GLOBAL_RUN_CONTEXT.__dict__.clear()
2958
+
2959
+ # Have to do this after runner.close() has disabled KI protection,
2960
+ # because otherwise there's a race where ki_pending could get set
2961
+ # after we check it.
2962
+ if runner.ki_pending:
2963
+ ki = KeyboardInterrupt()
2964
+ if isinstance(runner.main_task_outcome, Error):
2965
+ ki.__context__ = runner.main_task_outcome.error
2966
+ runner.main_task_outcome = Error(ki)
2967
+
2968
+
2969
+ ################################################################
2970
+ # Other public API functions
2971
+ ################################################################
2972
+
2973
+
2974
+ class _TaskStatusIgnored(TaskStatus[object]):
2975
+ def __repr__(self) -> str:
2976
+ return "TASK_STATUS_IGNORED"
2977
+
2978
+ def started(self, value: object = None) -> None:
2979
+ pass
2980
+
2981
+
2982
+ TASK_STATUS_IGNORED: Final[TaskStatus[object]] = _TaskStatusIgnored()
2983
+
2984
+
2985
+ def current_task() -> Task:
2986
+ """Return the :class:`Task` object representing the current task.
2987
+
2988
+ Returns:
2989
+ Task: the :class:`Task` that called :func:`current_task`.
2990
+
2991
+ """
2992
+
2993
+ try:
2994
+ return GLOBAL_RUN_CONTEXT.task
2995
+ except AttributeError:
2996
+ raise RuntimeError("must be called from async context") from None
2997
+
2998
+
2999
+ def current_effective_deadline() -> float:
3000
+ """Returns the current effective deadline for the current task.
3001
+
3002
+ This function examines all the cancellation scopes that are currently in
3003
+ effect (taking into account shielding), and returns the deadline that will
3004
+ expire first.
3005
+
3006
+ One example of where this might be is useful is if your code is trying to
3007
+ decide whether to begin an expensive operation like an RPC call, but wants
3008
+ to skip it if it knows that it can't possibly complete in the available
3009
+ time. Another example would be if you're using a protocol like gRPC that
3010
+ `propagates timeout information to the remote peer
3011
+ <http://www.grpc.io/docs/guides/concepts.html#deadlines>`__; this function
3012
+ gives a way to fetch that information so you can send it along.
3013
+
3014
+ If this is called in a context where a cancellation is currently active
3015
+ (i.e., a blocking call will immediately raise :exc:`Cancelled`), then
3016
+ returned deadline is ``-inf``. If it is called in a context where no
3017
+ scopes have a deadline set, it returns ``inf``.
3018
+
3019
+ Returns:
3020
+ float: the effective deadline, as an absolute time.
3021
+
3022
+ """
3023
+ return current_task()._cancel_status.effective_deadline()
3024
+
3025
+
3026
+ async def checkpoint() -> None:
3027
+ """A pure :ref:`checkpoint <checkpoints>`.
3028
+
3029
+ This checks for cancellation and allows other tasks to be scheduled,
3030
+ without otherwise blocking.
3031
+
3032
+ Note that the scheduler has the option of ignoring this and continuing to
3033
+ run the current task if it decides this is appropriate (e.g. for increased
3034
+ efficiency).
3035
+
3036
+ Equivalent to ``await trio.sleep(0)`` (which is implemented by calling
3037
+ :func:`checkpoint`.)
3038
+
3039
+ """
3040
+ # The scheduler is what checks timeouts and converts them into
3041
+ # cancellations. So by doing the schedule point first, we ensure that the
3042
+ # cancel point has the most up-to-date info.
3043
+ await cancel_shielded_checkpoint()
3044
+ task = current_task()
3045
+ task._cancel_points += 1
3046
+ if task._cancel_status.effectively_cancelled or (
3047
+ task is task._runner.main_task and task._runner.ki_pending
3048
+ ):
3049
+ cs = CancelScope(deadline=-inf)
3050
+ if (
3051
+ task._cancel_status._scope._cancel_reason is None
3052
+ and task is task._runner.main_task
3053
+ and task._runner.ki_pending
3054
+ ):
3055
+ task._cancel_status._scope._cancel_reason = CancelReason(
3056
+ source="KeyboardInterrupt"
3057
+ )
3058
+ assert task._cancel_status._scope._cancel_reason is not None
3059
+ cs._cancel_reason = task._cancel_status._scope._cancel_reason
3060
+ with cs:
3061
+ await _core.wait_task_rescheduled(lambda _: _core.Abort.SUCCEEDED)
3062
+
3063
+
3064
+ async def checkpoint_if_cancelled() -> None:
3065
+ """Issue a :ref:`checkpoint <checkpoints>` if the calling context has been
3066
+ cancelled.
3067
+
3068
+ Equivalent to (but potentially more efficient than)::
3069
+
3070
+ if trio.current_effective_deadline() == -inf:
3071
+ await trio.lowlevel.checkpoint()
3072
+
3073
+ This is either a no-op, or else it allow other tasks to be scheduled and
3074
+ then raises :exc:`trio.Cancelled`.
3075
+
3076
+ Typically used together with :func:`cancel_shielded_checkpoint`.
3077
+
3078
+ """
3079
+ task = current_task()
3080
+ if task._cancel_status.effectively_cancelled or (
3081
+ task is task._runner.main_task and task._runner.ki_pending
3082
+ ):
3083
+ await _core.checkpoint()
3084
+ raise AssertionError("this should never happen") # pragma: no cover
3085
+ task._cancel_points += 1
3086
+
3087
+
3088
+ def in_trio_run() -> bool:
3089
+ """Check whether we are in a Trio run.
3090
+ This returns `True` if and only if :func:`~trio.current_time` will succeed.
3091
+
3092
+ See also the discussion of differing ways of :ref:`detecting Trio <trio_contexts>`.
3093
+ """
3094
+ return hasattr(GLOBAL_RUN_CONTEXT, "runner")
3095
+
3096
+
3097
+ def in_trio_task() -> bool:
3098
+ """Check whether we are in a Trio task.
3099
+ This returns `True` if and only if :func:`~trio.lowlevel.current_task` will succeed.
3100
+
3101
+ See also the discussion of differing ways of :ref:`detecting Trio <trio_contexts>`.
3102
+ """
3103
+ return hasattr(GLOBAL_RUN_CONTEXT, "task")
3104
+
3105
+
3106
+ # export everything for the documentation
3107
+ if "sphinx.ext.autodoc" in sys.modules:
3108
+ from ._generated_io_epoll import *
3109
+ from ._generated_io_kqueue import *
3110
+ from ._generated_io_windows import *
3111
+
3112
+ if sys.platform == "win32":
3113
+ from ._generated_io_windows import *
3114
+ from ._io_windows import (
3115
+ EventResult as EventResult,
3116
+ WindowsIOManager as TheIOManager,
3117
+ _WindowsStatistics as IOStatistics,
3118
+ )
3119
+ elif (
3120
+ sys.platform == "linux"
3121
+ or sys.platform == "android"
3122
+ or (not TYPE_CHECKING and hasattr(select, "epoll"))
3123
+ ):
3124
+ from ._generated_io_epoll import *
3125
+ from ._io_epoll import (
3126
+ EpollIOManager as TheIOManager,
3127
+ EventResult as EventResult,
3128
+ _EpollStatistics as IOStatistics,
3129
+ )
3130
+ elif TYPE_CHECKING or hasattr(select, "kqueue"):
3131
+ from ._generated_io_kqueue import *
3132
+ from ._io_kqueue import (
3133
+ EventResult as EventResult,
3134
+ KqueueIOManager as TheIOManager,
3135
+ _KqueueStatistics as IOStatistics,
3136
+ )
3137
+ else: # pragma: no cover
3138
+ _patchers = sorted({"eventlet", "gevent"}.intersection(sys.modules))
3139
+ if _patchers:
3140
+ raise NotImplementedError(
3141
+ "unsupported platform or primitives Trio depends on are monkey-patched out by "
3142
+ + ", ".join(_patchers),
3143
+ )
3144
+
3145
+ raise NotImplementedError("unsupported platform")
3146
+
3147
+ from ._generated_instrumentation import *
3148
+ from ._generated_run import *