@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,908 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from typing import TYPE_CHECKING, Literal, Protocol, TypeVar
5
+
6
+ import attrs
7
+
8
+ import trio
9
+
10
+ from . import _core
11
+ from ._core import (
12
+ Abort,
13
+ ParkingLot,
14
+ RaiseCancelT,
15
+ add_parking_lot_breaker,
16
+ enable_ki_protection,
17
+ remove_parking_lot_breaker,
18
+ )
19
+ from ._deprecate import warn_deprecated
20
+ from ._util import final
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Callable
24
+ from types import TracebackType
25
+
26
+ from typing_extensions import deprecated
27
+
28
+ from ._core import Task
29
+ from ._core._parking_lot import ParkingLotStatistics
30
+ else:
31
+ T = TypeVar("T")
32
+
33
+ def deprecated(
34
+ message: str,
35
+ /,
36
+ *,
37
+ category: type[Warning] | None = DeprecationWarning,
38
+ stacklevel: int = 1,
39
+ ) -> Callable[[T], T]:
40
+ def wrapper(f: T) -> T:
41
+ return f
42
+
43
+ return wrapper
44
+
45
+
46
+ @attrs.frozen
47
+ class EventStatistics:
48
+ """An object containing debugging information.
49
+
50
+ Currently the following fields are defined:
51
+
52
+ * ``tasks_waiting``: The number of tasks blocked on this event's
53
+ :meth:`trio.Event.wait` method.
54
+
55
+ """
56
+
57
+ tasks_waiting: int
58
+
59
+
60
+ @final
61
+ @attrs.define(repr=False, eq=False)
62
+ class Event:
63
+ """A waitable boolean value useful for inter-task synchronization,
64
+ inspired by :class:`threading.Event`.
65
+
66
+ An event object has an internal boolean flag, representing whether
67
+ the event has happened yet. The flag is initially False, and the
68
+ :meth:`wait` method waits until the flag is True. If the flag is
69
+ already True, then :meth:`wait` returns immediately. (If the event has
70
+ already happened, there's nothing to wait for.) The :meth:`set` method
71
+ sets the flag to True, and wakes up any waiters.
72
+
73
+ This behavior is useful because it helps avoid race conditions and
74
+ lost wakeups: it doesn't matter whether :meth:`set` gets called just
75
+ before or after :meth:`wait`. If you want a lower-level wakeup
76
+ primitive that doesn't have this protection, consider :class:`Condition`
77
+ or :class:`trio.lowlevel.ParkingLot`.
78
+
79
+ .. note:: Unlike `threading.Event`, `trio.Event` has no
80
+ `~threading.Event.clear` method. In Trio, once an `Event` has happened,
81
+ it cannot un-happen. If you need to represent a series of events,
82
+ consider creating a new `Event` object for each one (they're cheap!),
83
+ or other synchronization methods like :ref:`channels <channels>` or
84
+ `trio.lowlevel.ParkingLot`.
85
+
86
+ """
87
+
88
+ _tasks: set[Task] = attrs.field(factory=set, init=False)
89
+ _flag: bool = attrs.field(default=False, init=False)
90
+
91
+ def is_set(self) -> bool:
92
+ """Return the current value of the internal flag."""
93
+ return self._flag
94
+
95
+ @enable_ki_protection
96
+ def set(self) -> None:
97
+ """Set the internal flag value to True, and wake any waiting tasks."""
98
+ if not self._flag:
99
+ self._flag = True
100
+ for task in self._tasks:
101
+ _core.reschedule(task)
102
+ self._tasks.clear()
103
+
104
+ async def wait(self) -> None:
105
+ """Block until the internal flag value becomes True.
106
+
107
+ If it's already True, then this method returns immediately.
108
+
109
+ """
110
+ if self._flag:
111
+ await trio.lowlevel.checkpoint()
112
+ else:
113
+ task = _core.current_task()
114
+ self._tasks.add(task)
115
+
116
+ def abort_fn(_: RaiseCancelT) -> Abort:
117
+ self._tasks.remove(task)
118
+ return _core.Abort.SUCCEEDED
119
+
120
+ await _core.wait_task_rescheduled(abort_fn)
121
+
122
+ def statistics(self) -> EventStatistics:
123
+ """Return an object containing debugging information.
124
+
125
+ Currently the following fields are defined:
126
+
127
+ * ``tasks_waiting``: The number of tasks blocked on this event's
128
+ :meth:`wait` method.
129
+
130
+ """
131
+ return EventStatistics(tasks_waiting=len(self._tasks))
132
+
133
+ @deprecated(
134
+ "trio.Event.__bool__ is deprecated since Trio 0.31.0; use trio.Event.is_set instead (https://github.com/python-trio/trio/issues/3238)",
135
+ stacklevel=2,
136
+ )
137
+ def __bool__(self) -> Literal[True]:
138
+ """Return True and raise warning."""
139
+ warn_deprecated(
140
+ self.__bool__,
141
+ "0.31.0",
142
+ issue=3238,
143
+ instead=self.is_set,
144
+ )
145
+ return True
146
+
147
+
148
+ class _HasAcquireRelease(Protocol):
149
+ """Only classes with acquire() and release() can use the mixin's implementations."""
150
+
151
+ async def acquire(self) -> object: ...
152
+
153
+ def release(self) -> object: ...
154
+
155
+
156
+ class AsyncContextManagerMixin:
157
+ @enable_ki_protection
158
+ async def __aenter__(self: _HasAcquireRelease) -> None:
159
+ await self.acquire()
160
+
161
+ @enable_ki_protection
162
+ async def __aexit__(
163
+ self: _HasAcquireRelease,
164
+ exc_type: type[BaseException] | None,
165
+ exc_value: BaseException | None,
166
+ traceback: TracebackType | None,
167
+ ) -> None:
168
+ self.release()
169
+
170
+
171
+ @attrs.frozen
172
+ class CapacityLimiterStatistics:
173
+ """An object containing debugging information.
174
+
175
+ Currently the following fields are defined:
176
+
177
+ * ``borrowed_tokens``: The number of tokens currently borrowed from
178
+ the sack.
179
+ * ``total_tokens``: The total number of tokens in the sack. Usually
180
+ this will be larger than ``borrowed_tokens``, but it's possibly for
181
+ it to be smaller if :attr:`trio.CapacityLimiter.total_tokens` was recently decreased.
182
+ * ``borrowers``: A list of all tasks or other entities that currently
183
+ hold a token.
184
+ * ``tasks_waiting``: The number of tasks blocked on this
185
+ :class:`CapacityLimiter`\'s :meth:`trio.CapacityLimiter.acquire` or
186
+ :meth:`trio.CapacityLimiter.acquire_on_behalf_of` methods.
187
+
188
+ """
189
+
190
+ borrowed_tokens: int
191
+ total_tokens: int | float
192
+ borrowers: list[Task | object]
193
+ tasks_waiting: int
194
+
195
+
196
+ # Can be a generic type with a default of Task if/when PEP 696 is released
197
+ # and implemented in type checkers. Making it fully generic would currently
198
+ # introduce a lot of unnecessary hassle.
199
+ @final
200
+ class CapacityLimiter(AsyncContextManagerMixin):
201
+ """An object for controlling access to a resource with limited capacity.
202
+
203
+ Sometimes you need to put a limit on how many tasks can do something at
204
+ the same time. For example, you might want to use some threads to run
205
+ multiple blocking I/O operations in parallel... but if you use too many
206
+ threads at once, then your system can become overloaded and it'll actually
207
+ make things slower. One popular solution is to impose a policy like "run
208
+ up to 40 threads at the same time, but no more". But how do you implement
209
+ a policy like this?
210
+
211
+ That's what :class:`CapacityLimiter` is for. You can think of a
212
+ :class:`CapacityLimiter` object as a sack that starts out holding some fixed
213
+ number of tokens::
214
+
215
+ limit = trio.CapacityLimiter(40)
216
+
217
+ Then tasks can come along and borrow a token out of the sack::
218
+
219
+ # Borrow a token:
220
+ async with limit:
221
+ # We are holding a token!
222
+ await perform_expensive_operation()
223
+ # Exiting the 'async with' block puts the token back into the sack
224
+
225
+ And crucially, if you try to borrow a token but the sack is empty, then
226
+ you have to wait for another task to finish what it's doing and put its
227
+ token back first before you can take it and continue.
228
+
229
+ Another way to think of it: a :class:`CapacityLimiter` is like a sofa with a
230
+ fixed number of seats, and if they're all taken then you have to wait for
231
+ someone to get up before you can sit down.
232
+
233
+ By default, :func:`trio.to_thread.run_sync` uses a
234
+ :class:`CapacityLimiter` to limit the number of threads running at once;
235
+ see `trio.to_thread.current_default_thread_limiter` for details.
236
+
237
+ If you're familiar with semaphores, then you can think of this as a
238
+ restricted semaphore that's specialized for one common use case, with
239
+ additional error checking. For a more traditional semaphore, see
240
+ :class:`Semaphore`.
241
+
242
+ .. note::
243
+
244
+ Don't confuse this with the `"leaky bucket"
245
+ <https://en.wikipedia.org/wiki/Leaky_bucket>`__ or `"token bucket"
246
+ <https://en.wikipedia.org/wiki/Token_bucket>`__ algorithms used to
247
+ limit bandwidth usage on networks. The basic idea of using tokens to
248
+ track a resource limit is similar, but this is a very simple sack where
249
+ tokens aren't automatically created or destroyed over time; they're
250
+ just borrowed and then put back.
251
+
252
+ """
253
+
254
+ # total_tokens would ideally be int|Literal[math.inf] - but that's not valid typing
255
+ def __init__(self, total_tokens: int | float) -> None: # noqa: PYI041
256
+ self._lot = ParkingLot()
257
+ self._borrowers: set[Task | object] = set()
258
+ # Maps tasks attempting to acquire -> borrower, to handle on-behalf-of
259
+ self._pending_borrowers: dict[Task, Task | object] = {}
260
+ # invoke the property setter for validation
261
+ self.total_tokens: int | float = total_tokens
262
+ assert self._total_tokens == total_tokens
263
+
264
+ def __repr__(self) -> str:
265
+ return f"<trio.CapacityLimiter at {id(self):#x}, {len(self._borrowers)}/{self._total_tokens} with {len(self._lot)} waiting>"
266
+
267
+ @property
268
+ def total_tokens(self) -> int | float:
269
+ """The total capacity available.
270
+
271
+ You can change :attr:`total_tokens` by assigning to this attribute. If
272
+ you make it larger, then the appropriate number of waiting tasks will
273
+ be woken immediately to take the new tokens. If you decrease
274
+ total_tokens below the number of tasks that are currently using the
275
+ resource, then all current tasks will be allowed to finish as normal,
276
+ but no new tasks will be allowed in until the total number of tasks
277
+ drops below the new total_tokens.
278
+
279
+ """
280
+ return self._total_tokens
281
+
282
+ @total_tokens.setter
283
+ def total_tokens(self, new_total_tokens: int | float) -> None: # noqa: PYI041
284
+ if not isinstance(new_total_tokens, int) and new_total_tokens != math.inf:
285
+ raise TypeError("total_tokens must be an int or math.inf")
286
+ if new_total_tokens < 0:
287
+ raise ValueError("total_tokens must be >= 0")
288
+ self._total_tokens = new_total_tokens
289
+ self._wake_waiters()
290
+
291
+ def _wake_waiters(self) -> None:
292
+ available = self._total_tokens - len(self._borrowers)
293
+ for woken in self._lot.unpark(count=available):
294
+ self._borrowers.add(self._pending_borrowers.pop(woken))
295
+
296
+ @property
297
+ def borrowed_tokens(self) -> int:
298
+ """The amount of capacity that's currently in use."""
299
+ return len(self._borrowers)
300
+
301
+ @property
302
+ def available_tokens(self) -> int | float:
303
+ """The amount of capacity that's available to use."""
304
+ return self.total_tokens - self.borrowed_tokens
305
+
306
+ @enable_ki_protection
307
+ def acquire_nowait(self) -> None:
308
+ """Borrow a token from the sack, without blocking.
309
+
310
+ Raises:
311
+ WouldBlock: if no tokens are available.
312
+ RuntimeError: if the current task already holds one of this sack's
313
+ tokens.
314
+
315
+ """
316
+ self.acquire_on_behalf_of_nowait(trio.lowlevel.current_task())
317
+
318
+ @enable_ki_protection
319
+ def acquire_on_behalf_of_nowait(self, borrower: Task | object) -> None:
320
+ """Borrow a token from the sack on behalf of ``borrower``, without
321
+ blocking.
322
+
323
+ Args:
324
+ borrower: A :class:`trio.lowlevel.Task` or arbitrary opaque object
325
+ used to record who is borrowing this token. This is used by
326
+ :func:`trio.to_thread.run_sync` to allow threads to "hold
327
+ tokens", with the intention in the future of using it to `allow
328
+ deadlock detection and other useful things
329
+ <https://github.com/python-trio/trio/issues/182>`__
330
+
331
+ Raises:
332
+ WouldBlock: if no tokens are available.
333
+ RuntimeError: if ``borrower`` already holds one of this sack's
334
+ tokens.
335
+
336
+ """
337
+ if borrower in self._borrowers:
338
+ raise RuntimeError(
339
+ "this borrower is already holding one of this CapacityLimiter's tokens",
340
+ )
341
+ if len(self._borrowers) < self._total_tokens and not self._lot:
342
+ self._borrowers.add(borrower)
343
+ else:
344
+ raise trio.WouldBlock
345
+
346
+ @enable_ki_protection
347
+ async def acquire(self) -> None:
348
+ """Borrow a token from the sack, blocking if necessary.
349
+
350
+ Raises:
351
+ RuntimeError: if the current task already holds one of this sack's
352
+ tokens.
353
+
354
+ """
355
+ await self.acquire_on_behalf_of(trio.lowlevel.current_task())
356
+
357
+ @enable_ki_protection
358
+ async def acquire_on_behalf_of(self, borrower: Task | object) -> None:
359
+ """Borrow a token from the sack on behalf of ``borrower``, blocking if
360
+ necessary.
361
+
362
+ Args:
363
+ borrower: A :class:`trio.lowlevel.Task` or arbitrary opaque object
364
+ used to record who is borrowing this token; see
365
+ :meth:`acquire_on_behalf_of_nowait` for details.
366
+
367
+ Raises:
368
+ RuntimeError: if ``borrower`` task already holds one of this sack's
369
+ tokens.
370
+
371
+ """
372
+ await trio.lowlevel.checkpoint_if_cancelled()
373
+ try:
374
+ self.acquire_on_behalf_of_nowait(borrower)
375
+ except trio.WouldBlock:
376
+ task = trio.lowlevel.current_task()
377
+ self._pending_borrowers[task] = borrower
378
+ try:
379
+ await self._lot.park()
380
+ except trio.Cancelled:
381
+ self._pending_borrowers.pop(task)
382
+ raise
383
+ else:
384
+ await trio.lowlevel.cancel_shielded_checkpoint()
385
+
386
+ @enable_ki_protection
387
+ def release(self) -> None:
388
+ """Put a token back into the sack.
389
+
390
+ Raises:
391
+ RuntimeError: if the current task has not acquired one of this
392
+ sack's tokens.
393
+
394
+ """
395
+ self.release_on_behalf_of(trio.lowlevel.current_task())
396
+
397
+ @enable_ki_protection
398
+ def release_on_behalf_of(self, borrower: Task | object) -> None:
399
+ """Put a token back into the sack on behalf of ``borrower``.
400
+
401
+ Raises:
402
+ RuntimeError: if the given borrower has not acquired one of this
403
+ sack's tokens.
404
+
405
+ """
406
+ if borrower not in self._borrowers:
407
+ raise RuntimeError(
408
+ "this borrower isn't holding any of this CapacityLimiter's tokens",
409
+ )
410
+ self._borrowers.remove(borrower)
411
+ self._wake_waiters()
412
+
413
+ def statistics(self) -> CapacityLimiterStatistics:
414
+ """Return an object containing debugging information.
415
+
416
+ Currently the following fields are defined:
417
+
418
+ * ``borrowed_tokens``: The number of tokens currently borrowed from
419
+ the sack.
420
+ * ``total_tokens``: The total number of tokens in the sack. Usually
421
+ this will be larger than ``borrowed_tokens``, but it's possibly for
422
+ it to be smaller if :attr:`total_tokens` was recently decreased.
423
+ * ``borrowers``: A list of all tasks or other entities that currently
424
+ hold a token.
425
+ * ``tasks_waiting``: The number of tasks blocked on this
426
+ :class:`CapacityLimiter`\'s :meth:`acquire` or
427
+ :meth:`acquire_on_behalf_of` methods.
428
+
429
+ """
430
+ return CapacityLimiterStatistics(
431
+ borrowed_tokens=len(self._borrowers),
432
+ total_tokens=self._total_tokens,
433
+ # Use a list instead of a frozenset just in case we start to allow
434
+ # one borrower to hold multiple tokens in the future
435
+ borrowers=list(self._borrowers),
436
+ tasks_waiting=len(self._lot),
437
+ )
438
+
439
+
440
+ @final
441
+ class Semaphore(AsyncContextManagerMixin):
442
+ """A `semaphore <https://en.wikipedia.org/wiki/Semaphore_(programming)>`__.
443
+
444
+ A semaphore holds an integer value, which can be incremented by
445
+ calling :meth:`release` and decremented by calling :meth:`acquire` – but
446
+ the value is never allowed to drop below zero. If the value is zero, then
447
+ :meth:`acquire` will block until someone calls :meth:`release`.
448
+
449
+ If you're looking for a :class:`Semaphore` to limit the number of tasks
450
+ that can access some resource simultaneously, then consider using a
451
+ :class:`CapacityLimiter` instead.
452
+
453
+ This object's interface is similar to, but different from, that of
454
+ :class:`threading.Semaphore`.
455
+
456
+ A :class:`Semaphore` object can be used as an async context manager; it
457
+ blocks on entry but not on exit.
458
+
459
+ Args:
460
+ initial_value (int): A non-negative integer giving semaphore's initial
461
+ value.
462
+ max_value (int or None): If given, makes this a "bounded" semaphore that
463
+ raises an error if the value is about to exceed the given
464
+ ``max_value``.
465
+
466
+ """
467
+
468
+ def __init__(self, initial_value: int, *, max_value: int | None = None) -> None:
469
+ if not isinstance(initial_value, int):
470
+ raise TypeError("initial_value must be an int")
471
+ if initial_value < 0:
472
+ raise ValueError("initial value must be >= 0")
473
+ if max_value is not None:
474
+ if not isinstance(max_value, int):
475
+ raise TypeError("max_value must be None or an int")
476
+ if max_value < initial_value:
477
+ raise ValueError("max_values must be >= initial_value")
478
+
479
+ # Invariants:
480
+ # bool(self._lot) implies self._value == 0
481
+ # (or equivalently: self._value > 0 implies not self._lot)
482
+ self._lot = trio.lowlevel.ParkingLot()
483
+ self._value = initial_value
484
+ self._max_value = max_value
485
+
486
+ def __repr__(self) -> str:
487
+ if self._max_value is None:
488
+ max_value_str = ""
489
+ else:
490
+ max_value_str = f", max_value={self._max_value}"
491
+ return f"<trio.Semaphore({self._value}{max_value_str}) at {id(self):#x}>"
492
+
493
+ @property
494
+ def value(self) -> int:
495
+ """The current value of the semaphore."""
496
+ return self._value
497
+
498
+ @property
499
+ def max_value(self) -> int | None:
500
+ """The maximum allowed value. May be None to indicate no limit."""
501
+ return self._max_value
502
+
503
+ @enable_ki_protection
504
+ def acquire_nowait(self) -> None:
505
+ """Attempt to decrement the semaphore value, without blocking.
506
+
507
+ Raises:
508
+ WouldBlock: if the value is zero.
509
+
510
+ """
511
+ if self._value > 0:
512
+ assert not self._lot
513
+ self._value -= 1
514
+ else:
515
+ raise trio.WouldBlock
516
+
517
+ @enable_ki_protection
518
+ async def acquire(self) -> None:
519
+ """Decrement the semaphore value, blocking if necessary to avoid
520
+ letting it drop below zero.
521
+
522
+ """
523
+ await trio.lowlevel.checkpoint_if_cancelled()
524
+ try:
525
+ self.acquire_nowait()
526
+ except trio.WouldBlock:
527
+ await self._lot.park()
528
+ else:
529
+ await trio.lowlevel.cancel_shielded_checkpoint()
530
+
531
+ @enable_ki_protection
532
+ def release(self) -> None:
533
+ """Increment the semaphore value, possibly waking a task blocked in
534
+ :meth:`acquire`.
535
+
536
+ Raises:
537
+ ValueError: if incrementing the value would cause it to exceed
538
+ :attr:`max_value`.
539
+
540
+ """
541
+ if self._lot:
542
+ assert self._value == 0
543
+ self._lot.unpark(count=1)
544
+ else:
545
+ if self._max_value is not None and self._value == self._max_value:
546
+ raise ValueError("semaphore released too many times")
547
+ self._value += 1
548
+
549
+ def statistics(self) -> ParkingLotStatistics:
550
+ """Return an object containing debugging information.
551
+
552
+ Currently the following fields are defined:
553
+
554
+ * ``tasks_waiting``: The number of tasks blocked on this semaphore's
555
+ :meth:`acquire` method.
556
+
557
+ """
558
+ return self._lot.statistics()
559
+
560
+
561
+ @attrs.frozen
562
+ class LockStatistics:
563
+ """An object containing debugging information for a Lock.
564
+
565
+ Currently the following fields are defined:
566
+
567
+ * ``locked`` (boolean): indicating whether the lock is held.
568
+ * ``owner``: the :class:`trio.lowlevel.Task` currently holding the lock,
569
+ or None if the lock is not held.
570
+ * ``tasks_waiting`` (int): The number of tasks blocked on this lock's
571
+ :meth:`trio.Lock.acquire` method.
572
+
573
+ """
574
+
575
+ locked: bool
576
+ owner: Task | None
577
+ tasks_waiting: int
578
+
579
+
580
+ @attrs.define(eq=False, repr=False, slots=False)
581
+ class _LockImpl(AsyncContextManagerMixin):
582
+ _lot: ParkingLot = attrs.field(factory=ParkingLot, init=False)
583
+ _owner: Task | None = attrs.field(default=None, init=False)
584
+
585
+ def __repr__(self) -> str:
586
+ if self.locked():
587
+ s1 = "locked"
588
+ s2 = f" with {len(self._lot)} waiters"
589
+ else:
590
+ s1 = "unlocked"
591
+ s2 = ""
592
+ return f"<{s1} {self.__class__.__name__} object at {id(self):#x}{s2}>"
593
+
594
+ def locked(self) -> bool:
595
+ """Check whether the lock is currently held.
596
+
597
+ Returns:
598
+ bool: True if the lock is held, False otherwise.
599
+
600
+ """
601
+ return self._owner is not None
602
+
603
+ @enable_ki_protection
604
+ def acquire_nowait(self) -> None:
605
+ """Attempt to acquire the lock, without blocking.
606
+
607
+ Raises:
608
+ WouldBlock: if the lock is held.
609
+
610
+ """
611
+
612
+ task = trio.lowlevel.current_task()
613
+ if self._owner is task:
614
+ raise RuntimeError("attempt to re-acquire an already held Lock")
615
+ elif self._owner is None and not self._lot:
616
+ # No-one owns it
617
+ self._owner = task
618
+ add_parking_lot_breaker(task, self._lot)
619
+ else:
620
+ raise trio.WouldBlock
621
+
622
+ @enable_ki_protection
623
+ async def acquire(self) -> None:
624
+ """Acquire the lock, blocking if necessary.
625
+
626
+ Raises:
627
+ BrokenResourceError: if the owner of the lock exits without releasing.
628
+ """
629
+ await trio.lowlevel.checkpoint_if_cancelled()
630
+ try:
631
+ self.acquire_nowait()
632
+ except trio.WouldBlock:
633
+ try:
634
+ # NOTE: it's important that the contended acquire path is just
635
+ # "_lot.park()", because that's how Condition.wait() acquires the
636
+ # lock as well.
637
+ await self._lot.park()
638
+ except trio.BrokenResourceError:
639
+ raise trio.BrokenResourceError(
640
+ f"Owner of this lock exited without releasing: {self._owner}",
641
+ ) from None
642
+ else:
643
+ await trio.lowlevel.cancel_shielded_checkpoint()
644
+
645
+ @enable_ki_protection
646
+ def release(self) -> None:
647
+ """Release the lock.
648
+
649
+ Raises:
650
+ RuntimeError: if the calling task does not hold the lock.
651
+
652
+ """
653
+ task = trio.lowlevel.current_task()
654
+ if task is not self._owner:
655
+ raise RuntimeError("can't release a Lock you don't own")
656
+ remove_parking_lot_breaker(self._owner, self._lot)
657
+ if self._lot:
658
+ (self._owner,) = self._lot.unpark(count=1)
659
+ add_parking_lot_breaker(self._owner, self._lot)
660
+ else:
661
+ self._owner = None
662
+
663
+ def statistics(self) -> LockStatistics:
664
+ """Return an object containing debugging information.
665
+
666
+ Currently the following fields are defined:
667
+
668
+ * ``locked``: boolean indicating whether the lock is held.
669
+ * ``owner``: the :class:`trio.lowlevel.Task` currently holding the lock,
670
+ or None if the lock is not held.
671
+ * ``tasks_waiting``: The number of tasks blocked on this lock's
672
+ :meth:`acquire` method.
673
+
674
+ """
675
+ return LockStatistics(
676
+ locked=self.locked(),
677
+ owner=self._owner,
678
+ tasks_waiting=len(self._lot),
679
+ )
680
+
681
+
682
+ @final
683
+ class Lock(_LockImpl):
684
+ """A classic `mutex
685
+ <https://en.wikipedia.org/wiki/Lock_(computer_science)>`__.
686
+
687
+ This is a non-reentrant, single-owner lock. Unlike
688
+ :class:`threading.Lock`, only the owner of the lock is allowed to release
689
+ it.
690
+
691
+ A :class:`Lock` object can be used as an async context manager; it
692
+ blocks on entry but not on exit.
693
+
694
+ """
695
+
696
+
697
+ @final
698
+ class StrictFIFOLock(_LockImpl):
699
+ r"""A variant of :class:`Lock` where tasks are guaranteed to acquire the
700
+ lock in strict first-come-first-served order.
701
+
702
+ An example of when this is useful is if you're implementing something like
703
+ :class:`trio.SSLStream` or an HTTP/2 server using `h2
704
+ <https://hyper-h2.readthedocs.io/>`__, where you have multiple concurrent
705
+ tasks that are interacting with a shared state machine, and at
706
+ unpredictable moments the state machine requests that a chunk of data be
707
+ sent over the network. (For example, when using h2 simply reading incoming
708
+ data can occasionally `create outgoing data to send
709
+ <https://http2.github.io/http2-spec/#PING>`__.) The challenge is to make
710
+ sure that these chunks are sent in the correct order, without being
711
+ garbled.
712
+
713
+ One option would be to use a regular :class:`Lock`, and wrap it around
714
+ every interaction with the state machine::
715
+
716
+ # This approach is sometimes workable but often sub-optimal; see below
717
+ async with lock:
718
+ state_machine.do_something()
719
+ if state_machine.has_data_to_send():
720
+ await conn.sendall(state_machine.get_data_to_send())
721
+
722
+ But this can be problematic. If you're using h2 then *usually* reading
723
+ incoming data doesn't create the need to send any data, so we don't want
724
+ to force every task that tries to read from the network to sit and wait
725
+ a potentially long time for ``sendall`` to finish. And in some situations
726
+ this could even potentially cause a deadlock, if the remote peer is
727
+ waiting for you to read some data before it accepts the data you're
728
+ sending.
729
+
730
+ :class:`StrictFIFOLock` provides an alternative. We can rewrite our
731
+ example like::
732
+
733
+ # Note: no awaits between when we start using the state machine and
734
+ # when we block to take the lock!
735
+ state_machine.do_something()
736
+ if state_machine.has_data_to_send():
737
+ # Notice that we fetch the data to send out of the state machine
738
+ # *before* sleeping, so that other tasks won't see it.
739
+ chunk = state_machine.get_data_to_send()
740
+ async with strict_fifo_lock:
741
+ await conn.sendall(chunk)
742
+
743
+ First we do all our interaction with the state machine in a single
744
+ scheduling quantum (notice there are no ``await``\s in there), so it's
745
+ automatically atomic with respect to other tasks. And then if and only if
746
+ we have data to send, we get in line to send it – and
747
+ :class:`StrictFIFOLock` guarantees that each task will send its data in
748
+ the same order that the state machine generated it.
749
+
750
+ Currently, :class:`StrictFIFOLock` is identical to :class:`Lock`,
751
+ but (a) this may not always be true in the future, especially if Trio ever
752
+ implements `more sophisticated scheduling policies
753
+ <https://github.com/python-trio/trio/issues/32>`__, and (b) the above code
754
+ is relying on a pretty subtle property of its lock. Using a
755
+ :class:`StrictFIFOLock` acts as an executable reminder that you're relying
756
+ on this property.
757
+
758
+ """
759
+
760
+
761
+ @attrs.frozen
762
+ class ConditionStatistics:
763
+ r"""An object containing debugging information for a Condition.
764
+
765
+ Currently the following fields are defined:
766
+
767
+ * ``tasks_waiting`` (int): The number of tasks blocked on this condition's
768
+ :meth:`trio.Condition.wait` method.
769
+ * ``lock_statistics``: The result of calling the underlying
770
+ :class:`Lock`\s :meth:`~Lock.statistics` method.
771
+
772
+ """
773
+
774
+ tasks_waiting: int
775
+ lock_statistics: LockStatistics
776
+
777
+
778
+ @final
779
+ class Condition(AsyncContextManagerMixin):
780
+ """A classic `condition variable
781
+ <https://en.wikipedia.org/wiki/Monitor_(synchronization)>`__, similar to
782
+ :class:`threading.Condition`.
783
+
784
+ A :class:`Condition` object can be used as an async context manager to
785
+ acquire the underlying lock; it blocks on entry but not on exit.
786
+
787
+ Args:
788
+ lock (Lock): the lock object to use. If given, must be a
789
+ :class:`trio.Lock`. If None, a new :class:`Lock` will be allocated
790
+ and used.
791
+
792
+ """
793
+
794
+ def __init__(self, lock: Lock | None = None) -> None:
795
+ if lock is None:
796
+ lock = Lock()
797
+ if type(lock) is not Lock:
798
+ raise TypeError("lock must be a trio.Lock")
799
+ self._lock = lock
800
+ self._lot = trio.lowlevel.ParkingLot()
801
+
802
+ def locked(self) -> bool:
803
+ """Check whether the underlying lock is currently held.
804
+
805
+ Returns:
806
+ bool: True if the lock is held, False otherwise.
807
+
808
+ """
809
+ return self._lock.locked()
810
+
811
+ def acquire_nowait(self) -> None:
812
+ """Attempt to acquire the underlying lock, without blocking.
813
+
814
+ Raises:
815
+ WouldBlock: if the lock is currently held.
816
+
817
+ """
818
+ return self._lock.acquire_nowait()
819
+
820
+ async def acquire(self) -> None:
821
+ """Acquire the underlying lock, blocking if necessary.
822
+
823
+ Raises:
824
+ BrokenResourceError: if the owner of the underlying lock exits without releasing.
825
+ """
826
+ await self._lock.acquire()
827
+
828
+ def release(self) -> None:
829
+ """Release the underlying lock."""
830
+ self._lock.release()
831
+
832
+ @enable_ki_protection
833
+ async def wait(self) -> None:
834
+ """Wait for another task to call :meth:`notify` or
835
+ :meth:`notify_all`.
836
+
837
+ When calling this method, you must hold the lock. It releases the lock
838
+ while waiting, and then re-acquires it before waking up.
839
+
840
+ There is a subtlety with how this method interacts with cancellation:
841
+ when cancelled it will block to re-acquire the lock before raising
842
+ :exc:`Cancelled`. This may cause cancellation to be less prompt than
843
+ expected. The advantage is that it makes code like this work::
844
+
845
+ async with condition:
846
+ await condition.wait()
847
+
848
+ If we didn't re-acquire the lock before waking up, and :meth:`wait`
849
+ were cancelled here, then we'd crash in ``condition.__aexit__`` when
850
+ we tried to release the lock we no longer held.
851
+
852
+ Raises:
853
+ RuntimeError: if the calling task does not hold the lock.
854
+ BrokenResourceError: if the owner of the lock exits without releasing, when attempting to re-acquire.
855
+
856
+ """
857
+ if trio.lowlevel.current_task() is not self._lock._owner:
858
+ raise RuntimeError("must hold the lock to wait")
859
+ self.release()
860
+ # NOTE: we go to sleep on self._lot, but we'll wake up on
861
+ # self._lock._lot. That's all that's required to acquire a Lock.
862
+ try:
863
+ await self._lot.park()
864
+ except:
865
+ with trio.CancelScope(shield=True):
866
+ await self.acquire()
867
+ raise
868
+
869
+ def notify(self, n: int = 1) -> None:
870
+ """Wake one or more tasks that are blocked in :meth:`wait`.
871
+
872
+ Args:
873
+ n (int): The number of tasks to wake.
874
+
875
+ Raises:
876
+ RuntimeError: if the calling task does not hold the lock.
877
+
878
+ """
879
+ if trio.lowlevel.current_task() is not self._lock._owner:
880
+ raise RuntimeError("must hold the lock to notify")
881
+ self._lot.repark(self._lock._lot, count=n)
882
+
883
+ def notify_all(self) -> None:
884
+ """Wake all tasks that are currently blocked in :meth:`wait`.
885
+
886
+ Raises:
887
+ RuntimeError: if the calling task does not hold the lock.
888
+
889
+ """
890
+ if trio.lowlevel.current_task() is not self._lock._owner:
891
+ raise RuntimeError("must hold the lock to notify")
892
+ self._lot.repark_all(self._lock._lot)
893
+
894
+ def statistics(self) -> ConditionStatistics:
895
+ r"""Return an object containing debugging information.
896
+
897
+ Currently the following fields are defined:
898
+
899
+ * ``tasks_waiting``: The number of tasks blocked on this condition's
900
+ :meth:`wait` method.
901
+ * ``lock_statistics``: The result of calling the underlying
902
+ :class:`Lock`\s :meth:`~Lock.statistics` method.
903
+
904
+ """
905
+ return ConditionStatistics(
906
+ tasks_waiting=len(self._lot),
907
+ lock_statistics=self._lock.statistics(),
908
+ )