@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,15 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import TYPE_CHECKING, Final
5
+
6
+ if TYPE_CHECKING:
7
+ from ._run import Runner, Task
8
+
9
+
10
+ class RunContext(threading.local):
11
+ runner: Runner
12
+ task: Task
13
+
14
+
15
+ GLOBAL_RUN_CONTEXT: Final = RunContext()
@@ -0,0 +1,339 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import sys
5
+ import weakref
6
+ from math import inf
7
+ from types import AsyncGeneratorType
8
+ from typing import TYPE_CHECKING, NoReturn
9
+
10
+ import pytest
11
+
12
+ from ... import _core
13
+ from .tutil import gc_collect_harder, restore_unraisablehook
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import AsyncGenerator
17
+
18
+
19
+ def test_asyncgen_basics() -> None:
20
+ collected = []
21
+
22
+ async def example(cause: str) -> AsyncGenerator[int, None]:
23
+ try:
24
+ with contextlib.suppress(GeneratorExit):
25
+ yield 42
26
+ await _core.checkpoint()
27
+ except _core.Cancelled:
28
+ assert "exhausted" not in cause
29
+ task_name = _core.current_task().name
30
+ assert cause in task_name or task_name == "<init>"
31
+ assert _core.current_effective_deadline() == -inf
32
+ with pytest.raises(_core.Cancelled):
33
+ await _core.checkpoint()
34
+ collected.append(cause)
35
+ else:
36
+ assert "async_main" in _core.current_task().name
37
+ assert "exhausted" in cause
38
+ assert _core.current_effective_deadline() == inf
39
+ await _core.checkpoint()
40
+ collected.append(cause)
41
+
42
+ saved = []
43
+
44
+ async def async_main() -> None:
45
+ # GC'ed before exhausted
46
+ with pytest.warns( # noqa: PT031
47
+ ResourceWarning,
48
+ match="Async generator.*collected before.*exhausted",
49
+ ):
50
+ assert await example("abandoned").asend(None) == 42
51
+ gc_collect_harder()
52
+ await _core.wait_all_tasks_blocked()
53
+ assert collected.pop() == "abandoned"
54
+
55
+ aiter_ = example("exhausted 1")
56
+ try:
57
+ assert await aiter_.asend(None) == 42
58
+ finally:
59
+ await aiter_.aclose()
60
+ assert collected.pop() == "exhausted 1"
61
+
62
+ # Also fine if you exhaust it at point of use
63
+ async for val in example("exhausted 2"):
64
+ assert val == 42
65
+ assert collected.pop() == "exhausted 2"
66
+
67
+ gc_collect_harder()
68
+
69
+ # No problems saving the geniter when using either of these patterns
70
+ aiter_ = example("exhausted 3")
71
+ try:
72
+ saved.append(aiter_)
73
+ assert await aiter_.asend(None) == 42
74
+ finally:
75
+ await aiter_.aclose()
76
+ assert collected.pop() == "exhausted 3"
77
+
78
+ # Also fine if you exhaust it at point of use
79
+ saved.append(example("exhausted 4"))
80
+ async for val in saved[-1]:
81
+ assert val == 42
82
+ assert collected.pop() == "exhausted 4"
83
+
84
+ # Leave one referenced-but-unexhausted and make sure it gets cleaned up
85
+ saved.append(example("outlived run"))
86
+ assert await saved[-1].asend(None) == 42
87
+ assert collected == []
88
+
89
+ _core.run(async_main)
90
+ assert collected.pop() == "outlived run"
91
+ for agen in saved:
92
+ assert isinstance(agen, AsyncGeneratorType)
93
+ assert agen.ag_frame is None # all should now be exhausted
94
+
95
+
96
+ async def test_asyncgen_throws_during_finalization(
97
+ caplog: pytest.LogCaptureFixture,
98
+ ) -> None:
99
+ record = []
100
+
101
+ async def agen() -> AsyncGenerator[int, None]:
102
+ try:
103
+ yield 1
104
+ finally:
105
+ await _core.cancel_shielded_checkpoint()
106
+ record.append("crashing")
107
+ raise ValueError("oops")
108
+
109
+ with restore_unraisablehook():
110
+ await agen().asend(None)
111
+ gc_collect_harder()
112
+ await _core.wait_all_tasks_blocked()
113
+ assert record == ["crashing"]
114
+ # Following type ignore is because typing for LogCaptureFixture is wrong
115
+ exc_type, exc_value, _exc_traceback = caplog.records[0].exc_info # type: ignore[misc]
116
+ assert exc_type is ValueError
117
+ assert str(exc_value) == "oops"
118
+ assert "during finalization of async generator" in caplog.records[0].message
119
+
120
+
121
+ def test_firstiter_after_closing() -> None:
122
+ saved = []
123
+ record = []
124
+
125
+ async def funky_agen() -> AsyncGenerator[int, None]:
126
+ try:
127
+ yield 1
128
+ except GeneratorExit:
129
+ record.append("cleanup 1")
130
+ raise
131
+ try:
132
+ yield 2
133
+ finally:
134
+ record.append("cleanup 2")
135
+ await funky_agen().asend(None)
136
+
137
+ async def async_main() -> None:
138
+ aiter_ = funky_agen()
139
+ saved.append(aiter_)
140
+ assert await aiter_.asend(None) == 1
141
+ assert await aiter_.asend(None) == 2
142
+
143
+ _core.run(async_main)
144
+ assert record == ["cleanup 2", "cleanup 1"]
145
+
146
+
147
+ def test_interdependent_asyncgen_cleanup_order() -> None:
148
+ saved: list[AsyncGenerator[int, None]] = []
149
+ record: list[int | str] = []
150
+
151
+ async def innermost() -> AsyncGenerator[int, None]:
152
+ try:
153
+ yield 1
154
+ finally:
155
+ await _core.cancel_shielded_checkpoint()
156
+ record.append("innermost")
157
+
158
+ async def agen(
159
+ label: int,
160
+ inner: AsyncGenerator[int, None],
161
+ ) -> AsyncGenerator[int, None]:
162
+ try:
163
+ yield await inner.asend(None)
164
+ finally:
165
+ # Either `inner` has already been cleaned up, or
166
+ # we're about to exhaust it. Either way, we wind
167
+ # up with `record` containing the labels in
168
+ # innermost-to-outermost order.
169
+ with pytest.raises(StopAsyncIteration):
170
+ await inner.asend(None)
171
+ record.append(label)
172
+
173
+ async def async_main() -> None:
174
+ # This makes a chain of 101 interdependent asyncgens:
175
+ # agen(99)'s cleanup will iterate agen(98)'s will iterate
176
+ # ... agen(0)'s will iterate innermost()'s
177
+ ag_chain = innermost()
178
+ for idx in range(100):
179
+ ag_chain = agen(idx, ag_chain)
180
+ saved.append(ag_chain)
181
+ assert await ag_chain.asend(None) == 1
182
+ assert record == []
183
+
184
+ _core.run(async_main)
185
+ assert record == ["innermost", *range(100)]
186
+
187
+
188
+ @restore_unraisablehook()
189
+ def test_last_minute_gc_edge_case() -> None:
190
+ saved: list[AsyncGenerator[int, None]] = []
191
+ record = []
192
+ needs_retry = True
193
+
194
+ async def agen() -> AsyncGenerator[int, None]:
195
+ try:
196
+ yield 1
197
+ finally:
198
+ record.append("cleaned up")
199
+
200
+ def collect_at_opportune_moment(token: _core._entry_queue.TrioToken) -> None:
201
+ runner = _core._run.GLOBAL_RUN_CONTEXT.runner
202
+ assert runner.system_nursery is not None
203
+ if runner.system_nursery._closed and isinstance(
204
+ runner.asyncgens.alive,
205
+ weakref.WeakSet,
206
+ ):
207
+ saved.clear()
208
+ record.append("final collection")
209
+ gc_collect_harder()
210
+ record.append("done")
211
+ else:
212
+ try:
213
+ token.run_sync_soon(collect_at_opportune_moment, token)
214
+ except _core.RunFinishedError: # pragma: no cover
215
+ nonlocal needs_retry
216
+ needs_retry = True
217
+
218
+ async def async_main() -> None:
219
+ token = _core.current_trio_token()
220
+ token.run_sync_soon(collect_at_opportune_moment, token)
221
+ saved.append(agen())
222
+ await saved[-1].asend(None)
223
+
224
+ ATTEMPT_AMOUNT = 50
225
+
226
+ # Actually running into the edge case requires that the run_sync_soon task
227
+ # execute in between the system nursery's closure and the strong-ification
228
+ # of runner.asyncgens. There's about a 25% chance that it doesn't
229
+ # (if the run_sync_soon task runs before init on one tick and after init
230
+ # on the next tick); if we try enough times, we can make the chance of
231
+ # failure as small as we want.
232
+ for _ in range(ATTEMPT_AMOUNT):
233
+ needs_retry = False
234
+ record.clear()
235
+ saved.clear()
236
+ _core.run(async_main)
237
+ if needs_retry: # pragma: no cover
238
+ assert record == ["cleaned up"]
239
+ else:
240
+ assert record == ["final collection", "done", "cleaned up"]
241
+ break
242
+ else: # pragma: no cover
243
+ pytest.fail(
244
+ "Didn't manage to hit the trailing_finalizer_asyncgens case "
245
+ f"despite trying {ATTEMPT_AMOUNT} times",
246
+ )
247
+
248
+
249
+ async def step_outside_async_context(aiter_: AsyncGenerator[int, None]) -> None:
250
+ # abort_fns run outside of task context, at least if they're
251
+ # triggered by a deadline expiry rather than a direct
252
+ # cancellation. Thus, an asyncgen first iterated inside one
253
+ # will appear non-Trio, and since no other hooks were installed,
254
+ # will use the last-ditch fallback handling (that tries to mimic
255
+ # CPython's behavior with no hooks).
256
+ #
257
+ # NB: the strangeness with aiter being an attribute of abort_fn is
258
+ # to make it as easy as possible to ensure we don't hang onto a
259
+ # reference to aiter inside the guts of the run loop.
260
+ def abort_fn(_: _core.RaiseCancelT) -> _core.Abort:
261
+ with pytest.raises(StopIteration, match="42"):
262
+ abort_fn.aiter.asend(None).send(None) # type: ignore[attr-defined] # Callables don't have attribute "aiter"
263
+ del abort_fn.aiter # type: ignore[attr-defined]
264
+ return _core.Abort.SUCCEEDED
265
+
266
+ abort_fn.aiter = aiter_ # type: ignore[attr-defined]
267
+
268
+ async with _core.open_nursery() as nursery:
269
+ nursery.start_soon(_core.wait_task_rescheduled, abort_fn)
270
+ await _core.wait_all_tasks_blocked()
271
+ nursery.cancel_scope.deadline = _core.current_time()
272
+
273
+
274
+ async def test_fallback_when_no_hook_claims_it(
275
+ capsys: pytest.CaptureFixture[str],
276
+ ) -> None:
277
+ async def well_behaved() -> AsyncGenerator[int, None]:
278
+ yield 42
279
+
280
+ async def yields_after_yield() -> AsyncGenerator[int, None]:
281
+ with pytest.raises(GeneratorExit):
282
+ yield 42
283
+ yield 100
284
+
285
+ async def awaits_after_yield() -> AsyncGenerator[int, None]:
286
+ with pytest.raises(GeneratorExit):
287
+ yield 42
288
+ await _core.cancel_shielded_checkpoint()
289
+
290
+ with restore_unraisablehook():
291
+ await step_outside_async_context(well_behaved())
292
+ gc_collect_harder()
293
+ assert capsys.readouterr().err == ""
294
+
295
+ await step_outside_async_context(yields_after_yield())
296
+ gc_collect_harder()
297
+ assert "ignored GeneratorExit" in capsys.readouterr().err
298
+
299
+ await step_outside_async_context(awaits_after_yield())
300
+ gc_collect_harder()
301
+ assert "awaited something during finalization" in capsys.readouterr().err
302
+
303
+
304
+ def test_delegation_to_existing_hooks() -> None:
305
+ record = []
306
+
307
+ def my_firstiter(agen: AsyncGenerator[object, NoReturn]) -> None:
308
+ assert isinstance(agen, AsyncGeneratorType)
309
+ record.append("firstiter " + agen.ag_frame.f_locals["arg"])
310
+
311
+ def my_finalizer(agen: AsyncGenerator[object, NoReturn]) -> None:
312
+ assert isinstance(agen, AsyncGeneratorType)
313
+ record.append("finalizer " + agen.ag_frame.f_locals["arg"])
314
+
315
+ async def example(arg: str) -> AsyncGenerator[int, None]:
316
+ try:
317
+ yield 42
318
+ finally:
319
+ with pytest.raises(_core.Cancelled):
320
+ await _core.checkpoint()
321
+ record.append("trio collected " + arg)
322
+
323
+ async def async_main() -> None:
324
+ await step_outside_async_context(example("theirs"))
325
+ assert await example("ours").asend(None) == 42
326
+ gc_collect_harder()
327
+ assert record == ["firstiter theirs", "finalizer theirs"]
328
+ record[:] = []
329
+ await _core.wait_all_tasks_blocked()
330
+ assert record == ["trio collected ours"]
331
+
332
+ with restore_unraisablehook():
333
+ old_hooks = sys.get_asyncgen_hooks()
334
+ sys.set_asyncgen_hooks(my_firstiter, my_finalizer)
335
+ try:
336
+ _core.run(async_main)
337
+ finally:
338
+ assert sys.get_asyncgen_hooks() == (my_firstiter, my_finalizer)
339
+ sys.set_asyncgen_hooks(*old_hooks)
@@ -0,0 +1,222 @@
1
+ import pickle
2
+ import re
3
+ from math import inf
4
+
5
+ import pytest
6
+
7
+ import trio
8
+ from trio import Cancelled
9
+ from trio.lowlevel import current_task
10
+ from trio.testing import wait_all_tasks_blocked
11
+
12
+ from .test_ki import ki_self
13
+
14
+
15
+ def test_Cancelled_init() -> None:
16
+ with pytest.raises(TypeError, match=r"^trio.Cancelled has no public constructor$"):
17
+ raise Cancelled
18
+
19
+ with pytest.raises(TypeError, match=r"^trio.Cancelled has no public constructor$"):
20
+ Cancelled(source="explicit")
21
+
22
+ # private constructor should not raise
23
+ Cancelled._create(source="explicit")
24
+
25
+
26
+ async def test_Cancelled_str() -> None:
27
+ cancelled = Cancelled._create(source="explicit")
28
+ assert str(cancelled) == "cancelled due to explicit"
29
+ # note: repr(current_task()) is often fairly verbose
30
+ assert re.fullmatch(
31
+ r"cancelled due to deadline from task "
32
+ r"<Task 'trio._core._tests.test_cancelled.test_Cancelled_str' at 0x\w*>",
33
+ str(
34
+ Cancelled._create(
35
+ source="deadline",
36
+ source_task=repr(current_task()),
37
+ )
38
+ ),
39
+ )
40
+
41
+ assert re.fullmatch(
42
+ rf"cancelled due to nursery with reason 'pigs flying' from task {current_task()!r}",
43
+ str(
44
+ Cancelled._create(
45
+ source="nursery",
46
+ source_task=repr(current_task()),
47
+ reason="pigs flying",
48
+ )
49
+ ),
50
+ )
51
+
52
+
53
+ def test_Cancelled_subclass() -> None:
54
+ with pytest.raises(TypeError):
55
+ type("Subclass", (Cancelled,), {})
56
+
57
+
58
+ # https://github.com/python-trio/trio/issues/3248
59
+ def test_Cancelled_pickle() -> None:
60
+ cancelled = Cancelled._create(source="KeyboardInterrupt")
61
+ pickled_cancelled = pickle.loads(pickle.dumps(cancelled))
62
+ assert isinstance(pickled_cancelled, Cancelled)
63
+ assert cancelled.source == pickled_cancelled.source
64
+ assert cancelled.source_task == pickled_cancelled.source_task
65
+ assert cancelled.reason == pickled_cancelled.reason
66
+
67
+
68
+ async def test_cancel_reason() -> None:
69
+ with trio.CancelScope() as cs:
70
+ cs.cancel(reason="hello")
71
+ with pytest.raises(
72
+ Cancelled,
73
+ match=rf"^cancelled due to explicit with reason 'hello' from task {current_task()!r}$",
74
+ ) as excinfo:
75
+ await trio.lowlevel.checkpoint()
76
+ assert excinfo.value.source == "explicit"
77
+ assert excinfo.value.reason == "hello"
78
+ assert excinfo.value.source_task == repr(current_task())
79
+
80
+ with trio.CancelScope(deadline=-inf) as cs:
81
+ with pytest.raises(Cancelled, match=r"^cancelled due to deadline"):
82
+ await trio.lowlevel.checkpoint()
83
+
84
+ with trio.CancelScope() as cs:
85
+ cs.deadline = -inf
86
+ with pytest.raises(
87
+ Cancelled,
88
+ match=r"^cancelled due to deadline",
89
+ ):
90
+ await trio.lowlevel.checkpoint()
91
+
92
+
93
+ match_str = r"^cancelled due to nursery with reason 'child task raised exception ValueError\(\)' from task {0!r}$"
94
+
95
+
96
+ async def cancelled_task(
97
+ fail_task: trio.lowlevel.Task, task_status: trio.TaskStatus
98
+ ) -> None:
99
+ task_status.started()
100
+ with pytest.raises(Cancelled, match=match_str.format(fail_task)):
101
+ await trio.sleep_forever()
102
+ raise TypeError
103
+
104
+
105
+ # failing_task raises before cancelled_task is started
106
+ async def test_cancel_reason_nursery() -> None:
107
+ async def failing_task(task_status: trio.TaskStatus[trio.lowlevel.Task]) -> None:
108
+ task_status.started(current_task())
109
+ raise ValueError
110
+
111
+ with pytest.RaisesGroup(ValueError, TypeError):
112
+ async with trio.open_nursery() as nursery:
113
+ fail_task = await nursery.start(failing_task)
114
+ with pytest.raises(Cancelled, match=match_str.format(fail_task)):
115
+ await wait_all_tasks_blocked()
116
+ await nursery.start(cancelled_task, fail_task)
117
+
118
+
119
+ # wait until both tasks are running before failing_task raises
120
+ async def test_cancel_reason_nursery2() -> None:
121
+ async def failing_task(task_status: trio.TaskStatus[trio.lowlevel.Task]) -> None:
122
+ task_status.started(current_task())
123
+ await wait_all_tasks_blocked()
124
+ raise ValueError
125
+
126
+ with pytest.RaisesGroup(ValueError, TypeError):
127
+ async with trio.open_nursery() as nursery:
128
+ fail_task = await nursery.start(failing_task)
129
+ await nursery.start(cancelled_task, fail_task)
130
+
131
+
132
+ # failing_task raises before calling task_status.started()
133
+ async def test_cancel_reason_nursery3() -> None:
134
+ async def failing_task(task_status: trio.TaskStatus[None]) -> None:
135
+ raise ValueError
136
+
137
+ parent_task = current_task()
138
+
139
+ async def cancelled_task() -> None:
140
+ # We don't have a way of distinguishing that the nursery code block failed
141
+ # because it failed to `start()` a task.
142
+ with pytest.raises(
143
+ Cancelled,
144
+ match=re.escape(
145
+ rf"cancelled due to nursery with reason 'Code block inside nursery contextmanager raised exception ValueError()' from task {parent_task!r}"
146
+ ),
147
+ ):
148
+ await trio.sleep_forever()
149
+
150
+ with pytest.RaisesGroup(ValueError):
151
+ async with trio.open_nursery() as nursery:
152
+ nursery.start_soon(cancelled_task)
153
+ await wait_all_tasks_blocked()
154
+ await nursery.start(failing_task)
155
+
156
+
157
+ async def test_cancel_reason_not_overwritten() -> None:
158
+ with trio.CancelScope() as cs:
159
+ cs.cancel()
160
+ with pytest.raises(
161
+ Cancelled,
162
+ match=rf"^cancelled due to explicit from task {current_task()!r}$",
163
+ ):
164
+ await trio.lowlevel.checkpoint()
165
+ cs.deadline = -inf
166
+ with pytest.raises(
167
+ Cancelled,
168
+ match=rf"^cancelled due to explicit from task {current_task()!r}$",
169
+ ):
170
+ await trio.lowlevel.checkpoint()
171
+
172
+
173
+ async def test_cancel_reason_not_overwritten_2() -> None:
174
+ with trio.CancelScope() as cs:
175
+ cs.deadline = -inf
176
+ with pytest.raises(Cancelled, match=r"^cancelled due to deadline$"):
177
+ await trio.lowlevel.checkpoint()
178
+ cs.cancel()
179
+ with pytest.raises(Cancelled, match=r"^cancelled due to deadline$"):
180
+ await trio.lowlevel.checkpoint()
181
+
182
+
183
+ async def test_nested_child_source() -> None:
184
+ ev = trio.Event()
185
+ parent_task = current_task()
186
+
187
+ async def child() -> None:
188
+ ev.set()
189
+ with pytest.raises(
190
+ Cancelled,
191
+ match=rf"^cancelled due to nursery with reason 'Code block inside nursery contextmanager raised exception ValueError\(\)' from task {parent_task!r}$",
192
+ ):
193
+ await trio.sleep_forever()
194
+
195
+ with pytest.RaisesGroup(ValueError):
196
+ async with trio.open_nursery() as nursery:
197
+ nursery.start_soon(child)
198
+ await ev.wait()
199
+ raise ValueError
200
+
201
+
202
+ async def test_reason_delayed_ki() -> None:
203
+ # simplified version of test_ki.test_ki_protection_works check #2
204
+ parent_task = current_task()
205
+
206
+ async def sleeper(name: str) -> None:
207
+ with pytest.raises(
208
+ Cancelled,
209
+ match=rf"^cancelled due to KeyboardInterrupt from task {parent_task!r}$",
210
+ ):
211
+ while True:
212
+ await trio.lowlevel.checkpoint()
213
+
214
+ async def raiser(name: str) -> None:
215
+ ki_self()
216
+
217
+ with pytest.RaisesGroup(KeyboardInterrupt):
218
+ async with trio.open_nursery() as nursery:
219
+ nursery.start_soon(sleeper, "s1")
220
+ nursery.start_soon(sleeper, "s2")
221
+ nursery.start_soon(trio.lowlevel.enable_ki_protection(raiser), "r1")
222
+ # __aexit__ blocks, and then receives the KI
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ import gc
4
+ import sys
5
+ from traceback import extract_tb
6
+ from typing import TYPE_CHECKING, NoReturn
7
+
8
+ import pytest
9
+
10
+ from .._concat_tb import concat_tb
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Callable
14
+ from types import TracebackType
15
+
16
+ if sys.version_info < (3, 11):
17
+ from exceptiongroup import ExceptionGroup
18
+
19
+
20
+ def raiser1() -> NoReturn:
21
+ raiser1_2()
22
+
23
+
24
+ def raiser1_2() -> NoReturn:
25
+ raiser1_3()
26
+
27
+
28
+ def raiser1_3() -> NoReturn:
29
+ raise ValueError("raiser1_string")
30
+
31
+
32
+ def raiser2() -> NoReturn:
33
+ raiser2_2()
34
+
35
+
36
+ def raiser2_2() -> NoReturn:
37
+ raise KeyError("raiser2_string")
38
+
39
+
40
+ def get_exc(raiser: Callable[[], NoReturn]) -> Exception:
41
+ try:
42
+ raiser()
43
+ except Exception as exc:
44
+ return exc
45
+ raise AssertionError("raiser should always raise") # pragma: no cover
46
+
47
+
48
+ def get_tb(raiser: Callable[[], NoReturn]) -> TracebackType | None:
49
+ return get_exc(raiser).__traceback__
50
+
51
+
52
+ def test_concat_tb() -> None:
53
+ tb1 = get_tb(raiser1)
54
+ tb2 = get_tb(raiser2)
55
+
56
+ # These return a list of (filename, lineno, fn name, text) tuples
57
+ # https://docs.python.org/3/library/traceback.html#traceback.extract_tb
58
+ entries1 = extract_tb(tb1)
59
+ entries2 = extract_tb(tb2)
60
+
61
+ tb12 = concat_tb(tb1, tb2)
62
+ assert extract_tb(tb12) == entries1 + entries2
63
+
64
+ tb21 = concat_tb(tb2, tb1)
65
+ assert extract_tb(tb21) == entries2 + entries1
66
+
67
+ # Check degenerate cases
68
+ assert extract_tb(concat_tb(None, tb1)) == entries1
69
+ assert extract_tb(concat_tb(tb1, None)) == entries1
70
+ assert concat_tb(None, None) is None
71
+
72
+ # Make sure the original tracebacks didn't get mutated by mistake
73
+ assert extract_tb(get_tb(raiser1)) == entries1
74
+ assert extract_tb(get_tb(raiser2)) == entries2
75
+
76
+
77
+ # Unclear if this can still fail, removing the `del` from _concat_tb.copy_tb does not seem
78
+ # to trigger it (on a platform where the `del` is executed)
79
+ @pytest.mark.skipif(
80
+ sys.implementation.name != "cpython",
81
+ reason="Only makes sense with refcounting GC",
82
+ )
83
+ def test_ExceptionGroup_catch_doesnt_create_cyclic_garbage() -> None:
84
+ # https://github.com/python-trio/trio/pull/2063
85
+ gc.collect()
86
+ old_flags = gc.get_debug()
87
+
88
+ def make_multi() -> NoReturn:
89
+ raise ExceptionGroup("", [get_exc(raiser1), get_exc(raiser2)])
90
+
91
+ try:
92
+ gc.set_debug(gc.DEBUG_SAVEALL)
93
+ with pytest.raises(ExceptionGroup) as excinfo:
94
+ # covers ~~MultiErrorCatcher.__exit__ and~~ _concat_tb.copy_tb
95
+ # TODO: is the above comment true anymore? as this no longer uses MultiError.catch
96
+ raise make_multi()
97
+ for exc in excinfo.value.exceptions:
98
+ assert isinstance(exc, (ValueError, KeyError))
99
+ gc.collect()
100
+ assert not gc.garbage
101
+ finally:
102
+ gc.set_debug(old_flags)
103
+ gc.garbage.clear()