@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,271 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import signal
|
|
4
|
+
import sys
|
|
5
|
+
import weakref
|
|
6
|
+
from typing import TYPE_CHECKING, Generic, Protocol, TypeGuard, TypeVar
|
|
7
|
+
|
|
8
|
+
import attrs
|
|
9
|
+
|
|
10
|
+
from .._util import is_main_thread
|
|
11
|
+
from ._run_context import GLOBAL_RUN_CONTEXT
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
import types
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
|
|
17
|
+
from typing_extensions import Self
|
|
18
|
+
# In ordinary single-threaded Python code, when you hit control-C, it raises
|
|
19
|
+
# an exception and automatically does all the regular unwinding stuff.
|
|
20
|
+
#
|
|
21
|
+
# In Trio code, we would like hitting control-C to raise an exception and
|
|
22
|
+
# automatically do all the regular unwinding stuff. In particular, we would
|
|
23
|
+
# like to maintain our invariant that all tasks always run to completion (one
|
|
24
|
+
# way or another), by unwinding all of them.
|
|
25
|
+
#
|
|
26
|
+
# But it's basically impossible to write the core task running code in such a
|
|
27
|
+
# way that it can maintain this invariant in the face of KeyboardInterrupt
|
|
28
|
+
# exceptions arising at arbitrary bytecode positions. Similarly, if a
|
|
29
|
+
# KeyboardInterrupt happened at the wrong moment inside pretty much any of our
|
|
30
|
+
# inter-task synchronization or I/O primitives, then the system state could
|
|
31
|
+
# get corrupted and prevent our being able to clean up properly.
|
|
32
|
+
#
|
|
33
|
+
# So, we need a way to defer KeyboardInterrupt processing from these critical
|
|
34
|
+
# sections.
|
|
35
|
+
#
|
|
36
|
+
# Things that don't work:
|
|
37
|
+
#
|
|
38
|
+
# - Listen for SIGINT and process it in a system task: works fine for
|
|
39
|
+
# well-behaved programs that regularly pass through the event loop, but if
|
|
40
|
+
# user-code goes into an infinite loop then it can't be interrupted. Which
|
|
41
|
+
# is unfortunate, since dealing with infinite loops is what
|
|
42
|
+
# KeyboardInterrupt is for!
|
|
43
|
+
#
|
|
44
|
+
# - Use pthread_sigmask to disable signal delivery during critical section:
|
|
45
|
+
# (a) windows has no pthread_sigmask, (b) python threads start with all
|
|
46
|
+
# signals unblocked, so if there are any threads around they'll receive the
|
|
47
|
+
# signal and then tell the main thread to run the handler, even if the main
|
|
48
|
+
# thread has that signal blocked.
|
|
49
|
+
#
|
|
50
|
+
# - Install a signal handler which checks a global variable to decide whether
|
|
51
|
+
# to raise the exception immediately (if we're in a non-critical section),
|
|
52
|
+
# or to schedule it on the event loop (if we're in a critical section). The
|
|
53
|
+
# problem here is that it's impossible to transition safely out of user code:
|
|
54
|
+
#
|
|
55
|
+
# with keyboard_interrupt_enabled:
|
|
56
|
+
# msg = coro.send(value)
|
|
57
|
+
#
|
|
58
|
+
# If this raises a KeyboardInterrupt, it might be because the coroutine got
|
|
59
|
+
# interrupted and has unwound... or it might be the KeyboardInterrupt
|
|
60
|
+
# arrived just *after* 'send' returned, so the coroutine is still running,
|
|
61
|
+
# but we just lost the message it sent. (And worse, in our actual task
|
|
62
|
+
# runner, the send is hidden inside a utility function etc.)
|
|
63
|
+
#
|
|
64
|
+
# Solution:
|
|
65
|
+
#
|
|
66
|
+
# Mark *stack frames* as being interrupt-safe or interrupt-unsafe, and from
|
|
67
|
+
# the signal handler check which kind of frame we're currently in when
|
|
68
|
+
# deciding whether to raise or schedule the exception.
|
|
69
|
+
#
|
|
70
|
+
# There are still some cases where this can fail, like if someone hits
|
|
71
|
+
# control-C while the process is in the event loop, and then it immediately
|
|
72
|
+
# enters an infinite loop in user code. In this case the user has to hit
|
|
73
|
+
# control-C a second time. And of course if the user code is written so that
|
|
74
|
+
# it doesn't actually exit after a task crashes and everything gets cancelled,
|
|
75
|
+
# then there's not much to be done. (Hitting control-C repeatedly might help,
|
|
76
|
+
# but in general the solution is to kill the process some other way, just like
|
|
77
|
+
# for any Python program that's written to catch and ignore
|
|
78
|
+
# KeyboardInterrupt.)
|
|
79
|
+
|
|
80
|
+
_T = TypeVar("_T")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class _IdRef(weakref.ref[_T]):
|
|
84
|
+
__slots__ = ("_hash",)
|
|
85
|
+
_hash: int
|
|
86
|
+
|
|
87
|
+
def __new__(
|
|
88
|
+
cls,
|
|
89
|
+
ob: _T,
|
|
90
|
+
callback: Callable[[Self], object] | None = None,
|
|
91
|
+
/,
|
|
92
|
+
) -> Self:
|
|
93
|
+
self: Self = weakref.ref.__new__(cls, ob, callback)
|
|
94
|
+
self._hash = object.__hash__(ob)
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def __eq__(self, other: object) -> bool:
|
|
98
|
+
if self is other:
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
if not isinstance(other, _IdRef):
|
|
102
|
+
return NotImplemented
|
|
103
|
+
|
|
104
|
+
my_obj = None
|
|
105
|
+
try:
|
|
106
|
+
my_obj = self()
|
|
107
|
+
return my_obj is not None and my_obj is other()
|
|
108
|
+
finally:
|
|
109
|
+
del my_obj
|
|
110
|
+
|
|
111
|
+
# we're overriding a builtin so we do need this
|
|
112
|
+
def __ne__(self, other: object) -> bool:
|
|
113
|
+
return not self == other
|
|
114
|
+
|
|
115
|
+
def __hash__(self) -> int:
|
|
116
|
+
return self._hash
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
_KT = TypeVar("_KT")
|
|
120
|
+
_VT = TypeVar("_VT")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# see also: https://github.com/python/cpython/issues/88306
|
|
124
|
+
class WeakKeyIdentityDictionary(Generic[_KT, _VT]):
|
|
125
|
+
def __init__(self) -> None:
|
|
126
|
+
self._data: dict[_IdRef[_KT], _VT] = {}
|
|
127
|
+
|
|
128
|
+
def remove(
|
|
129
|
+
k: _IdRef[_KT],
|
|
130
|
+
selfref: weakref.ref[
|
|
131
|
+
WeakKeyIdentityDictionary[_KT, _VT]
|
|
132
|
+
] = weakref.ref( # noqa: B008 # function-call-in-default-argument
|
|
133
|
+
self,
|
|
134
|
+
),
|
|
135
|
+
) -> None:
|
|
136
|
+
self = selfref()
|
|
137
|
+
if self is not None:
|
|
138
|
+
try: # noqa: SIM105 # suppressible-exception
|
|
139
|
+
del self._data[k]
|
|
140
|
+
except KeyError:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
self._remove = remove
|
|
144
|
+
|
|
145
|
+
def __getitem__(self, k: _KT) -> _VT:
|
|
146
|
+
return self._data[_IdRef(k)]
|
|
147
|
+
|
|
148
|
+
def __setitem__(self, k: _KT, v: _VT) -> None:
|
|
149
|
+
self._data[_IdRef(k, self._remove)] = v
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_CODE_KI_PROTECTION_STATUS_WMAP: WeakKeyIdentityDictionary[
|
|
153
|
+
types.CodeType,
|
|
154
|
+
bool,
|
|
155
|
+
] = WeakKeyIdentityDictionary()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# This is to support the async_generator package necessary for aclosing on <3.10
|
|
159
|
+
# functions decorated @async_generator are given this magic property that's a
|
|
160
|
+
# reference to the object itself
|
|
161
|
+
# see python-trio/async_generator/async_generator/_impl.py
|
|
162
|
+
def legacy_isasyncgenfunction(
|
|
163
|
+
obj: object,
|
|
164
|
+
) -> TypeGuard[Callable[..., types.AsyncGeneratorType[object, object]]]:
|
|
165
|
+
return getattr(obj, "_async_gen_function", None) == id(obj)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# NB: according to the signal.signal docs, 'frame' can be None on entry to
|
|
169
|
+
# this function:
|
|
170
|
+
def ki_protection_enabled(frame: types.FrameType | None) -> bool:
|
|
171
|
+
try:
|
|
172
|
+
task = GLOBAL_RUN_CONTEXT.task
|
|
173
|
+
except AttributeError:
|
|
174
|
+
task_ki_protected = False
|
|
175
|
+
task_frame = None
|
|
176
|
+
else:
|
|
177
|
+
task_ki_protected = task._ki_protected
|
|
178
|
+
task_frame = task.coro.cr_frame
|
|
179
|
+
|
|
180
|
+
while frame is not None:
|
|
181
|
+
try:
|
|
182
|
+
v = _CODE_KI_PROTECTION_STATUS_WMAP[frame.f_code]
|
|
183
|
+
except KeyError:
|
|
184
|
+
pass
|
|
185
|
+
else:
|
|
186
|
+
return bool(v)
|
|
187
|
+
if frame.f_code.co_name == "__del__":
|
|
188
|
+
return True
|
|
189
|
+
if frame is task_frame:
|
|
190
|
+
return task_ki_protected
|
|
191
|
+
frame = frame.f_back
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def currently_ki_protected() -> bool:
|
|
196
|
+
r"""Check whether the calling code has :exc:`KeyboardInterrupt` protection
|
|
197
|
+
enabled.
|
|
198
|
+
|
|
199
|
+
It's surprisingly easy to think that one's :exc:`KeyboardInterrupt`
|
|
200
|
+
protection is enabled when it isn't, or vice-versa. This function tells
|
|
201
|
+
you what Trio thinks of the matter, which makes it useful for ``assert``\s
|
|
202
|
+
and unit tests.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
bool: True if protection is enabled, and False otherwise.
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
return ki_protection_enabled(sys._getframe())
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class _SupportsCode(Protocol):
|
|
212
|
+
__code__: types.CodeType
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
_T_supports_code = TypeVar("_T_supports_code", bound=_SupportsCode)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def enable_ki_protection(f: _T_supports_code, /) -> _T_supports_code:
|
|
219
|
+
"""Decorator to enable KI protection."""
|
|
220
|
+
orig = f
|
|
221
|
+
|
|
222
|
+
if legacy_isasyncgenfunction(f):
|
|
223
|
+
f = f.__wrapped__ # type: ignore
|
|
224
|
+
|
|
225
|
+
_CODE_KI_PROTECTION_STATUS_WMAP[f.__code__] = True
|
|
226
|
+
return orig
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def disable_ki_protection(f: _T_supports_code, /) -> _T_supports_code:
|
|
230
|
+
"""Decorator to disable KI protection."""
|
|
231
|
+
orig = f
|
|
232
|
+
|
|
233
|
+
if legacy_isasyncgenfunction(f):
|
|
234
|
+
f = f.__wrapped__ # type: ignore
|
|
235
|
+
|
|
236
|
+
_CODE_KI_PROTECTION_STATUS_WMAP[f.__code__] = False
|
|
237
|
+
return orig
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@attrs.define(slots=False)
|
|
241
|
+
class KIManager:
|
|
242
|
+
handler: Callable[[int, types.FrameType | None], None] | None = None
|
|
243
|
+
|
|
244
|
+
def install(
|
|
245
|
+
self,
|
|
246
|
+
deliver_cb: Callable[[], object],
|
|
247
|
+
restrict_keyboard_interrupt_to_checkpoints: bool,
|
|
248
|
+
) -> None:
|
|
249
|
+
assert self.handler is None
|
|
250
|
+
if (
|
|
251
|
+
not is_main_thread()
|
|
252
|
+
or signal.getsignal(signal.SIGINT) != signal.default_int_handler
|
|
253
|
+
):
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
def handler(signum: int, frame: types.FrameType | None) -> None:
|
|
257
|
+
assert signum == signal.SIGINT
|
|
258
|
+
protection_enabled = ki_protection_enabled(frame)
|
|
259
|
+
if protection_enabled or restrict_keyboard_interrupt_to_checkpoints:
|
|
260
|
+
deliver_cb()
|
|
261
|
+
else:
|
|
262
|
+
raise KeyboardInterrupt
|
|
263
|
+
|
|
264
|
+
self.handler = handler
|
|
265
|
+
signal.signal(signal.SIGINT, handler)
|
|
266
|
+
|
|
267
|
+
def close(self) -> None:
|
|
268
|
+
if self.handler is not None:
|
|
269
|
+
if signal.getsignal(signal.SIGINT) is self.handler:
|
|
270
|
+
signal.signal(signal.SIGINT, signal.default_int_handler)
|
|
271
|
+
self.handler = None
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Generic, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
# Runvar implementations
|
|
6
|
+
import attrs
|
|
7
|
+
|
|
8
|
+
from .._util import NoPublicConstructor, final
|
|
9
|
+
from . import _run
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@final
|
|
15
|
+
class _NoValue: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@final
|
|
19
|
+
@attrs.define(eq=False)
|
|
20
|
+
class RunVarToken(Generic[T], metaclass=NoPublicConstructor):
|
|
21
|
+
_var: RunVar[T]
|
|
22
|
+
previous_value: T | type[_NoValue] = _NoValue
|
|
23
|
+
redeemed: bool = attrs.field(default=False, init=False)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def _empty(cls, var: RunVar[T]) -> RunVarToken[T]:
|
|
27
|
+
return cls._create(var)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@final
|
|
31
|
+
@attrs.define(eq=False, repr=False)
|
|
32
|
+
class RunVar(Generic[T]):
|
|
33
|
+
"""The run-local variant of a context variable.
|
|
34
|
+
|
|
35
|
+
:class:`RunVar` objects are similar to context variable objects,
|
|
36
|
+
except that they are shared across a single call to :func:`trio.run`
|
|
37
|
+
rather than a single task.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_name: str = attrs.field(alias="name")
|
|
42
|
+
_default: T | type[_NoValue] = attrs.field(default=_NoValue, alias="default")
|
|
43
|
+
|
|
44
|
+
def get(self, default: T | type[_NoValue] = _NoValue) -> T:
|
|
45
|
+
"""Gets the value of this :class:`RunVar` for the current run call."""
|
|
46
|
+
try:
|
|
47
|
+
return cast("T", _run.GLOBAL_RUN_CONTEXT.runner._locals[self])
|
|
48
|
+
except AttributeError:
|
|
49
|
+
raise RuntimeError("Cannot be used outside of a run context") from None
|
|
50
|
+
except KeyError:
|
|
51
|
+
# contextvars consistency
|
|
52
|
+
# `type: ignore` awaiting https://github.com/python/mypy/issues/15553 to be fixed & released
|
|
53
|
+
if default is not _NoValue:
|
|
54
|
+
return default # type: ignore[return-value]
|
|
55
|
+
|
|
56
|
+
if self._default is not _NoValue:
|
|
57
|
+
return self._default # type: ignore[return-value]
|
|
58
|
+
|
|
59
|
+
raise LookupError(self) from None
|
|
60
|
+
|
|
61
|
+
def set(self, value: T) -> RunVarToken[T]:
|
|
62
|
+
"""Sets the value of this :class:`RunVar` for this current run
|
|
63
|
+
call.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
old_value = self.get()
|
|
68
|
+
except LookupError:
|
|
69
|
+
token = RunVarToken._empty(self)
|
|
70
|
+
else:
|
|
71
|
+
token = RunVarToken[T]._create(self, old_value)
|
|
72
|
+
|
|
73
|
+
# This can't fail, because if we weren't in Trio context then the
|
|
74
|
+
# get() above would have failed.
|
|
75
|
+
_run.GLOBAL_RUN_CONTEXT.runner._locals[self] = value
|
|
76
|
+
return token
|
|
77
|
+
|
|
78
|
+
def reset(self, token: RunVarToken[T]) -> None:
|
|
79
|
+
"""Resets the value of this :class:`RunVar` to what it was
|
|
80
|
+
previously specified by the token.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
if token is None:
|
|
84
|
+
raise TypeError("token must not be none")
|
|
85
|
+
|
|
86
|
+
if token.redeemed:
|
|
87
|
+
raise ValueError("token has already been used")
|
|
88
|
+
|
|
89
|
+
if token._var is not self:
|
|
90
|
+
raise ValueError("token is not for us")
|
|
91
|
+
|
|
92
|
+
previous = token.previous_value
|
|
93
|
+
try:
|
|
94
|
+
if previous is _NoValue:
|
|
95
|
+
_run.GLOBAL_RUN_CONTEXT.runner._locals.pop(self)
|
|
96
|
+
else:
|
|
97
|
+
_run.GLOBAL_RUN_CONTEXT.runner._locals[self] = previous
|
|
98
|
+
except AttributeError:
|
|
99
|
+
raise RuntimeError("Cannot be used outside of a run context") from None
|
|
100
|
+
|
|
101
|
+
token.redeemed = True
|
|
102
|
+
|
|
103
|
+
def __repr__(self) -> str:
|
|
104
|
+
return f"<RunVar name={self._name!r}>"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from math import inf
|
|
3
|
+
|
|
4
|
+
from .. import _core
|
|
5
|
+
from .._abc import Clock
|
|
6
|
+
from .._util import final
|
|
7
|
+
from ._run import GLOBAL_RUN_CONTEXT
|
|
8
|
+
|
|
9
|
+
################################################################
|
|
10
|
+
# The glorious MockClock
|
|
11
|
+
################################################################
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Prior art:
|
|
15
|
+
# https://twistedmatrix.com/documents/current/api/twisted.internet.task.Clock.html
|
|
16
|
+
# https://github.com/ztellman/manifold/issues/57
|
|
17
|
+
@final
|
|
18
|
+
class MockClock(Clock):
|
|
19
|
+
"""A user-controllable clock suitable for writing tests.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
rate (float): the initial :attr:`rate`.
|
|
23
|
+
autojump_threshold (float): the initial :attr:`autojump_threshold`.
|
|
24
|
+
|
|
25
|
+
.. attribute:: rate
|
|
26
|
+
|
|
27
|
+
How many seconds of clock time pass per second of real time. Default is
|
|
28
|
+
0.0, i.e. the clock only advances through manuals calls to :meth:`jump`
|
|
29
|
+
or when the :attr:`autojump_threshold` is triggered. You can assign to
|
|
30
|
+
this attribute to change it.
|
|
31
|
+
|
|
32
|
+
.. attribute:: autojump_threshold
|
|
33
|
+
|
|
34
|
+
The clock keeps an eye on the run loop, and if at any point it detects
|
|
35
|
+
that all tasks have been blocked for this many real seconds (i.e.,
|
|
36
|
+
according to the actual clock, not this clock), then the clock
|
|
37
|
+
automatically jumps ahead to the run loop's next scheduled
|
|
38
|
+
timeout. Default is :data:`math.inf`, i.e., to never autojump. You can
|
|
39
|
+
assign to this attribute to change it.
|
|
40
|
+
|
|
41
|
+
Basically the idea is that if you have code or tests that use sleeps
|
|
42
|
+
and timeouts, you can use this to make it run much faster, totally
|
|
43
|
+
automatically. (At least, as long as those sleeps/timeouts are
|
|
44
|
+
happening inside Trio; if your test involves talking to external
|
|
45
|
+
service and waiting for it to timeout then obviously we can't help you
|
|
46
|
+
there.)
|
|
47
|
+
|
|
48
|
+
You should set this to the smallest value that lets you reliably avoid
|
|
49
|
+
"false alarms" where some I/O is in flight (e.g. between two halves of
|
|
50
|
+
a socketpair) but the threshold gets triggered and time gets advanced
|
|
51
|
+
anyway. This will depend on the details of your tests and test
|
|
52
|
+
environment. If you aren't doing any I/O (like in our sleeping example
|
|
53
|
+
above) then just set it to zero, and the clock will jump whenever all
|
|
54
|
+
tasks are blocked.
|
|
55
|
+
|
|
56
|
+
.. note:: If you use ``autojump_threshold`` and
|
|
57
|
+
`wait_all_tasks_blocked` at the same time, then you might wonder how
|
|
58
|
+
they interact, since they both cause things to happen after the run
|
|
59
|
+
loop goes idle for some time. The answer is:
|
|
60
|
+
`wait_all_tasks_blocked` takes priority. If there's a task blocked
|
|
61
|
+
in `wait_all_tasks_blocked`, then the autojump feature treats that
|
|
62
|
+
as active task and does *not* jump the clock.
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, rate: float = 0.0, autojump_threshold: float = inf) -> None:
|
|
67
|
+
# when the real clock said 'real_base', the virtual time was
|
|
68
|
+
# 'virtual_base', and since then it's advanced at 'rate' virtual
|
|
69
|
+
# seconds per real second.
|
|
70
|
+
self._real_base = 0.0
|
|
71
|
+
self._virtual_base = 0.0
|
|
72
|
+
self._rate = 0.0
|
|
73
|
+
|
|
74
|
+
# kept as an attribute so that our tests can monkeypatch it
|
|
75
|
+
self._real_clock = time.perf_counter
|
|
76
|
+
|
|
77
|
+
# use the property update logic to set initial values
|
|
78
|
+
self.rate = rate
|
|
79
|
+
self.autojump_threshold = autojump_threshold
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
return f"<MockClock, time={self.current_time():.7f}, rate={self._rate} @ {id(self):#x}>"
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def rate(self) -> float:
|
|
86
|
+
return self._rate
|
|
87
|
+
|
|
88
|
+
@rate.setter
|
|
89
|
+
def rate(self, new_rate: float) -> None:
|
|
90
|
+
if new_rate < 0:
|
|
91
|
+
raise ValueError("rate must be >= 0")
|
|
92
|
+
else:
|
|
93
|
+
real = self._real_clock()
|
|
94
|
+
virtual = self._real_to_virtual(real)
|
|
95
|
+
self._virtual_base = virtual
|
|
96
|
+
self._real_base = real
|
|
97
|
+
self._rate = float(new_rate)
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def autojump_threshold(self) -> float:
|
|
101
|
+
return self._autojump_threshold
|
|
102
|
+
|
|
103
|
+
@autojump_threshold.setter
|
|
104
|
+
def autojump_threshold(self, new_autojump_threshold: float) -> None:
|
|
105
|
+
self._autojump_threshold = float(new_autojump_threshold)
|
|
106
|
+
self._try_resync_autojump_threshold()
|
|
107
|
+
|
|
108
|
+
# runner.clock_autojump_threshold is an internal API that isn't easily
|
|
109
|
+
# usable by custom third-party Clock objects. If you need access to this
|
|
110
|
+
# functionality, let us know, and we'll figure out how to make a public
|
|
111
|
+
# API. Discussion:
|
|
112
|
+
#
|
|
113
|
+
# https://github.com/python-trio/trio/issues/1587
|
|
114
|
+
def _try_resync_autojump_threshold(self) -> None:
|
|
115
|
+
try:
|
|
116
|
+
runner = GLOBAL_RUN_CONTEXT.runner
|
|
117
|
+
if runner.is_guest:
|
|
118
|
+
runner.force_guest_tick_asap()
|
|
119
|
+
except AttributeError:
|
|
120
|
+
pass
|
|
121
|
+
else:
|
|
122
|
+
if runner.clock is self:
|
|
123
|
+
runner.clock_autojump_threshold = self._autojump_threshold
|
|
124
|
+
|
|
125
|
+
# Invoked by the run loop when runner.clock_autojump_threshold is
|
|
126
|
+
# exceeded.
|
|
127
|
+
def _autojump(self) -> None:
|
|
128
|
+
statistics = _core.current_statistics()
|
|
129
|
+
jump = statistics.seconds_to_next_deadline
|
|
130
|
+
if 0 < jump < inf:
|
|
131
|
+
self.jump(jump)
|
|
132
|
+
|
|
133
|
+
def _real_to_virtual(self, real: float) -> float:
|
|
134
|
+
real_offset = real - self._real_base
|
|
135
|
+
virtual_offset = self._rate * real_offset
|
|
136
|
+
return self._virtual_base + virtual_offset
|
|
137
|
+
|
|
138
|
+
def start_clock(self) -> None:
|
|
139
|
+
self._try_resync_autojump_threshold()
|
|
140
|
+
|
|
141
|
+
def current_time(self) -> float:
|
|
142
|
+
return self._real_to_virtual(self._real_clock())
|
|
143
|
+
|
|
144
|
+
def deadline_to_sleep_time(self, deadline: float) -> float:
|
|
145
|
+
virtual_timeout = deadline - self.current_time()
|
|
146
|
+
if virtual_timeout <= 0:
|
|
147
|
+
return 0
|
|
148
|
+
elif self._rate > 0:
|
|
149
|
+
return virtual_timeout / self._rate
|
|
150
|
+
else:
|
|
151
|
+
return 999999999
|
|
152
|
+
|
|
153
|
+
def jump(self, seconds: float) -> None:
|
|
154
|
+
"""Manually advance the clock by the given number of seconds.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
seconds (float): the number of seconds to jump the clock forward.
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValueError: if you try to pass a negative value for ``seconds``.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
if seconds < 0:
|
|
164
|
+
raise ValueError("time can't go backwards")
|
|
165
|
+
self._virtual_base += seconds
|