@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,735 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import weakref
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from typing import TypeAlias
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from .. import _core
|
|
11
|
+
from .._core._parking_lot import GLOBAL_PARKING_LOT_BREAKER
|
|
12
|
+
from .._sync import *
|
|
13
|
+
from .._timeouts import sleep_forever
|
|
14
|
+
from ..testing import assert_checkpoints, wait_all_tasks_blocked
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def test_Event() -> None:
|
|
18
|
+
e = Event()
|
|
19
|
+
assert not e.is_set()
|
|
20
|
+
assert e.statistics().tasks_waiting == 0
|
|
21
|
+
|
|
22
|
+
with pytest.warns(
|
|
23
|
+
DeprecationWarning,
|
|
24
|
+
match=r"trio\.Event\.__bool__ is deprecated since Trio 0\.31\.0; use trio\.Event\.is_set instead \(https://github.com/python-trio/trio/issues/3238\)",
|
|
25
|
+
):
|
|
26
|
+
e.__bool__()
|
|
27
|
+
|
|
28
|
+
e.set()
|
|
29
|
+
assert e.is_set()
|
|
30
|
+
with assert_checkpoints():
|
|
31
|
+
await e.wait()
|
|
32
|
+
|
|
33
|
+
e = Event()
|
|
34
|
+
|
|
35
|
+
record = []
|
|
36
|
+
|
|
37
|
+
async def child() -> None:
|
|
38
|
+
record.append("sleeping")
|
|
39
|
+
await e.wait()
|
|
40
|
+
record.append("woken")
|
|
41
|
+
|
|
42
|
+
async with _core.open_nursery() as nursery:
|
|
43
|
+
nursery.start_soon(child)
|
|
44
|
+
nursery.start_soon(child)
|
|
45
|
+
await wait_all_tasks_blocked()
|
|
46
|
+
assert record == ["sleeping", "sleeping"]
|
|
47
|
+
assert e.statistics().tasks_waiting == 2
|
|
48
|
+
e.set()
|
|
49
|
+
await wait_all_tasks_blocked()
|
|
50
|
+
assert record == ["sleeping", "sleeping", "woken", "woken"]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def test_CapacityLimiter() -> None:
|
|
54
|
+
assert CapacityLimiter(0).total_tokens == 0
|
|
55
|
+
with pytest.raises(TypeError):
|
|
56
|
+
CapacityLimiter(1.0)
|
|
57
|
+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
|
|
58
|
+
CapacityLimiter(-1)
|
|
59
|
+
c = CapacityLimiter(2)
|
|
60
|
+
repr(c) # smoke test
|
|
61
|
+
assert c.total_tokens == 2
|
|
62
|
+
assert c.borrowed_tokens == 0
|
|
63
|
+
assert c.available_tokens == 2
|
|
64
|
+
with pytest.raises(RuntimeError):
|
|
65
|
+
c.release()
|
|
66
|
+
assert c.borrowed_tokens == 0
|
|
67
|
+
c.acquire_nowait()
|
|
68
|
+
assert c.borrowed_tokens == 1
|
|
69
|
+
assert c.available_tokens == 1
|
|
70
|
+
|
|
71
|
+
stats = c.statistics()
|
|
72
|
+
assert stats.borrowed_tokens == 1
|
|
73
|
+
assert stats.total_tokens == 2
|
|
74
|
+
assert stats.borrowers == [_core.current_task()]
|
|
75
|
+
assert stats.tasks_waiting == 0
|
|
76
|
+
|
|
77
|
+
# Can't re-acquire when we already have it
|
|
78
|
+
with pytest.raises(RuntimeError):
|
|
79
|
+
c.acquire_nowait()
|
|
80
|
+
assert c.borrowed_tokens == 1
|
|
81
|
+
with pytest.raises(RuntimeError):
|
|
82
|
+
await c.acquire()
|
|
83
|
+
assert c.borrowed_tokens == 1
|
|
84
|
+
|
|
85
|
+
# We can acquire on behalf of someone else though
|
|
86
|
+
with assert_checkpoints():
|
|
87
|
+
await c.acquire_on_behalf_of("someone")
|
|
88
|
+
|
|
89
|
+
# But then we've run out of capacity
|
|
90
|
+
assert c.borrowed_tokens == 2
|
|
91
|
+
with pytest.raises(_core.WouldBlock):
|
|
92
|
+
c.acquire_on_behalf_of_nowait("third party")
|
|
93
|
+
|
|
94
|
+
assert set(c.statistics().borrowers) == {_core.current_task(), "someone"}
|
|
95
|
+
|
|
96
|
+
# Until we release one
|
|
97
|
+
c.release_on_behalf_of(_core.current_task())
|
|
98
|
+
assert c.statistics().borrowers == ["someone"]
|
|
99
|
+
|
|
100
|
+
c.release_on_behalf_of("someone")
|
|
101
|
+
assert c.borrowed_tokens == 0
|
|
102
|
+
with assert_checkpoints():
|
|
103
|
+
async with c:
|
|
104
|
+
assert c.borrowed_tokens == 1
|
|
105
|
+
|
|
106
|
+
async with _core.open_nursery() as nursery:
|
|
107
|
+
await c.acquire_on_behalf_of("value 1")
|
|
108
|
+
await c.acquire_on_behalf_of("value 2")
|
|
109
|
+
nursery.start_soon(c.acquire_on_behalf_of, "value 3")
|
|
110
|
+
await wait_all_tasks_blocked()
|
|
111
|
+
assert c.borrowed_tokens == 2
|
|
112
|
+
assert c.statistics().tasks_waiting == 1
|
|
113
|
+
c.release_on_behalf_of("value 2")
|
|
114
|
+
# Fairness:
|
|
115
|
+
assert c.borrowed_tokens == 2
|
|
116
|
+
with pytest.raises(_core.WouldBlock):
|
|
117
|
+
c.acquire_nowait()
|
|
118
|
+
|
|
119
|
+
c.release_on_behalf_of("value 3")
|
|
120
|
+
c.release_on_behalf_of("value 1")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def test_CapacityLimiter_inf() -> None:
|
|
124
|
+
from math import inf
|
|
125
|
+
|
|
126
|
+
c = CapacityLimiter(inf)
|
|
127
|
+
repr(c) # smoke test
|
|
128
|
+
assert c.total_tokens == inf
|
|
129
|
+
assert c.borrowed_tokens == 0
|
|
130
|
+
assert c.available_tokens == inf
|
|
131
|
+
with pytest.raises(RuntimeError):
|
|
132
|
+
c.release()
|
|
133
|
+
assert c.borrowed_tokens == 0
|
|
134
|
+
c.acquire_nowait()
|
|
135
|
+
assert c.borrowed_tokens == 1
|
|
136
|
+
assert c.available_tokens == inf
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def test_CapacityLimiter_change_total_tokens() -> None:
|
|
140
|
+
c = CapacityLimiter(2)
|
|
141
|
+
|
|
142
|
+
with pytest.raises(TypeError):
|
|
143
|
+
c.total_tokens = 1.0
|
|
144
|
+
|
|
145
|
+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
|
|
146
|
+
c.total_tokens = -1
|
|
147
|
+
|
|
148
|
+
with pytest.raises(ValueError, match=r"^total_tokens must be >= 0$"):
|
|
149
|
+
c.total_tokens = -10
|
|
150
|
+
|
|
151
|
+
assert c.total_tokens == 2
|
|
152
|
+
|
|
153
|
+
async with _core.open_nursery() as nursery:
|
|
154
|
+
for i in range(5):
|
|
155
|
+
nursery.start_soon(c.acquire_on_behalf_of, i)
|
|
156
|
+
await wait_all_tasks_blocked()
|
|
157
|
+
assert set(c.statistics().borrowers) == {0, 1}
|
|
158
|
+
assert c.statistics().tasks_waiting == 3
|
|
159
|
+
c.total_tokens += 2
|
|
160
|
+
assert set(c.statistics().borrowers) == {0, 1, 2, 3}
|
|
161
|
+
assert c.statistics().tasks_waiting == 1
|
|
162
|
+
c.total_tokens -= 3
|
|
163
|
+
assert c.borrowed_tokens == 4
|
|
164
|
+
assert c.total_tokens == 1
|
|
165
|
+
c.release_on_behalf_of(0)
|
|
166
|
+
c.release_on_behalf_of(1)
|
|
167
|
+
c.release_on_behalf_of(2)
|
|
168
|
+
assert set(c.statistics().borrowers) == {3}
|
|
169
|
+
assert c.statistics().tasks_waiting == 1
|
|
170
|
+
c.release_on_behalf_of(3)
|
|
171
|
+
assert set(c.statistics().borrowers) == {4}
|
|
172
|
+
assert c.statistics().tasks_waiting == 0
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# regression test for issue #548
|
|
176
|
+
async def test_CapacityLimiter_memleak_548() -> None:
|
|
177
|
+
limiter = CapacityLimiter(total_tokens=1)
|
|
178
|
+
await limiter.acquire()
|
|
179
|
+
|
|
180
|
+
async with _core.open_nursery() as n:
|
|
181
|
+
n.start_soon(limiter.acquire)
|
|
182
|
+
await wait_all_tasks_blocked() # give it a chance to run the task
|
|
183
|
+
n.cancel_scope.cancel()
|
|
184
|
+
|
|
185
|
+
# if this is 1, the acquire call (despite being killed) is still there in the task, and will
|
|
186
|
+
# leak memory all the while the limiter is active
|
|
187
|
+
assert len(limiter._pending_borrowers) == 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def test_CapacityLimiter_zero_limit_tokens() -> None:
|
|
191
|
+
c = CapacityLimiter(5)
|
|
192
|
+
|
|
193
|
+
assert c.total_tokens == 5
|
|
194
|
+
|
|
195
|
+
async with _core.open_nursery() as nursery:
|
|
196
|
+
c.total_tokens = 0
|
|
197
|
+
|
|
198
|
+
for i in range(5):
|
|
199
|
+
nursery.start_soon(c.acquire_on_behalf_of, i)
|
|
200
|
+
await wait_all_tasks_blocked()
|
|
201
|
+
|
|
202
|
+
assert set(c.statistics().borrowers) == set()
|
|
203
|
+
assert c.statistics().tasks_waiting == 5
|
|
204
|
+
|
|
205
|
+
c.total_tokens = 5
|
|
206
|
+
|
|
207
|
+
assert set(c.statistics().borrowers) == {0, 1, 2, 3, 4}
|
|
208
|
+
|
|
209
|
+
nursery.start_soon(c.acquire_on_behalf_of, 5)
|
|
210
|
+
await wait_all_tasks_blocked()
|
|
211
|
+
|
|
212
|
+
assert c.statistics().tasks_waiting == 1
|
|
213
|
+
|
|
214
|
+
for i in range(5):
|
|
215
|
+
c.release_on_behalf_of(i)
|
|
216
|
+
|
|
217
|
+
assert c.statistics().tasks_waiting == 0
|
|
218
|
+
c.release_on_behalf_of(5)
|
|
219
|
+
|
|
220
|
+
# making sure that zero limit capacity limiter doesn't let any tasks through
|
|
221
|
+
|
|
222
|
+
c.total_tokens = 0
|
|
223
|
+
|
|
224
|
+
with pytest.raises(_core.WouldBlock):
|
|
225
|
+
c.acquire_nowait()
|
|
226
|
+
|
|
227
|
+
nursery.start_soon(c.acquire_on_behalf_of, 6)
|
|
228
|
+
await wait_all_tasks_blocked()
|
|
229
|
+
|
|
230
|
+
assert c.statistics().tasks_waiting == 1
|
|
231
|
+
assert c.statistics().borrowers == []
|
|
232
|
+
|
|
233
|
+
c.total_tokens = 1
|
|
234
|
+
assert c.statistics().tasks_waiting == 0
|
|
235
|
+
assert c.statistics().borrowers == [6]
|
|
236
|
+
c.release_on_behalf_of(6)
|
|
237
|
+
|
|
238
|
+
await c.acquire_on_behalf_of(0) # total_tokens is 1
|
|
239
|
+
|
|
240
|
+
nursery.start_soon(c.acquire_on_behalf_of, 1)
|
|
241
|
+
await wait_all_tasks_blocked()
|
|
242
|
+
c.total_tokens = 0
|
|
243
|
+
|
|
244
|
+
assert c.statistics().borrowers == [0]
|
|
245
|
+
|
|
246
|
+
c.release_on_behalf_of(0)
|
|
247
|
+
await wait_all_tasks_blocked()
|
|
248
|
+
assert c.statistics().borrowers == []
|
|
249
|
+
assert c.statistics().tasks_waiting == 1
|
|
250
|
+
|
|
251
|
+
c.total_tokens = 1
|
|
252
|
+
await wait_all_tasks_blocked()
|
|
253
|
+
assert c.statistics().borrowers == [1]
|
|
254
|
+
assert c.statistics().tasks_waiting == 0
|
|
255
|
+
|
|
256
|
+
c.release_on_behalf_of(1)
|
|
257
|
+
|
|
258
|
+
c.total_tokens = 0
|
|
259
|
+
|
|
260
|
+
nursery.cancel_scope.cancel()
|
|
261
|
+
|
|
262
|
+
assert c.total_tokens == 0
|
|
263
|
+
assert c.statistics().borrowers == []
|
|
264
|
+
assert c._pending_borrowers == {}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def test_Semaphore() -> None:
|
|
268
|
+
with pytest.raises(TypeError):
|
|
269
|
+
Semaphore(1.0) # type: ignore[arg-type]
|
|
270
|
+
with pytest.raises(ValueError, match=r"^initial value must be >= 0$"):
|
|
271
|
+
Semaphore(-1)
|
|
272
|
+
s = Semaphore(1)
|
|
273
|
+
repr(s) # smoke test
|
|
274
|
+
assert s.value == 1
|
|
275
|
+
assert s.max_value is None
|
|
276
|
+
s.release()
|
|
277
|
+
assert s.value == 2
|
|
278
|
+
assert s.statistics().tasks_waiting == 0
|
|
279
|
+
s.acquire_nowait()
|
|
280
|
+
assert s.value == 1
|
|
281
|
+
with assert_checkpoints():
|
|
282
|
+
await s.acquire()
|
|
283
|
+
assert s.value == 0
|
|
284
|
+
with pytest.raises(_core.WouldBlock):
|
|
285
|
+
s.acquire_nowait()
|
|
286
|
+
|
|
287
|
+
s.release()
|
|
288
|
+
assert s.value == 1
|
|
289
|
+
with assert_checkpoints():
|
|
290
|
+
async with s:
|
|
291
|
+
assert s.value == 0
|
|
292
|
+
assert s.value == 1
|
|
293
|
+
s.acquire_nowait()
|
|
294
|
+
|
|
295
|
+
record = []
|
|
296
|
+
|
|
297
|
+
async def do_acquire(s: Semaphore) -> None:
|
|
298
|
+
record.append("started")
|
|
299
|
+
await s.acquire()
|
|
300
|
+
record.append("finished")
|
|
301
|
+
|
|
302
|
+
async with _core.open_nursery() as nursery:
|
|
303
|
+
nursery.start_soon(do_acquire, s)
|
|
304
|
+
await wait_all_tasks_blocked()
|
|
305
|
+
assert record == ["started"]
|
|
306
|
+
assert s.value == 0
|
|
307
|
+
s.release()
|
|
308
|
+
# Fairness:
|
|
309
|
+
assert s.value == 0
|
|
310
|
+
with pytest.raises(_core.WouldBlock):
|
|
311
|
+
s.acquire_nowait()
|
|
312
|
+
assert record == ["started", "finished"]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_Semaphore_bounded() -> None:
|
|
316
|
+
with pytest.raises(TypeError):
|
|
317
|
+
Semaphore(1, max_value=1.0) # type: ignore[arg-type]
|
|
318
|
+
with pytest.raises(ValueError, match=r"^max_values must be >= initial_value$"):
|
|
319
|
+
Semaphore(2, max_value=1)
|
|
320
|
+
bs = Semaphore(1, max_value=1)
|
|
321
|
+
assert bs.max_value == 1
|
|
322
|
+
repr(bs) # smoke test
|
|
323
|
+
with pytest.raises(ValueError, match=r"^semaphore released too many times$"):
|
|
324
|
+
bs.release()
|
|
325
|
+
assert bs.value == 1
|
|
326
|
+
bs.acquire_nowait()
|
|
327
|
+
assert bs.value == 0
|
|
328
|
+
bs.release()
|
|
329
|
+
assert bs.value == 1
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@pytest.mark.parametrize("lockcls", [Lock, StrictFIFOLock], ids=lambda fn: fn.__name__)
|
|
333
|
+
async def test_Lock_and_StrictFIFOLock(
|
|
334
|
+
lockcls: type[Lock | StrictFIFOLock],
|
|
335
|
+
) -> None:
|
|
336
|
+
l = lockcls() # noqa
|
|
337
|
+
assert not l.locked()
|
|
338
|
+
|
|
339
|
+
# make sure locks can be weakref'ed (gh-331)
|
|
340
|
+
r = weakref.ref(l)
|
|
341
|
+
assert r() is l
|
|
342
|
+
|
|
343
|
+
repr(l) # smoke test
|
|
344
|
+
# make sure repr uses the right name for subclasses
|
|
345
|
+
assert lockcls.__name__ in repr(l)
|
|
346
|
+
with assert_checkpoints():
|
|
347
|
+
async with l:
|
|
348
|
+
assert l.locked()
|
|
349
|
+
repr(l) # smoke test (repr branches on locked/unlocked)
|
|
350
|
+
assert not l.locked()
|
|
351
|
+
l.acquire_nowait()
|
|
352
|
+
assert l.locked()
|
|
353
|
+
l.release()
|
|
354
|
+
assert not l.locked()
|
|
355
|
+
with assert_checkpoints():
|
|
356
|
+
await l.acquire()
|
|
357
|
+
assert l.locked()
|
|
358
|
+
l.release()
|
|
359
|
+
assert not l.locked()
|
|
360
|
+
|
|
361
|
+
l.acquire_nowait()
|
|
362
|
+
with pytest.raises(RuntimeError):
|
|
363
|
+
# Error out if we already own the lock
|
|
364
|
+
l.acquire_nowait()
|
|
365
|
+
l.release()
|
|
366
|
+
with pytest.raises(RuntimeError):
|
|
367
|
+
# Error out if we don't own the lock
|
|
368
|
+
l.release()
|
|
369
|
+
|
|
370
|
+
holder_task = None
|
|
371
|
+
|
|
372
|
+
async def holder() -> None:
|
|
373
|
+
nonlocal holder_task
|
|
374
|
+
holder_task = _core.current_task()
|
|
375
|
+
async with l:
|
|
376
|
+
await sleep_forever()
|
|
377
|
+
|
|
378
|
+
async with _core.open_nursery() as nursery:
|
|
379
|
+
assert not l.locked()
|
|
380
|
+
nursery.start_soon(holder)
|
|
381
|
+
await wait_all_tasks_blocked()
|
|
382
|
+
assert l.locked()
|
|
383
|
+
# WouldBlock if someone else holds the lock
|
|
384
|
+
with pytest.raises(_core.WouldBlock):
|
|
385
|
+
l.acquire_nowait()
|
|
386
|
+
# Can't release a lock someone else holds
|
|
387
|
+
with pytest.raises(RuntimeError):
|
|
388
|
+
l.release()
|
|
389
|
+
|
|
390
|
+
statistics = l.statistics()
|
|
391
|
+
print(statistics)
|
|
392
|
+
assert statistics.locked
|
|
393
|
+
assert statistics.owner is holder_task
|
|
394
|
+
assert statistics.tasks_waiting == 0
|
|
395
|
+
|
|
396
|
+
nursery.start_soon(holder)
|
|
397
|
+
await wait_all_tasks_blocked()
|
|
398
|
+
statistics = l.statistics()
|
|
399
|
+
print(statistics)
|
|
400
|
+
assert statistics.tasks_waiting == 1
|
|
401
|
+
|
|
402
|
+
nursery.cancel_scope.cancel()
|
|
403
|
+
|
|
404
|
+
statistics = l.statistics()
|
|
405
|
+
assert not statistics.locked
|
|
406
|
+
assert statistics.owner is None
|
|
407
|
+
assert statistics.tasks_waiting == 0
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
async def test_Condition() -> None:
|
|
411
|
+
with pytest.raises(TypeError):
|
|
412
|
+
Condition(Semaphore(1)) # type: ignore[arg-type]
|
|
413
|
+
with pytest.raises(TypeError):
|
|
414
|
+
Condition(StrictFIFOLock) # type: ignore[arg-type]
|
|
415
|
+
l = Lock() # noqa
|
|
416
|
+
c = Condition(l)
|
|
417
|
+
assert not l.locked()
|
|
418
|
+
assert not c.locked()
|
|
419
|
+
with assert_checkpoints():
|
|
420
|
+
await c.acquire()
|
|
421
|
+
assert l.locked()
|
|
422
|
+
assert c.locked()
|
|
423
|
+
|
|
424
|
+
c = Condition()
|
|
425
|
+
assert not c.locked()
|
|
426
|
+
c.acquire_nowait()
|
|
427
|
+
assert c.locked()
|
|
428
|
+
with pytest.raises(RuntimeError):
|
|
429
|
+
c.acquire_nowait()
|
|
430
|
+
c.release()
|
|
431
|
+
|
|
432
|
+
with pytest.raises(RuntimeError):
|
|
433
|
+
# Can't wait without holding the lock
|
|
434
|
+
await c.wait()
|
|
435
|
+
with pytest.raises(RuntimeError):
|
|
436
|
+
# Can't notify without holding the lock
|
|
437
|
+
c.notify()
|
|
438
|
+
with pytest.raises(RuntimeError):
|
|
439
|
+
# Can't notify without holding the lock
|
|
440
|
+
c.notify_all()
|
|
441
|
+
|
|
442
|
+
finished_waiters = set()
|
|
443
|
+
|
|
444
|
+
async def waiter(i: int) -> None:
|
|
445
|
+
async with c:
|
|
446
|
+
await c.wait()
|
|
447
|
+
finished_waiters.add(i)
|
|
448
|
+
|
|
449
|
+
async with _core.open_nursery() as nursery:
|
|
450
|
+
for i in range(3):
|
|
451
|
+
nursery.start_soon(waiter, i)
|
|
452
|
+
await wait_all_tasks_blocked()
|
|
453
|
+
async with c:
|
|
454
|
+
c.notify()
|
|
455
|
+
assert c.locked()
|
|
456
|
+
await wait_all_tasks_blocked()
|
|
457
|
+
assert finished_waiters == {0}
|
|
458
|
+
async with c:
|
|
459
|
+
c.notify_all()
|
|
460
|
+
await wait_all_tasks_blocked()
|
|
461
|
+
assert finished_waiters == {0, 1, 2}
|
|
462
|
+
|
|
463
|
+
finished_waiters = set()
|
|
464
|
+
async with _core.open_nursery() as nursery:
|
|
465
|
+
for i in range(3):
|
|
466
|
+
nursery.start_soon(waiter, i)
|
|
467
|
+
await wait_all_tasks_blocked()
|
|
468
|
+
async with c:
|
|
469
|
+
c.notify(2)
|
|
470
|
+
statistics = c.statistics()
|
|
471
|
+
print(statistics)
|
|
472
|
+
assert statistics.tasks_waiting == 1
|
|
473
|
+
assert statistics.lock_statistics.tasks_waiting == 2
|
|
474
|
+
# exiting the context manager hands off the lock to the first task
|
|
475
|
+
assert c.statistics().lock_statistics.tasks_waiting == 1
|
|
476
|
+
|
|
477
|
+
await wait_all_tasks_blocked()
|
|
478
|
+
assert finished_waiters == {0, 1}
|
|
479
|
+
|
|
480
|
+
async with c:
|
|
481
|
+
c.notify_all()
|
|
482
|
+
|
|
483
|
+
# After being cancelled still hold the lock (!)
|
|
484
|
+
# (Note that c.__aexit__ checks that we hold the lock as well)
|
|
485
|
+
with _core.CancelScope() as scope:
|
|
486
|
+
async with c:
|
|
487
|
+
scope.cancel()
|
|
488
|
+
try:
|
|
489
|
+
await c.wait()
|
|
490
|
+
finally:
|
|
491
|
+
assert c.locked()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
from .._channel import open_memory_channel
|
|
495
|
+
from .._sync import AsyncContextManagerMixin
|
|
496
|
+
|
|
497
|
+
# Three ways of implementing a Lock in terms of a channel. Used to let us put
|
|
498
|
+
# the channel through the generic lock tests.
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class ChannelLock1(AsyncContextManagerMixin):
|
|
502
|
+
def __init__(self, capacity: int) -> None:
|
|
503
|
+
self.s, self.r = open_memory_channel[None](capacity)
|
|
504
|
+
for _ in range(capacity - 1):
|
|
505
|
+
self.s.send_nowait(None)
|
|
506
|
+
|
|
507
|
+
def acquire_nowait(self) -> None:
|
|
508
|
+
self.s.send_nowait(None)
|
|
509
|
+
|
|
510
|
+
async def acquire(self) -> None:
|
|
511
|
+
await self.s.send(None)
|
|
512
|
+
|
|
513
|
+
def release(self) -> None:
|
|
514
|
+
self.r.receive_nowait()
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
class ChannelLock2(AsyncContextManagerMixin):
|
|
518
|
+
def __init__(self) -> None:
|
|
519
|
+
self.s, self.r = open_memory_channel[None](10)
|
|
520
|
+
self.s.send_nowait(None)
|
|
521
|
+
|
|
522
|
+
def acquire_nowait(self) -> None:
|
|
523
|
+
self.r.receive_nowait()
|
|
524
|
+
|
|
525
|
+
async def acquire(self) -> None:
|
|
526
|
+
await self.r.receive()
|
|
527
|
+
|
|
528
|
+
def release(self) -> None:
|
|
529
|
+
self.s.send_nowait(None)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class ChannelLock3(AsyncContextManagerMixin):
|
|
533
|
+
def __init__(self) -> None:
|
|
534
|
+
self.s, self.r = open_memory_channel[None](0)
|
|
535
|
+
# self.acquired is true when one task acquires the lock and
|
|
536
|
+
# only becomes false when it's released and no tasks are
|
|
537
|
+
# waiting to acquire.
|
|
538
|
+
self.acquired = False
|
|
539
|
+
|
|
540
|
+
def acquire_nowait(self) -> None:
|
|
541
|
+
assert not self.acquired
|
|
542
|
+
self.acquired = True
|
|
543
|
+
|
|
544
|
+
async def acquire(self) -> None:
|
|
545
|
+
if self.acquired:
|
|
546
|
+
await self.s.send(None)
|
|
547
|
+
else:
|
|
548
|
+
self.acquired = True
|
|
549
|
+
await _core.checkpoint()
|
|
550
|
+
|
|
551
|
+
def release(self) -> None:
|
|
552
|
+
try:
|
|
553
|
+
self.r.receive_nowait()
|
|
554
|
+
except _core.WouldBlock:
|
|
555
|
+
assert self.acquired
|
|
556
|
+
self.acquired = False
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
lock_factories = [
|
|
560
|
+
lambda: CapacityLimiter(1),
|
|
561
|
+
lambda: Semaphore(1),
|
|
562
|
+
Lock,
|
|
563
|
+
StrictFIFOLock,
|
|
564
|
+
lambda: ChannelLock1(10),
|
|
565
|
+
lambda: ChannelLock1(1),
|
|
566
|
+
ChannelLock2,
|
|
567
|
+
ChannelLock3,
|
|
568
|
+
]
|
|
569
|
+
lock_factory_names = [
|
|
570
|
+
"CapacityLimiter(1)",
|
|
571
|
+
"Semaphore(1)",
|
|
572
|
+
"Lock",
|
|
573
|
+
"StrictFIFOLock",
|
|
574
|
+
"ChannelLock1(10)",
|
|
575
|
+
"ChannelLock1(1)",
|
|
576
|
+
"ChannelLock2",
|
|
577
|
+
"ChannelLock3",
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
generic_lock_test = pytest.mark.parametrize(
|
|
581
|
+
"lock_factory",
|
|
582
|
+
lock_factories,
|
|
583
|
+
ids=lock_factory_names,
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
LockLike: TypeAlias = (
|
|
587
|
+
CapacityLimiter
|
|
588
|
+
| Semaphore
|
|
589
|
+
| Lock
|
|
590
|
+
| StrictFIFOLock
|
|
591
|
+
| ChannelLock1
|
|
592
|
+
| ChannelLock2
|
|
593
|
+
| ChannelLock3
|
|
594
|
+
)
|
|
595
|
+
LockFactory: TypeAlias = Callable[[], LockLike]
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
# Spawn a bunch of workers that take a lock and then yield; make sure that
|
|
599
|
+
# only one worker is ever in the critical section at a time.
|
|
600
|
+
@generic_lock_test
|
|
601
|
+
async def test_generic_lock_exclusion(lock_factory: LockFactory) -> None:
|
|
602
|
+
LOOPS = 10
|
|
603
|
+
WORKERS = 5
|
|
604
|
+
in_critical_section = False
|
|
605
|
+
acquires = 0
|
|
606
|
+
|
|
607
|
+
async def worker(lock_like: LockLike) -> None:
|
|
608
|
+
nonlocal in_critical_section, acquires
|
|
609
|
+
for _ in range(LOOPS):
|
|
610
|
+
async with lock_like:
|
|
611
|
+
acquires += 1
|
|
612
|
+
assert not in_critical_section
|
|
613
|
+
in_critical_section = True
|
|
614
|
+
await _core.checkpoint()
|
|
615
|
+
await _core.checkpoint()
|
|
616
|
+
assert in_critical_section
|
|
617
|
+
in_critical_section = False
|
|
618
|
+
|
|
619
|
+
async with _core.open_nursery() as nursery:
|
|
620
|
+
lock_like = lock_factory()
|
|
621
|
+
for _ in range(WORKERS):
|
|
622
|
+
nursery.start_soon(worker, lock_like)
|
|
623
|
+
assert not in_critical_section
|
|
624
|
+
assert acquires == LOOPS * WORKERS
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# Several workers queue on the same lock; make sure they each get it, in
|
|
628
|
+
# order.
|
|
629
|
+
@generic_lock_test
|
|
630
|
+
async def test_generic_lock_fifo_fairness(lock_factory: LockFactory) -> None:
|
|
631
|
+
initial_order = []
|
|
632
|
+
record = []
|
|
633
|
+
LOOPS = 5
|
|
634
|
+
|
|
635
|
+
async def loopy(name: int, lock_like: LockLike) -> None:
|
|
636
|
+
# Record the order each task was initially scheduled in
|
|
637
|
+
initial_order.append(name)
|
|
638
|
+
for _ in range(LOOPS):
|
|
639
|
+
async with lock_like:
|
|
640
|
+
record.append(name)
|
|
641
|
+
|
|
642
|
+
lock_like = lock_factory()
|
|
643
|
+
async with _core.open_nursery() as nursery:
|
|
644
|
+
nursery.start_soon(loopy, 1, lock_like)
|
|
645
|
+
nursery.start_soon(loopy, 2, lock_like)
|
|
646
|
+
nursery.start_soon(loopy, 3, lock_like)
|
|
647
|
+
# The first three could be in any order due to scheduling randomness,
|
|
648
|
+
# but after that they should repeat in the same order
|
|
649
|
+
for i in range(LOOPS):
|
|
650
|
+
assert record[3 * i : 3 * (i + 1)] == initial_order
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
@generic_lock_test
|
|
654
|
+
async def test_generic_lock_acquire_nowait_blocks_acquire(
|
|
655
|
+
lock_factory: LockFactory,
|
|
656
|
+
) -> None:
|
|
657
|
+
lock_like = lock_factory()
|
|
658
|
+
|
|
659
|
+
record = []
|
|
660
|
+
|
|
661
|
+
async def lock_taker() -> None:
|
|
662
|
+
record.append("started")
|
|
663
|
+
async with lock_like:
|
|
664
|
+
pass
|
|
665
|
+
record.append("finished")
|
|
666
|
+
|
|
667
|
+
async with _core.open_nursery() as nursery:
|
|
668
|
+
lock_like.acquire_nowait()
|
|
669
|
+
nursery.start_soon(lock_taker)
|
|
670
|
+
await wait_all_tasks_blocked()
|
|
671
|
+
assert record == ["started"]
|
|
672
|
+
lock_like.release()
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
async def test_lock_acquire_unowned_lock() -> None:
|
|
676
|
+
"""Test that trying to acquire a lock whose owner has exited raises an error.
|
|
677
|
+
see https://github.com/python-trio/trio/issues/3035
|
|
678
|
+
"""
|
|
679
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|
|
680
|
+
lock = trio.Lock()
|
|
681
|
+
async with trio.open_nursery() as nursery:
|
|
682
|
+
nursery.start_soon(lock.acquire)
|
|
683
|
+
owner_str = re.escape(str(lock._lot.broken_by[0]))
|
|
684
|
+
with pytest.raises(
|
|
685
|
+
trio.BrokenResourceError,
|
|
686
|
+
match=f"^Owner of this lock exited without releasing: {owner_str}$",
|
|
687
|
+
):
|
|
688
|
+
await lock.acquire()
|
|
689
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
async def test_lock_multiple_acquire() -> None:
|
|
693
|
+
"""Test for error if awaiting on a lock whose owner exits without releasing.
|
|
694
|
+
see https://github.com/python-trio/trio/issues/3035"""
|
|
695
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|
|
696
|
+
lock = trio.Lock()
|
|
697
|
+
with pytest.RaisesGroup(
|
|
698
|
+
pytest.RaisesExc(
|
|
699
|
+
trio.BrokenResourceError,
|
|
700
|
+
match="^Owner of this lock exited without releasing: ",
|
|
701
|
+
),
|
|
702
|
+
):
|
|
703
|
+
async with trio.open_nursery() as nursery:
|
|
704
|
+
nursery.start_soon(lock.acquire)
|
|
705
|
+
nursery.start_soon(lock.acquire)
|
|
706
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
async def test_lock_handover() -> None:
|
|
710
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|
|
711
|
+
child_task: Task | None = None
|
|
712
|
+
lock = trio.Lock()
|
|
713
|
+
|
|
714
|
+
# this task acquires the lock
|
|
715
|
+
lock.acquire_nowait()
|
|
716
|
+
assert {
|
|
717
|
+
_core.current_task(): [
|
|
718
|
+
lock._lot,
|
|
719
|
+
],
|
|
720
|
+
} == GLOBAL_PARKING_LOT_BREAKER
|
|
721
|
+
|
|
722
|
+
async with trio.open_nursery() as nursery:
|
|
723
|
+
nursery.start_soon(lock.acquire)
|
|
724
|
+
await wait_all_tasks_blocked()
|
|
725
|
+
|
|
726
|
+
# hand over the lock to the child task
|
|
727
|
+
lock.release()
|
|
728
|
+
|
|
729
|
+
# check values, and get the identifier out of the dict for later check
|
|
730
|
+
assert len(GLOBAL_PARKING_LOT_BREAKER) == 1
|
|
731
|
+
child_task = next(iter(GLOBAL_PARKING_LOT_BREAKER))
|
|
732
|
+
assert GLOBAL_PARKING_LOT_BREAKER[child_task] == [lock._lot]
|
|
733
|
+
|
|
734
|
+
assert lock._lot.broken_by == [child_task]
|
|
735
|
+
assert not GLOBAL_PARKING_LOT_BREAKER
|