@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,117 @@
1
+ # Utilities for testing
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import gc
6
+ import os
7
+ import socket as stdlib_socket
8
+ import sys
9
+ import warnings
10
+ from contextlib import closing, contextmanager
11
+ from typing import TYPE_CHECKING, TypeVar
12
+
13
+ import pytest
14
+
15
+ # See trio/_tests/pytest_plugin.py for the other half of this
16
+ from trio._tests.pytest_plugin import RUN_SLOW
17
+
18
+ if TYPE_CHECKING:
19
+ from collections.abc import Generator, Iterable, Sequence
20
+
21
+ slow = pytest.mark.skipif(not RUN_SLOW, reason="use --run-slow to run slow tests")
22
+
23
+ T = TypeVar("T")
24
+
25
+ try:
26
+ s = stdlib_socket.socket(stdlib_socket.AF_INET6, stdlib_socket.SOCK_STREAM, 0)
27
+ except OSError: # pragma: no cover
28
+ # Some systems don't even support creating an IPv6 socket, let alone
29
+ # binding it. (ex: Linux with 'ipv6.disable=1' in the kernel command line)
30
+ # We don't have any of those in our CI, and there's nothing that gets
31
+ # tested _only_ if can_create_ipv6 = False, so we'll just no-cover this.
32
+ can_create_ipv6 = False
33
+ can_bind_ipv6 = False
34
+ else:
35
+ can_create_ipv6 = True
36
+ with s:
37
+ try:
38
+ s.bind(("::1", 0))
39
+ except OSError: # pragma: no cover # since support for 3.7 was removed
40
+ can_bind_ipv6 = False
41
+ else:
42
+ can_bind_ipv6 = True
43
+
44
+ creates_ipv6 = pytest.mark.skipif(not can_create_ipv6, reason="need IPv6")
45
+ binds_ipv6 = pytest.mark.skipif(not can_bind_ipv6, reason="need IPv6")
46
+
47
+
48
+ def gc_collect_harder() -> None:
49
+ # In the test suite we sometimes want to call gc.collect() to make sure
50
+ # that any objects with noisy __del__ methods (e.g. unawaited coroutines)
51
+ # get collected before we continue, so their noise doesn't leak into
52
+ # unrelated tests.
53
+ #
54
+ # On PyPy, coroutine objects (for example) can survive at least 1 round of
55
+ # garbage collection, because executing their __del__ method to print the
56
+ # warning can cause them to be resurrected. So we call collect a few times
57
+ # to make sure.
58
+ for _ in range(5):
59
+ gc.collect()
60
+
61
+
62
+ # Some of our tests need to leak coroutines, and thus trigger the
63
+ # "RuntimeWarning: coroutine '...' was never awaited" message. This context
64
+ # manager should be used anywhere this happens to hide those messages, because
65
+ # when expected they're clutter.
66
+ @contextmanager
67
+ def ignore_coroutine_never_awaited_warnings() -> Generator[None, None, None]:
68
+ with warnings.catch_warnings():
69
+ warnings.filterwarnings("ignore", message="coroutine '.*' was never awaited")
70
+ try:
71
+ yield
72
+ finally:
73
+ # Make sure to trigger any coroutine __del__ methods now, before
74
+ # we leave the context manager.
75
+ gc_collect_harder()
76
+
77
+
78
+ def _noop(*args: object, **kwargs: object) -> None:
79
+ pass # pragma: no cover
80
+
81
+
82
+ @contextmanager
83
+ def restore_unraisablehook() -> Generator[None, None, None]:
84
+ sys.unraisablehook, prev = sys.__unraisablehook__, sys.unraisablehook
85
+ try:
86
+ yield
87
+ finally:
88
+ sys.unraisablehook = prev
89
+
90
+
91
+ # Used to check sequences that might have some elements out of order.
92
+ # Example usage:
93
+ # The sequences [1, 2.1, 2.2, 3] and [1, 2.2, 2.1, 3] are both
94
+ # matched by the template [1, {2.1, 2.2}, 3]
95
+ def check_sequence_matches(seq: Sequence[T], template: Iterable[T | set[T]]) -> None:
96
+ i = 0
97
+ for pattern in template:
98
+ if not isinstance(pattern, set):
99
+ pattern = {pattern}
100
+ got = set(seq[i : i + len(pattern)])
101
+ assert got == pattern
102
+ i += len(got)
103
+
104
+
105
+ # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=246350
106
+ skip_if_fbsd_pipes_broken = pytest.mark.skipif(
107
+ sys.platform != "win32" # prevent mypy from complaining about missing uname
108
+ and hasattr(os, "uname")
109
+ and os.uname().sysname == "FreeBSD"
110
+ and os.uname().release[:4] < "12.2",
111
+ reason="hangs on FreeBSD 12.1 and earlier, due to FreeBSD bug #246350",
112
+ )
113
+
114
+
115
+ def create_asyncio_future_in_new_loop() -> asyncio.Future[object]:
116
+ with closing(asyncio.new_event_loop()) as loop:
117
+ return loop.create_future()
@@ -0,0 +1,79 @@
1
+ """Test variadic generic typing for Nursery.start[_soon]()."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from trio import TASK_STATUS_IGNORED, Nursery, TaskStatus
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Awaitable, Callable
9
+
10
+
11
+ async def task_0() -> None: ...
12
+
13
+
14
+ async def task_1a(value: int) -> None: ...
15
+
16
+
17
+ async def task_1b(value: str) -> None: ...
18
+
19
+
20
+ async def task_2a(a: int, b: str) -> None: ...
21
+
22
+
23
+ async def task_2b(a: str, b: int) -> None: ...
24
+
25
+
26
+ async def task_2c(a: str, b: int, optional: bool = False) -> None: ...
27
+
28
+
29
+ async def task_requires_kw(a: int, *, b: bool) -> None: ...
30
+
31
+
32
+ async def task_startable_1(
33
+ a: str,
34
+ *,
35
+ task_status: TaskStatus[bool] = TASK_STATUS_IGNORED,
36
+ ) -> None: ...
37
+
38
+
39
+ async def task_startable_2(
40
+ a: str,
41
+ b: float,
42
+ *,
43
+ task_status: TaskStatus[bool] = TASK_STATUS_IGNORED,
44
+ ) -> None: ...
45
+
46
+
47
+ async def task_requires_start(*, task_status: TaskStatus[str]) -> None:
48
+ """Check a function requiring start() to be used."""
49
+
50
+
51
+ async def task_pos_or_kw(value: str, task_status: TaskStatus[int]) -> None:
52
+ """Check a function which doesn't use the *-syntax works."""
53
+
54
+
55
+ def check_start_soon(nursery: Nursery) -> None:
56
+ """start_soon() functionality."""
57
+ nursery.start_soon(task_0)
58
+ nursery.start_soon(task_1a) # type: ignore
59
+ nursery.start_soon(task_2b) # type: ignore
60
+
61
+ nursery.start_soon(task_0, 45) # type: ignore
62
+ nursery.start_soon(task_1a, 32)
63
+ nursery.start_soon(task_1b, 32) # type: ignore
64
+ nursery.start_soon(task_1a, "abc") # type: ignore
65
+ nursery.start_soon(task_1b, "abc")
66
+
67
+ nursery.start_soon(task_2b, "abc") # type: ignore
68
+ nursery.start_soon(task_2a, 38, "46")
69
+ nursery.start_soon(task_2c, "abc", 12, True)
70
+
71
+ nursery.start_soon(task_2c, "abc", 12)
72
+ task_2c_cast: Callable[[str, int], Awaitable[object]] = (
73
+ task_2c # The assignment makes it work.
74
+ )
75
+ nursery.start_soon(task_2c_cast, "abc", 12)
76
+
77
+ nursery.start_soon(task_requires_kw, 12, True) # type: ignore
78
+ # Tasks following the start() API can be made to work.
79
+ nursery.start_soon(task_startable_1, "cdf")
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, overload
4
+
5
+ import trio
6
+ from typing_extensions import assert_type
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Sequence
10
+
11
+
12
+ async def sleep_sort(values: Sequence[float]) -> list[float]:
13
+ return [1]
14
+
15
+
16
+ async def has_optional(arg: int | None = None) -> int:
17
+ return 5
18
+
19
+
20
+ @overload
21
+ async def foo_overloaded(arg: int) -> str: ...
22
+
23
+
24
+ @overload
25
+ async def foo_overloaded(arg: str) -> int: ...
26
+
27
+
28
+ async def foo_overloaded(arg: int | str) -> int | str:
29
+ if isinstance(arg, str):
30
+ return 5
31
+ return "hello"
32
+
33
+
34
+ v = trio.run(
35
+ sleep_sort,
36
+ (1, 3, 5, 2, 4),
37
+ clock=trio.testing.MockClock(autojump_threshold=0),
38
+ )
39
+ assert_type(v, "list[float]")
40
+ trio.run(sleep_sort, ["hi", "there"]) # type: ignore[arg-type]
41
+ trio.run(sleep_sort) # type: ignore[arg-type]
42
+
43
+ r = trio.run(has_optional)
44
+ assert_type(r, int)
45
+ r = trio.run(has_optional, 5)
46
+ trio.run(has_optional, 7, 8) # type: ignore[arg-type]
47
+ trio.run(has_optional, "hello") # type: ignore[arg-type]
48
+
49
+
50
+ assert_type(trio.run(foo_overloaded, 5), str)
51
+ assert_type(trio.run(foo_overloaded, ""), int)
@@ -0,0 +1,317 @@
1
+ from __future__ import annotations
2
+
3
+ import ctypes
4
+ import ctypes.util
5
+ import os
6
+ import sys
7
+ import traceback
8
+ from functools import partial
9
+ from itertools import count
10
+ from threading import Lock, Thread
11
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
12
+
13
+ import outcome
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ RetT = TypeVar("RetT")
19
+
20
+
21
+ def _to_os_thread_name(name: str) -> bytes:
22
+ # ctypes handles the trailing \00
23
+ return name.encode("ascii", errors="replace")[:15]
24
+
25
+
26
+ # used to construct the method used to set os thread name, or None, depending on platform.
27
+ # called once on import
28
+ def get_os_thread_name_func() -> Callable[[int | None, str], None] | None:
29
+ def namefunc(
30
+ setname: Callable[[int, bytes], int],
31
+ ident: int | None,
32
+ name: str,
33
+ ) -> None:
34
+ # Thread.ident is None "if it has not been started". Unclear if that can happen
35
+ # with current usage.
36
+ if ident is not None: # pragma: no cover
37
+ setname(ident, _to_os_thread_name(name))
38
+
39
+ # namefunc on Mac also takes an ident, even if pthread_setname_np doesn't/can't use it
40
+ # so the caller don't need to care about platform.
41
+ def darwin_namefunc(
42
+ setname: Callable[[bytes], int],
43
+ ident: int | None,
44
+ name: str,
45
+ ) -> None:
46
+ # I don't know if Mac can rename threads that hasn't been started, but default
47
+ # to no to be on the safe side.
48
+ if ident is not None: # pragma: no cover
49
+ setname(_to_os_thread_name(name))
50
+
51
+ # find the pthread library
52
+ # this will fail on windows and musl
53
+ libpthread_path = ctypes.util.find_library("pthread")
54
+ if not libpthread_path:
55
+ # musl includes pthread functions directly in libc.so
56
+ # (but note that find_library("c") does not work on musl,
57
+ # see: https://github.com/python/cpython/issues/65821)
58
+ # so try that library instead
59
+ # if it doesn't exist, CDLL() will fail below
60
+ libpthread_path = "libc.so"
61
+
62
+ # Sometimes windows can find the path, but gives a permission error when
63
+ # accessing it. Catching a wider exception in case of more esoteric errors.
64
+ # https://github.com/python-trio/trio/issues/2688
65
+ try:
66
+ libpthread = ctypes.CDLL(libpthread_path)
67
+ except Exception: # pragma: no cover
68
+ return None
69
+
70
+ # get the setname method from it
71
+ # afaik this should never fail
72
+ pthread_setname_np = getattr(libpthread, "pthread_setname_np", None)
73
+ if pthread_setname_np is None: # pragma: no cover
74
+ return None
75
+
76
+ # specify function prototype
77
+ pthread_setname_np.restype = ctypes.c_int
78
+
79
+ # on mac OSX pthread_setname_np does not take a thread id,
80
+ # it only lets threads name themselves, which is not a problem for us.
81
+ # Just need to make sure to call it correctly
82
+ if sys.platform == "darwin":
83
+ pthread_setname_np.argtypes = [ctypes.c_char_p]
84
+ return partial(darwin_namefunc, pthread_setname_np)
85
+
86
+ # otherwise assume linux parameter conventions. Should also work on *BSD
87
+ pthread_setname_np.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
88
+ return partial(namefunc, pthread_setname_np)
89
+
90
+
91
+ # construct os thread name method
92
+ set_os_thread_name = get_os_thread_name_func()
93
+
94
+ # The "thread cache" is a simple unbounded thread pool, i.e., it automatically
95
+ # spawns as many threads as needed to handle all the requests its given. Its
96
+ # only purpose is to cache worker threads so that they don't have to be
97
+ # started from scratch every time we want to delegate some work to a thread.
98
+ # It's expected that some higher-level code will track how many threads are in
99
+ # use to avoid overwhelming the system (e.g. the limiter= argument to
100
+ # trio.to_thread.run_sync).
101
+ #
102
+ # To maximize sharing, there's only one thread cache per process, even if you
103
+ # have multiple calls to trio.run.
104
+ #
105
+ # Guarantees:
106
+ #
107
+ # It's safe to call start_thread_soon simultaneously from
108
+ # multiple threads.
109
+ #
110
+ # Idle threads are chosen in LIFO order, i.e. we *don't* spread work evenly
111
+ # over all threads. Instead we try to let some threads do most of the work
112
+ # while others sit idle as much as possible. Compared to FIFO, this has better
113
+ # memory cache behavior, and it makes it easier to detect when we have too
114
+ # many threads, so idle ones can exit.
115
+ #
116
+ # This code assumes that 'dict' has the following properties:
117
+ #
118
+ # - __setitem__, __delitem__, and popitem are all thread-safe and atomic with
119
+ # respect to each other. This is guaranteed by the GIL.
120
+ #
121
+ # - popitem returns the most-recently-added item (i.e., __setitem__ + popitem
122
+ # give you a LIFO queue). This relies on dicts being insertion-ordered, like
123
+ # they are in py36+.
124
+
125
+ # How long a thread will idle waiting for new work before gives up and exits.
126
+ # This value is pretty arbitrary; I don't think it matters too much.
127
+ IDLE_TIMEOUT = 10 # seconds
128
+
129
+ name_counter = count()
130
+
131
+
132
+ class WorkerThread(Generic[RetT]):
133
+ __slots__ = ("_default_name", "_job", "_thread", "_thread_cache", "_worker_lock")
134
+
135
+ def __init__(self, thread_cache: ThreadCache) -> None:
136
+ self._job: (
137
+ tuple[
138
+ Callable[[], RetT],
139
+ Callable[[outcome.Outcome[RetT]], object],
140
+ str | None,
141
+ ]
142
+ | None
143
+ ) = None
144
+ self._thread_cache = thread_cache
145
+ # This Lock is used in an unconventional way.
146
+ #
147
+ # "Unlocked" means we have a pending job that's been assigned to us;
148
+ # "locked" means that we don't.
149
+ #
150
+ # Initially we have no job, so it starts out in locked state.
151
+ self._worker_lock = Lock()
152
+ self._worker_lock.acquire()
153
+ self._default_name = f"Trio thread {next(name_counter)}"
154
+
155
+ self._thread = Thread(target=self._work, name=self._default_name, daemon=True)
156
+
157
+ if set_os_thread_name:
158
+ set_os_thread_name(self._thread.ident, self._default_name)
159
+ self._thread.start()
160
+
161
+ def _handle_job(self) -> None:
162
+ # Handle job in a separate method to ensure user-created
163
+ # objects are cleaned up in a consistent manner.
164
+ assert self._job is not None
165
+ fn, deliver, name = self._job
166
+ self._job = None
167
+
168
+ # set name
169
+ if name is not None:
170
+ self._thread.name = name
171
+ if set_os_thread_name:
172
+ set_os_thread_name(self._thread.ident, name)
173
+ result = outcome.capture(fn)
174
+
175
+ # reset name if it was changed
176
+ if name is not None:
177
+ self._thread.name = self._default_name
178
+ if set_os_thread_name:
179
+ set_os_thread_name(self._thread.ident, self._default_name)
180
+
181
+ # Tell the cache that we're available to be assigned a new
182
+ # job. We do this *before* calling 'deliver', so that if
183
+ # 'deliver' triggers a new job, it can be assigned to us
184
+ # instead of spawning a new thread.
185
+ self._thread_cache._idle_workers[self] = None
186
+ try:
187
+ deliver(result)
188
+ except BaseException as e:
189
+ print("Exception while delivering result of thread", file=sys.stderr)
190
+ traceback.print_exception(type(e), e, e.__traceback__)
191
+
192
+ def _work(self) -> None:
193
+ while True:
194
+ if self._worker_lock.acquire(timeout=IDLE_TIMEOUT):
195
+ # We got a job
196
+ self._handle_job()
197
+ else:
198
+ # Timeout acquiring lock, so we can probably exit. But,
199
+ # there's a race condition: we might be assigned a job *just*
200
+ # as we're about to exit. So we have to check.
201
+ try:
202
+ del self._thread_cache._idle_workers[self]
203
+ except KeyError:
204
+ # Someone else removed us from the idle worker queue, so
205
+ # they must be in the process of assigning us a job - loop
206
+ # around and wait for it.
207
+ continue
208
+ else:
209
+ # We successfully removed ourselves from the idle
210
+ # worker queue, so no more jobs are incoming; it's safe to
211
+ # exit.
212
+ return
213
+
214
+
215
+ class ThreadCache:
216
+ __slots__ = ("_idle_workers",)
217
+
218
+ def __init__(self) -> None:
219
+ self._idle_workers: dict[WorkerThread[Any], None] = {} # type: ignore[explicit-any]
220
+
221
+ def start_thread_soon(
222
+ self,
223
+ fn: Callable[[], RetT],
224
+ deliver: Callable[[outcome.Outcome[RetT]], object],
225
+ name: str | None = None,
226
+ ) -> None:
227
+ worker: WorkerThread[RetT]
228
+ try:
229
+ worker, _ = self._idle_workers.popitem()
230
+ except KeyError:
231
+ worker = WorkerThread(self)
232
+ worker._job = (fn, deliver, name)
233
+ worker._worker_lock.release()
234
+
235
+
236
+ THREAD_CACHE = ThreadCache()
237
+
238
+
239
+ def start_thread_soon(
240
+ fn: Callable[[], RetT],
241
+ deliver: Callable[[outcome.Outcome[RetT]], object],
242
+ name: str | None = None,
243
+ ) -> None:
244
+ """Runs ``deliver(outcome.capture(fn))`` in a worker thread.
245
+
246
+ Generally ``fn`` does some blocking work, and ``deliver`` delivers the
247
+ result back to whoever is interested.
248
+
249
+ This is a low-level, no-frills interface, very similar to using
250
+ `threading.Thread` to spawn a thread directly. The main difference is
251
+ that this function tries to reuse threads when possible, so it can be
252
+ a bit faster than `threading.Thread`.
253
+
254
+ Worker threads have the `~threading.Thread.daemon` flag set, which means
255
+ that if your main thread exits, worker threads will automatically be
256
+ killed. If you want to make sure that your ``fn`` runs to completion, then
257
+ you should make sure that the main thread remains alive until ``deliver``
258
+ is called.
259
+
260
+ It is safe to call this function simultaneously from multiple threads.
261
+
262
+ Args:
263
+
264
+ fn (sync function): Performs arbitrary blocking work.
265
+
266
+ deliver (sync function): Takes the `outcome.Outcome` of ``fn``, and
267
+ delivers it. *Must not block.*
268
+
269
+ Because worker threads are cached and reused for multiple calls, neither
270
+ function should mutate thread-level state, like `threading.local` objects
271
+ – or if they do, they should be careful to revert their changes before
272
+ returning.
273
+
274
+ Note:
275
+
276
+ The split between ``fn`` and ``deliver`` serves two purposes. First,
277
+ it's convenient, since most callers need something like this anyway.
278
+
279
+ Second, it avoids a small race condition that could cause too many
280
+ threads to be spawned. Consider a program that wants to run several
281
+ jobs sequentially on a thread, so the main thread submits a job, waits
282
+ for it to finish, submits another job, etc. In theory, this program
283
+ should only need one worker thread. But what could happen is:
284
+
285
+ 1. Worker thread: First job finishes, and calls ``deliver``.
286
+
287
+ 2. Main thread: receives notification that the job finished, and calls
288
+ ``start_thread_soon``.
289
+
290
+ 3. Main thread: sees that no worker threads are marked idle, so spawns
291
+ a second worker thread.
292
+
293
+ 4. Original worker thread: marks itself as idle.
294
+
295
+ To avoid this, threads mark themselves as idle *before* calling
296
+ ``deliver``.
297
+
298
+ Is this potential extra thread a major problem? Maybe not, but it's
299
+ easy enough to avoid, and we figure that if the user is trying to
300
+ limit how many threads they're using then it's polite to respect that.
301
+
302
+ """
303
+ THREAD_CACHE.start_thread_soon(fn, deliver, name)
304
+
305
+
306
+ def clear_worker_threads() -> (
307
+ None
308
+ ): # pragma: no cover # see test_clear_thread_cache_after_fork
309
+ # This is OK because the child process does not actually have any
310
+ # worker threads. Additionally, while WorkerThread keeps a strong
311
+ # reference and so would get affected, the only place those are
312
+ # stored is here.
313
+ THREAD_CACHE._idle_workers.clear()
314
+
315
+
316
+ if hasattr(os, "register_at_fork"):
317
+ os.register_at_fork(after_in_child=clear_worker_threads)