@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.
- package/bin/kgraph-launcher +15 -3
- package/lib/kgraph/scripts/build-bundle.sh +17 -4
- package/lib/site-packages/outcome/__init__.py +20 -0
- package/lib/site-packages/outcome/_impl.py +239 -0
- package/lib/site-packages/outcome/_util.py +33 -0
- package/lib/site-packages/outcome/_version.py +7 -0
- package/lib/site-packages/outcome/py.typed +0 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE +3 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/METADATA +63 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/RECORD +13 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/WHEEL +6 -0
- package/lib/site-packages/outcome-1.3.0.post0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sniffio/__init__.py +17 -0
- package/lib/site-packages/sniffio/_impl.py +95 -0
- package/lib/site-packages/sniffio/_tests/__init__.py +0 -0
- package/lib/site-packages/sniffio/_tests/test_sniffio.py +84 -0
- package/lib/site-packages/sniffio/_version.py +3 -0
- package/lib/site-packages/sniffio/py.typed +0 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE +3 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/LICENSE.MIT +20 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/METADATA +104 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/RECORD +14 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/WHEEL +5 -0
- package/lib/site-packages/sniffio-1.3.1.dist-info/top_level.txt +1 -0
- package/lib/site-packages/sortedcontainers/__init__.py +74 -0
- package/lib/site-packages/sortedcontainers/sorteddict.py +812 -0
- package/lib/site-packages/sortedcontainers/sortedlist.py +2646 -0
- package/lib/site-packages/sortedcontainers/sortedset.py +733 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/LICENSE +13 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/METADATA +264 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/RECORD +10 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/WHEEL +6 -0
- package/lib/site-packages/sortedcontainers-2.4.0.dist-info/top_level.txt +1 -0
- package/lib/site-packages/trio/__init__.py +133 -0
- package/lib/site-packages/trio/__main__.py +3 -0
- package/lib/site-packages/trio/_abc.py +714 -0
- package/lib/site-packages/trio/_channel.py +610 -0
- package/lib/site-packages/trio/_core/__init__.py +94 -0
- package/lib/site-packages/trio/_core/_asyncgens.py +243 -0
- package/lib/site-packages/trio/_core/_concat_tb.py +26 -0
- package/lib/site-packages/trio/_core/_entry_queue.py +223 -0
- package/lib/site-packages/trio/_core/_exceptions.py +169 -0
- package/lib/site-packages/trio/_core/_generated_instrumentation.py +50 -0
- package/lib/site-packages/trio/_core/_generated_io_epoll.py +98 -0
- package/lib/site-packages/trio/_core/_generated_io_kqueue.py +153 -0
- package/lib/site-packages/trio/_core/_generated_io_windows.py +204 -0
- package/lib/site-packages/trio/_core/_generated_run.py +269 -0
- package/lib/site-packages/trio/_core/_generated_windows_ffi.py +10 -0
- package/lib/site-packages/trio/_core/_instrumentation.py +117 -0
- package/lib/site-packages/trio/_core/_io_common.py +31 -0
- package/lib/site-packages/trio/_core/_io_epoll.py +385 -0
- package/lib/site-packages/trio/_core/_io_kqueue.py +292 -0
- package/lib/site-packages/trio/_core/_io_windows.py +1036 -0
- package/lib/site-packages/trio/_core/_ki.py +271 -0
- package/lib/site-packages/trio/_core/_local.py +104 -0
- package/lib/site-packages/trio/_core/_mock_clock.py +165 -0
- package/lib/site-packages/trio/_core/_parking_lot.py +317 -0
- package/lib/site-packages/trio/_core/_run.py +3148 -0
- package/lib/site-packages/trio/_core/_run_context.py +15 -0
- package/lib/site-packages/trio/_core/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_core/_tests/test_asyncgen.py +339 -0
- package/lib/site-packages/trio/_core/_tests/test_cancelled.py +222 -0
- package/lib/site-packages/trio/_core/_tests/test_exceptiongroup_gc.py +103 -0
- package/lib/site-packages/trio/_core/_tests/test_guest_mode.py +755 -0
- package/lib/site-packages/trio/_core/_tests/test_instrumentation.py +315 -0
- package/lib/site-packages/trio/_core/_tests/test_io.py +522 -0
- package/lib/site-packages/trio/_core/_tests/test_ki.py +703 -0
- package/lib/site-packages/trio/_core/_tests/test_local.py +118 -0
- package/lib/site-packages/trio/_core/_tests/test_mock_clock.py +193 -0
- package/lib/site-packages/trio/_core/_tests/test_parking_lot.py +389 -0
- package/lib/site-packages/trio/_core/_tests/test_run.py +3024 -0
- package/lib/site-packages/trio/_core/_tests/test_thread_cache.py +227 -0
- package/lib/site-packages/trio/_core/_tests/test_tutil.py +13 -0
- package/lib/site-packages/trio/_core/_tests/test_unbounded_queue.py +154 -0
- package/lib/site-packages/trio/_core/_tests/test_windows.py +305 -0
- package/lib/site-packages/trio/_core/_tests/tutil.py +117 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/nursery_start.py +79 -0
- package/lib/site-packages/trio/_core/_tests/type_tests/run.py +51 -0
- package/lib/site-packages/trio/_core/_thread_cache.py +317 -0
- package/lib/site-packages/trio/_core/_traps.py +318 -0
- package/lib/site-packages/trio/_core/_unbounded_queue.py +163 -0
- package/lib/site-packages/trio/_core/_wakeup_socketpair.py +75 -0
- package/lib/site-packages/trio/_core/_windows_cffi.py +313 -0
- package/lib/site-packages/trio/_deprecate.py +171 -0
- package/lib/site-packages/trio/_dtls.py +1380 -0
- package/lib/site-packages/trio/_file_io.py +513 -0
- package/lib/site-packages/trio/_highlevel_generic.py +125 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_listeners.py +251 -0
- package/lib/site-packages/trio/_highlevel_open_tcp_stream.py +397 -0
- package/lib/site-packages/trio/_highlevel_open_unix_stream.py +65 -0
- package/lib/site-packages/trio/_highlevel_serve_listeners.py +148 -0
- package/lib/site-packages/trio/_highlevel_socket.py +423 -0
- package/lib/site-packages/trio/_highlevel_ssl_helpers.py +180 -0
- package/lib/site-packages/trio/_path.py +289 -0
- package/lib/site-packages/trio/_repl.py +159 -0
- package/lib/site-packages/trio/_signals.py +185 -0
- package/lib/site-packages/trio/_socket.py +1326 -0
- package/lib/site-packages/trio/_ssl.py +964 -0
- package/lib/site-packages/trio/_subprocess.py +1178 -0
- package/lib/site-packages/trio/_subprocess_platform/__init__.py +123 -0
- package/lib/site-packages/trio/_subprocess_platform/kqueue.py +48 -0
- package/lib/site-packages/trio/_subprocess_platform/waitid.py +113 -0
- package/lib/site-packages/trio/_subprocess_platform/windows.py +11 -0
- package/lib/site-packages/trio/_sync.py +908 -0
- package/lib/site-packages/trio/_tests/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/astrill-codesigning-cert.cer +0 -0
- package/lib/site-packages/trio/_tests/check_type_completeness.py +247 -0
- package/lib/site-packages/trio/_tests/module_with_deprecations.py +22 -0
- package/lib/site-packages/trio/_tests/pytest_plugin.py +54 -0
- package/lib/site-packages/trio/_tests/test_abc.py +72 -0
- package/lib/site-packages/trio/_tests/test_channel.py +750 -0
- package/lib/site-packages/trio/_tests/test_contextvars.py +56 -0
- package/lib/site-packages/trio/_tests/test_deprecate.py +277 -0
- package/lib/site-packages/trio/_tests/test_deprecate_strict_exception_groups_false.py +64 -0
- package/lib/site-packages/trio/_tests/test_dtls.py +950 -0
- package/lib/site-packages/trio/_tests/test_exports.py +626 -0
- package/lib/site-packages/trio/_tests/test_fakenet.py +317 -0
- package/lib/site-packages/trio/_tests/test_file_io.py +269 -0
- package/lib/site-packages/trio/_tests/test_highlevel_generic.py +98 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_listeners.py +419 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_tcp_stream.py +693 -0
- package/lib/site-packages/trio/_tests/test_highlevel_open_unix_stream.py +86 -0
- package/lib/site-packages/trio/_tests/test_highlevel_serve_listeners.py +186 -0
- package/lib/site-packages/trio/_tests/test_highlevel_socket.py +336 -0
- package/lib/site-packages/trio/_tests/test_highlevel_ssl_helpers.py +169 -0
- package/lib/site-packages/trio/_tests/test_path.py +279 -0
- package/lib/site-packages/trio/_tests/test_repl.py +428 -0
- package/lib/site-packages/trio/_tests/test_scheduler_determinism.py +47 -0
- package/lib/site-packages/trio/_tests/test_signals.py +186 -0
- package/lib/site-packages/trio/_tests/test_socket.py +1253 -0
- package/lib/site-packages/trio/_tests/test_ssl.py +1371 -0
- package/lib/site-packages/trio/_tests/test_subprocess.py +767 -0
- package/lib/site-packages/trio/_tests/test_sync.py +735 -0
- package/lib/site-packages/trio/_tests/test_testing.py +682 -0
- package/lib/site-packages/trio/_tests/test_testing_raisesgroup.py +1128 -0
- package/lib/site-packages/trio/_tests/test_threads.py +1173 -0
- package/lib/site-packages/trio/_tests/test_timeouts.py +281 -0
- package/lib/site-packages/trio/_tests/test_tracing.py +88 -0
- package/lib/site-packages/trio/_tests/test_trio.py +8 -0
- package/lib/site-packages/trio/_tests/test_unix_pipes.py +288 -0
- package/lib/site-packages/trio/_tests/test_util.py +349 -0
- package/lib/site-packages/trio/_tests/test_wait_for_object.py +225 -0
- package/lib/site-packages/trio/_tests/test_windows_pipes.py +112 -0
- package/lib/site-packages/trio/_tests/tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tests/tools/test_gen_exports.py +179 -0
- package/lib/site-packages/trio/_tests/tools/test_mypy_annotate.py +140 -0
- package/lib/site-packages/trio/_tests/tools/test_sync_requirements.py +80 -0
- package/lib/site-packages/trio/_tests/type_tests/check_wraps.py +9 -0
- package/lib/site-packages/trio/_tests/type_tests/open_memory_channel.py +4 -0
- package/lib/site-packages/trio/_tests/type_tests/path.py +140 -0
- package/lib/site-packages/trio/_tests/type_tests/subprocesses.py +23 -0
- package/lib/site-packages/trio/_tests/type_tests/task_status.py +29 -0
- package/lib/site-packages/trio/_threads.py +610 -0
- package/lib/site-packages/trio/_timeouts.py +197 -0
- package/lib/site-packages/trio/_tools/__init__.py +0 -0
- package/lib/site-packages/trio/_tools/gen_exports.py +401 -0
- package/lib/site-packages/trio/_tools/mypy_annotate.py +126 -0
- package/lib/site-packages/trio/_tools/sync_requirements.py +98 -0
- package/lib/site-packages/trio/_tools/windows_ffi_build.py +220 -0
- package/lib/site-packages/trio/_unix_pipes.py +197 -0
- package/lib/site-packages/trio/_util.py +385 -0
- package/lib/site-packages/trio/_version.py +3 -0
- package/lib/site-packages/trio/_wait_for_object.py +67 -0
- package/lib/site-packages/trio/_windows_pipes.py +144 -0
- package/lib/site-packages/trio/abc.py +23 -0
- package/lib/site-packages/trio/from_thread.py +13 -0
- package/lib/site-packages/trio/lowlevel.py +95 -0
- package/lib/site-packages/trio/py.typed +0 -0
- package/lib/site-packages/trio/socket.py +602 -0
- package/lib/site-packages/trio/testing/__init__.py +58 -0
- package/lib/site-packages/trio/testing/_check_streams.py +570 -0
- package/lib/site-packages/trio/testing/_checkpoints.py +69 -0
- package/lib/site-packages/trio/testing/_fake_net.py +584 -0
- package/lib/site-packages/trio/testing/_memory_streams.py +633 -0
- package/lib/site-packages/trio/testing/_network.py +36 -0
- package/lib/site-packages/trio/testing/_raises_group.py +1015 -0
- package/lib/site-packages/trio/testing/_sequencer.py +87 -0
- package/lib/site-packages/trio/testing/_trio_test.py +50 -0
- package/lib/site-packages/trio/to_thread.py +4 -0
- package/lib/site-packages/trio-0.33.0.dist-info/INSTALLER +1 -0
- package/lib/site-packages/trio-0.33.0.dist-info/METADATA +186 -0
- package/lib/site-packages/trio-0.33.0.dist-info/RECORD +156 -0
- package/lib/site-packages/trio-0.33.0.dist-info/REQUESTED +0 -0
- package/lib/site-packages/trio-0.33.0.dist-info/WHEEL +5 -0
- package/lib/site-packages/trio-0.33.0.dist-info/entry_points.txt +2 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE +3 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.APACHE2 +202 -0
- package/lib/site-packages/trio-0.33.0.dist-info/licenses/LICENSE.MIT +22 -0
- package/lib/site-packages/trio-0.33.0.dist-info/top_level.txt +1 -0
- 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)
|