ez-a-sync 0.32.29__cp310-cp310-win_amd64.whl
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.
Potentially problematic release.
This version of ez-a-sync might be problematic. Click here for more details.
- a_sync/ENVIRONMENT_VARIABLES.py +42 -0
- a_sync/__init__.pxd +2 -0
- a_sync/__init__.py +145 -0
- a_sync/_smart.c +22803 -0
- a_sync/_smart.cp310-win_amd64.pyd +0 -0
- a_sync/_smart.pxd +2 -0
- a_sync/_smart.pyi +202 -0
- a_sync/_smart.pyx +674 -0
- a_sync/_typing.py +258 -0
- a_sync/a_sync/__init__.py +60 -0
- a_sync/a_sync/_descriptor.c +20528 -0
- a_sync/a_sync/_descriptor.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/_descriptor.pyi +33 -0
- a_sync/a_sync/_descriptor.pyx +422 -0
- a_sync/a_sync/_flags.c +6074 -0
- a_sync/a_sync/_flags.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/_flags.pxd +3 -0
- a_sync/a_sync/_flags.pyx +92 -0
- a_sync/a_sync/_helpers.c +14521 -0
- a_sync/a_sync/_helpers.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/_helpers.pxd +3 -0
- a_sync/a_sync/_helpers.pyi +10 -0
- a_sync/a_sync/_helpers.pyx +167 -0
- a_sync/a_sync/_kwargs.c +12194 -0
- a_sync/a_sync/_kwargs.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/_kwargs.pxd +2 -0
- a_sync/a_sync/_kwargs.pyx +64 -0
- a_sync/a_sync/_meta.py +210 -0
- a_sync/a_sync/abstract.c +12411 -0
- a_sync/a_sync/abstract.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/abstract.pyi +141 -0
- a_sync/a_sync/abstract.pyx +221 -0
- a_sync/a_sync/base.c +14932 -0
- a_sync/a_sync/base.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/base.pyi +60 -0
- a_sync/a_sync/base.pyx +271 -0
- a_sync/a_sync/config.py +168 -0
- a_sync/a_sync/decorator.py +651 -0
- a_sync/a_sync/flags.c +5272 -0
- a_sync/a_sync/flags.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/flags.pxd +72 -0
- a_sync/a_sync/flags.pyi +74 -0
- a_sync/a_sync/flags.pyx +72 -0
- a_sync/a_sync/function.c +37846 -0
- a_sync/a_sync/function.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/function.pxd +28 -0
- a_sync/a_sync/function.pyi +571 -0
- a_sync/a_sync/function.pyx +1381 -0
- a_sync/a_sync/method.c +29774 -0
- a_sync/a_sync/method.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/method.pxd +9 -0
- a_sync/a_sync/method.pyi +525 -0
- a_sync/a_sync/method.pyx +1023 -0
- a_sync/a_sync/modifiers/__init__.pxd +1 -0
- a_sync/a_sync/modifiers/__init__.py +101 -0
- a_sync/a_sync/modifiers/cache/__init__.py +160 -0
- a_sync/a_sync/modifiers/cache/memory.py +165 -0
- a_sync/a_sync/modifiers/limiter.py +132 -0
- a_sync/a_sync/modifiers/manager.c +16149 -0
- a_sync/a_sync/modifiers/manager.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/modifiers/manager.pxd +5 -0
- a_sync/a_sync/modifiers/manager.pyi +219 -0
- a_sync/a_sync/modifiers/manager.pyx +299 -0
- a_sync/a_sync/modifiers/semaphores.py +173 -0
- a_sync/a_sync/property.c +27260 -0
- a_sync/a_sync/property.cp310-win_amd64.pyd +0 -0
- a_sync/a_sync/property.pyi +376 -0
- a_sync/a_sync/property.pyx +819 -0
- a_sync/a_sync/singleton.py +63 -0
- a_sync/aliases.py +3 -0
- a_sync/async_property/__init__.pxd +1 -0
- a_sync/async_property/__init__.py +1 -0
- a_sync/async_property/cached.c +20386 -0
- a_sync/async_property/cached.cp310-win_amd64.pyd +0 -0
- a_sync/async_property/cached.pxd +10 -0
- a_sync/async_property/cached.pyi +45 -0
- a_sync/async_property/cached.pyx +178 -0
- a_sync/async_property/proxy.c +34654 -0
- a_sync/async_property/proxy.cp310-win_amd64.pyd +0 -0
- a_sync/async_property/proxy.pxd +2 -0
- a_sync/async_property/proxy.pyi +124 -0
- a_sync/async_property/proxy.pyx +474 -0
- a_sync/asyncio/__init__.pxd +6 -0
- a_sync/asyncio/__init__.py +164 -0
- a_sync/asyncio/as_completed.c +18841 -0
- a_sync/asyncio/as_completed.cp310-win_amd64.pyd +0 -0
- a_sync/asyncio/as_completed.pxd +8 -0
- a_sync/asyncio/as_completed.pyi +109 -0
- a_sync/asyncio/as_completed.pyx +269 -0
- a_sync/asyncio/create_task.c +15902 -0
- a_sync/asyncio/create_task.cp310-win_amd64.pyd +0 -0
- a_sync/asyncio/create_task.pxd +2 -0
- a_sync/asyncio/create_task.pyi +51 -0
- a_sync/asyncio/create_task.pyx +271 -0
- a_sync/asyncio/gather.c +16679 -0
- a_sync/asyncio/gather.cp310-win_amd64.pyd +0 -0
- a_sync/asyncio/gather.pyi +107 -0
- a_sync/asyncio/gather.pyx +218 -0
- a_sync/asyncio/igather.c +12676 -0
- a_sync/asyncio/igather.cp310-win_amd64.pyd +0 -0
- a_sync/asyncio/igather.pxd +1 -0
- a_sync/asyncio/igather.pyi +7 -0
- a_sync/asyncio/igather.pyx +182 -0
- a_sync/asyncio/sleep.c +9593 -0
- a_sync/asyncio/sleep.cp310-win_amd64.pyd +0 -0
- a_sync/asyncio/sleep.pyi +14 -0
- a_sync/asyncio/sleep.pyx +49 -0
- a_sync/debugging.c +15362 -0
- a_sync/debugging.cp310-win_amd64.pyd +0 -0
- a_sync/debugging.pyi +76 -0
- a_sync/debugging.pyx +107 -0
- a_sync/exceptions.c +13312 -0
- a_sync/exceptions.cp310-win_amd64.pyd +0 -0
- a_sync/exceptions.pyi +376 -0
- a_sync/exceptions.pyx +446 -0
- a_sync/executor.py +619 -0
- a_sync/functools.c +12738 -0
- a_sync/functools.cp310-win_amd64.pyd +0 -0
- a_sync/functools.pxd +7 -0
- a_sync/functools.pyi +33 -0
- a_sync/functools.pyx +139 -0
- a_sync/future.py +1497 -0
- a_sync/iter.c +37271 -0
- a_sync/iter.cp310-win_amd64.pyd +0 -0
- a_sync/iter.pxd +11 -0
- a_sync/iter.pyi +370 -0
- a_sync/iter.pyx +981 -0
- a_sync/primitives/__init__.pxd +1 -0
- a_sync/primitives/__init__.py +53 -0
- a_sync/primitives/_debug.c +15757 -0
- a_sync/primitives/_debug.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/_debug.pxd +12 -0
- a_sync/primitives/_debug.pyi +52 -0
- a_sync/primitives/_debug.pyx +223 -0
- a_sync/primitives/_loggable.c +11529 -0
- a_sync/primitives/_loggable.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/_loggable.pxd +4 -0
- a_sync/primitives/_loggable.pyi +66 -0
- a_sync/primitives/_loggable.pyx +102 -0
- a_sync/primitives/locks/__init__.pxd +8 -0
- a_sync/primitives/locks/__init__.py +17 -0
- a_sync/primitives/locks/counter.c +17679 -0
- a_sync/primitives/locks/counter.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/locks/counter.pxd +12 -0
- a_sync/primitives/locks/counter.pyi +151 -0
- a_sync/primitives/locks/counter.pyx +260 -0
- a_sync/primitives/locks/event.c +17063 -0
- a_sync/primitives/locks/event.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/locks/event.pxd +22 -0
- a_sync/primitives/locks/event.pyi +43 -0
- a_sync/primitives/locks/event.pyx +185 -0
- a_sync/primitives/locks/prio_semaphore.c +25590 -0
- a_sync/primitives/locks/prio_semaphore.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/locks/prio_semaphore.pxd +25 -0
- a_sync/primitives/locks/prio_semaphore.pyi +217 -0
- a_sync/primitives/locks/prio_semaphore.pyx +597 -0
- a_sync/primitives/locks/semaphore.c +26509 -0
- a_sync/primitives/locks/semaphore.cp310-win_amd64.pyd +0 -0
- a_sync/primitives/locks/semaphore.pxd +21 -0
- a_sync/primitives/locks/semaphore.pyi +197 -0
- a_sync/primitives/locks/semaphore.pyx +454 -0
- a_sync/primitives/queue.py +1022 -0
- a_sync/py.typed +0 -0
- a_sync/sphinx/__init__.py +3 -0
- a_sync/sphinx/ext.py +289 -0
- a_sync/task.py +932 -0
- a_sync/utils/__init__.py +105 -0
- a_sync/utils/iterators.py +297 -0
- a_sync/utils/repr.c +15799 -0
- a_sync/utils/repr.cp310-win_amd64.pyd +0 -0
- a_sync/utils/repr.pyi +2 -0
- a_sync/utils/repr.pyx +73 -0
- ez_a_sync-0.32.29.dist-info/METADATA +367 -0
- ez_a_sync-0.32.29.dist-info/RECORD +177 -0
- ez_a_sync-0.32.29.dist-info/WHEEL +5 -0
- ez_a_sync-0.32.29.dist-info/licenses/LICENSE.txt +17 -0
- ez_a_sync-0.32.29.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# cython: boundscheck=False
|
|
2
|
+
"""
|
|
3
|
+
This module provides priority-based semaphore implementations. These semaphores allow
|
|
4
|
+
waiters to be assigned priorities, ensuring that higher priority waiters are
|
|
5
|
+
processed before lower priority ones.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import collections
|
|
10
|
+
import heapq
|
|
11
|
+
from logging import getLogger
|
|
12
|
+
from typing import List, Literal, Optional, Protocol, Set, Type, TypeVar
|
|
13
|
+
|
|
14
|
+
from cpython.unicode cimport PyUnicode_CompareWithASCIIString
|
|
15
|
+
|
|
16
|
+
from a_sync.primitives.locks.semaphore cimport Semaphore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# cdef collections
|
|
20
|
+
cdef object deque = collections.deque
|
|
21
|
+
del collections
|
|
22
|
+
|
|
23
|
+
# cdef heapq
|
|
24
|
+
cdef object heappush = heapq.heappush
|
|
25
|
+
cdef object heappop = heapq.heappop
|
|
26
|
+
del heapq
|
|
27
|
+
|
|
28
|
+
# cdef logging
|
|
29
|
+
cdef public object logger = getLogger(__name__)
|
|
30
|
+
cdef object c_logger = logger
|
|
31
|
+
cdef object DEBUG = 10
|
|
32
|
+
cdef object _logger_log = logger._log
|
|
33
|
+
cdef object _logger_is_enabled = logger.isEnabledFor
|
|
34
|
+
del getLogger
|
|
35
|
+
|
|
36
|
+
class Priority(Protocol):
|
|
37
|
+
def __lt__(self, other) -> bool: ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
PT = TypeVar("PT", bound=Priority)
|
|
41
|
+
|
|
42
|
+
CM = TypeVar("CM", bound="_AbstractPrioritySemaphoreContextManager[Priority]")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
cdef class _AbstractPrioritySemaphore(Semaphore):
|
|
46
|
+
"""
|
|
47
|
+
A semaphore that allows prioritization of waiters.
|
|
48
|
+
|
|
49
|
+
This semaphore manages waiters with associated priorities, ensuring that waiters with higher
|
|
50
|
+
priorities are processed before those with lower priorities. Subclasses must define the
|
|
51
|
+
`_top_priority` attribute to specify the default top priority behavior.
|
|
52
|
+
|
|
53
|
+
The `_context_manager_class` attribute should specify the class used for managing semaphore contexts.
|
|
54
|
+
|
|
55
|
+
See Also:
|
|
56
|
+
:class:`PrioritySemaphore` for an implementation using numeric priorities.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __cinit__(self) -> None:
|
|
60
|
+
self._context_managers = {}
|
|
61
|
+
"""A dictionary mapping priorities to their context managers."""
|
|
62
|
+
|
|
63
|
+
self._Semaphore__waiters = []
|
|
64
|
+
"""A heap queue of context managers, sorted by priority."""
|
|
65
|
+
|
|
66
|
+
# NOTE: This should (hopefully) be temporary
|
|
67
|
+
self._potential_lost_waiters = set()
|
|
68
|
+
"""A list of futures representing waiters that might have been lost."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
context_manager_class: Type[_AbstractPrioritySemaphoreContextManager],
|
|
73
|
+
top_priority: object,
|
|
74
|
+
value: int = 1,
|
|
75
|
+
*,
|
|
76
|
+
name: Optional[str] = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Initializes the priority semaphore.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
value: The initial capacity of the semaphore.
|
|
82
|
+
name: An optional name for the semaphore, used for debugging.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> semaphore = _AbstractPrioritySemaphore(5, name="test_semaphore")
|
|
86
|
+
"""
|
|
87
|
+
# context manager class is some temporary hacky shit, just ignore this
|
|
88
|
+
Semaphore.__init__(self, value, name=name)
|
|
89
|
+
|
|
90
|
+
self._capacity = value
|
|
91
|
+
"""The initial capacity of the semaphore."""
|
|
92
|
+
|
|
93
|
+
self._top_priority = top_priority
|
|
94
|
+
self._top_priority_manager = context_manager_class(
|
|
95
|
+
self, top_priority, name=self.name
|
|
96
|
+
)
|
|
97
|
+
self._context_manager_class = context_manager_class
|
|
98
|
+
|
|
99
|
+
def __repr__(self) -> str:
|
|
100
|
+
"""Returns a string representation of the semaphore."""
|
|
101
|
+
return f"<{self.__class__.__name__} name={self.name} capacity={self._capacity} value={self._Semaphore__value} waiters={self._count_waiters()}>"
|
|
102
|
+
|
|
103
|
+
async def __aenter__(self) -> None:
|
|
104
|
+
"""Enters the semaphore context, acquiring it with the top priority.
|
|
105
|
+
|
|
106
|
+
This method is part of the asynchronous context management protocol.
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
110
|
+
>>> async with semaphore:
|
|
111
|
+
... await do_stuff()
|
|
112
|
+
"""
|
|
113
|
+
await self._top_priority_manager.acquire()
|
|
114
|
+
|
|
115
|
+
async def __aexit__(self, *_) -> None:
|
|
116
|
+
"""Exits the semaphore context, releasing it with the top priority.
|
|
117
|
+
|
|
118
|
+
This method is part of the asynchronous context management protocol.
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
122
|
+
>>> async with semaphore:
|
|
123
|
+
... await do_stuff()
|
|
124
|
+
"""
|
|
125
|
+
self._top_priority_manager.release()
|
|
126
|
+
|
|
127
|
+
cpdef object acquire(self):
|
|
128
|
+
"""Acquires the semaphore with the top priority.
|
|
129
|
+
|
|
130
|
+
This method overrides :meth:`Semaphore.acquire` to handle priority-based logic.
|
|
131
|
+
|
|
132
|
+
Examples:
|
|
133
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
134
|
+
>>> await semaphore.acquire()
|
|
135
|
+
"""
|
|
136
|
+
return self._top_priority_manager.acquire()
|
|
137
|
+
|
|
138
|
+
def __getitem__(
|
|
139
|
+
self, priority: Optional[PT]
|
|
140
|
+
) -> "_AbstractPrioritySemaphoreContextManager[PT]":
|
|
141
|
+
"""Gets the context manager for a given priority.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
priority: The priority for which to get the context manager. If None, uses the top priority.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
The context manager associated with the given priority.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
151
|
+
>>> context_manager = semaphore[priority]
|
|
152
|
+
"""
|
|
153
|
+
return self.c_getitem(priority)
|
|
154
|
+
|
|
155
|
+
cdef _AbstractPrioritySemaphoreContextManager c_getitem(self, object priority):
|
|
156
|
+
cdef _AbstractPrioritySemaphoreContextManager context_manager
|
|
157
|
+
cdef dict[object, _AbstractPrioritySemaphoreContextManager] context_managers
|
|
158
|
+
|
|
159
|
+
if priority is None or priority == self._top_priority:
|
|
160
|
+
return self._top_priority_manager
|
|
161
|
+
|
|
162
|
+
context_managers = self._context_managers
|
|
163
|
+
context_manager = context_managers.get(priority)
|
|
164
|
+
if context_manager is None:
|
|
165
|
+
context_manager = self._context_manager_class(
|
|
166
|
+
self, priority, name=self.name
|
|
167
|
+
)
|
|
168
|
+
heappush(self._Semaphore__waiters, context_manager)
|
|
169
|
+
context_managers[priority] = context_manager
|
|
170
|
+
return context_manager
|
|
171
|
+
|
|
172
|
+
cpdef bint locked(self):
|
|
173
|
+
"""Checks if the semaphore is locked.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if the semaphore cannot be acquired immediately, False otherwise.
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
180
|
+
>>> semaphore.locked()
|
|
181
|
+
"""
|
|
182
|
+
cdef list waiters
|
|
183
|
+
if self._Semaphore__value == 0:
|
|
184
|
+
return True
|
|
185
|
+
for cm in self._context_managers.values():
|
|
186
|
+
waiters = (<Semaphore>cm).__waiters
|
|
187
|
+
for waiter in waiters:
|
|
188
|
+
if _is_not_cancelled(waiter):
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
cdef dict[object, Py_ssize_t] _count_waiters(self):
|
|
193
|
+
"""Counts the number of waiters for each priority.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
A dictionary mapping each priority to the number of waiters.
|
|
197
|
+
|
|
198
|
+
Examples:
|
|
199
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
200
|
+
>>> semaphore._count_waiters()
|
|
201
|
+
"""
|
|
202
|
+
cdef _AbstractPrioritySemaphoreContextManager manager
|
|
203
|
+
cdef list[_AbstractPrioritySemaphoreContextManager] waiters = self._Semaphore__waiters
|
|
204
|
+
return {manager._priority: len(manager._Semaphore__waiters) for manager in sorted(waiters)}
|
|
205
|
+
|
|
206
|
+
cpdef void _wake_up_next(self):
|
|
207
|
+
"""Wakes up the next waiter in line.
|
|
208
|
+
|
|
209
|
+
This method handles the waking of waiters based on priority. It includes an emergency
|
|
210
|
+
procedure to handle potential lost waiters, ensuring that no waiter is left indefinitely
|
|
211
|
+
waiting.
|
|
212
|
+
|
|
213
|
+
The emergency procedure is a temporary measure to address potential issues with lost waiters.
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
217
|
+
>>> semaphore._wake_up_next()
|
|
218
|
+
"""
|
|
219
|
+
cdef _AbstractPrioritySemaphoreContextManager manager
|
|
220
|
+
cdef object manager_waiters, get_next
|
|
221
|
+
cdef Py_ssize_t start_len, end_len
|
|
222
|
+
cdef set potential_lost_waiters
|
|
223
|
+
cdef bint woke_up, debug_logs
|
|
224
|
+
|
|
225
|
+
manager = self._top_priority_manager
|
|
226
|
+
manager_waiters = manager._Semaphore__waiters
|
|
227
|
+
potential_lost_waiters = self._potential_lost_waiters
|
|
228
|
+
debug_logs = _logger_is_enabled(DEBUG)
|
|
229
|
+
if manager_waiters:
|
|
230
|
+
get_next = manager_waiters.popleft
|
|
231
|
+
while manager_waiters:
|
|
232
|
+
waiter = get_next()
|
|
233
|
+
potential_lost_waiters.discard(waiter)
|
|
234
|
+
if _is_not_done(waiter):
|
|
235
|
+
waiter.set_result(None)
|
|
236
|
+
if debug_logs:
|
|
237
|
+
log_debug("woke up %s", (waiter, ))
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
while manager_waiters:
|
|
241
|
+
waiter = get_next()
|
|
242
|
+
potential_lost_waiters.discard(waiter)
|
|
243
|
+
if _is_not_done(waiter):
|
|
244
|
+
waiter.set_result(None)
|
|
245
|
+
if debug_logs:
|
|
246
|
+
log_debug("woke up %s", (waiter, ))
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
cdef list self_waiters = self._Semaphore__waiters
|
|
250
|
+
while self_waiters:
|
|
251
|
+
manager = heappop(self_waiters)
|
|
252
|
+
if len(manager) == 0:
|
|
253
|
+
# There are no more waiters, get rid of the empty manager
|
|
254
|
+
if debug_logs:
|
|
255
|
+
log_debug(
|
|
256
|
+
"manager %s has no more waiters, popping from %s",
|
|
257
|
+
(manager._repr_no_parent_(), self),
|
|
258
|
+
)
|
|
259
|
+
self._context_managers.pop(manager._priority)
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
woke_up = False
|
|
263
|
+
start_len = len(manager)
|
|
264
|
+
|
|
265
|
+
manager_waiters = manager._Semaphore__waiters
|
|
266
|
+
if debug_logs:
|
|
267
|
+
log_debug("waking up next for %s", (manager._repr_no_parent_(), ))
|
|
268
|
+
if not manager_waiters:
|
|
269
|
+
log_debug("not manager._Semaphore__waiters", ())
|
|
270
|
+
|
|
271
|
+
if manager_waiters:
|
|
272
|
+
get_next = manager_waiters.popleft
|
|
273
|
+
waiter = get_next()
|
|
274
|
+
potential_lost_waiters.discard(waiter)
|
|
275
|
+
if _is_not_done(waiter):
|
|
276
|
+
waiter.set_result(None)
|
|
277
|
+
woke_up = True
|
|
278
|
+
if debug_logs:
|
|
279
|
+
log_debug("woke up %s", (waiter, ))
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
if not woke_up:
|
|
283
|
+
while manager_waiters:
|
|
284
|
+
waiter = get_next()
|
|
285
|
+
potential_lost_waiters.discard(waiter)
|
|
286
|
+
if _is_not_done(waiter):
|
|
287
|
+
waiter.set_result(None)
|
|
288
|
+
woke_up = True
|
|
289
|
+
if debug_logs:
|
|
290
|
+
log_debug("woke up %s", (waiter, ))
|
|
291
|
+
break
|
|
292
|
+
|
|
293
|
+
if not woke_up:
|
|
294
|
+
self._context_managers.pop(manager._priority)
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
end_len = len(manager)
|
|
298
|
+
|
|
299
|
+
assert start_len > end_len, f"start {start_len} end {end_len}"
|
|
300
|
+
|
|
301
|
+
if end_len:
|
|
302
|
+
# There are still waiters, put the manager back
|
|
303
|
+
heappush(self_waiters, manager)
|
|
304
|
+
else:
|
|
305
|
+
# There are no more waiters, get rid of the empty manager
|
|
306
|
+
self._context_managers.pop(manager._priority)
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
# emergency procedure (hopefully temporary):
|
|
310
|
+
if not debug_logs:
|
|
311
|
+
while potential_lost_waiters:
|
|
312
|
+
waiter = potential_lost_waiters.pop()
|
|
313
|
+
if _is_not_done(waiter):
|
|
314
|
+
waiter.set_result(None)
|
|
315
|
+
return
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
while potential_lost_waiters:
|
|
319
|
+
waiter = potential_lost_waiters.pop()
|
|
320
|
+
log_debug("we found a lost waiter %s", (waiter, ))
|
|
321
|
+
if _is_not_done(waiter):
|
|
322
|
+
waiter.set_result(None)
|
|
323
|
+
log_debug("woke up lost waiter %s", (waiter, ))
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
log_debug("%s has no waiters to wake", (self, ))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
cdef class _AbstractPrioritySemaphoreContextManager(Semaphore):
|
|
330
|
+
"""
|
|
331
|
+
A context manager for priority semaphore waiters.
|
|
332
|
+
|
|
333
|
+
This context manager is associated with a specific priority and handles
|
|
334
|
+
the acquisition and release of the semaphore for waiters with that priority.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
def __init__(
|
|
338
|
+
self,
|
|
339
|
+
parent: _AbstractPrioritySemaphore,
|
|
340
|
+
priority: PT,
|
|
341
|
+
name: Optional[str] = None,
|
|
342
|
+
) -> None:
|
|
343
|
+
"""Initializes the context manager for a specific priority.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
parent: The parent semaphore.
|
|
347
|
+
priority: The priority associated with this context manager.
|
|
348
|
+
name: An optional name for the context manager, used for debugging.
|
|
349
|
+
|
|
350
|
+
Examples:
|
|
351
|
+
>>> parent_semaphore = _AbstractPrioritySemaphore(5)
|
|
352
|
+
>>> context_manager = _AbstractPrioritySemaphoreContextManager(parent_semaphore, priority=1)
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
Semaphore.__init__(self, 0, name=name)
|
|
356
|
+
|
|
357
|
+
self._parent = parent
|
|
358
|
+
"""The parent semaphore."""
|
|
359
|
+
|
|
360
|
+
self._priority = priority
|
|
361
|
+
"""The priority associated with this context manager."""
|
|
362
|
+
|
|
363
|
+
def __repr__(self) -> str:
|
|
364
|
+
"""Returns a string representation of the context manager."""
|
|
365
|
+
return f"<{self.__class__.__name__} parent={self._parent} {self._priority_name}={self._priority} waiters={len(self)}>"
|
|
366
|
+
|
|
367
|
+
cpdef str _repr_no_parent_(self):
|
|
368
|
+
"""Returns a string representation of the context manager without the parent."""
|
|
369
|
+
return f"<{self.__class__.__name__} parent_name={self._parent.name} {self._priority_name}={self._priority} waiters={len(self)}>"
|
|
370
|
+
|
|
371
|
+
def __richcmp__(self, _AbstractPrioritySemaphoreContextManager other, int op) -> bint:
|
|
372
|
+
"""Rich comparison special method. Compares this context manager with another based on priority.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
other: The other context manager to compare with.
|
|
376
|
+
op: The operation being performed. One of:
|
|
377
|
+
0 -> Py_LT (a < b)
|
|
378
|
+
1 -> Py_LE (a <= b)
|
|
379
|
+
2 -> Py_EQ (a == b)
|
|
380
|
+
3 -> Py_NE (a != b)
|
|
381
|
+
4 -> Py_GT (a > b)
|
|
382
|
+
5 -> Py_GE (a >= b)
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
The boolean result of the comparison.
|
|
386
|
+
|
|
387
|
+
Raises:
|
|
388
|
+
TypeError: If the other object is not an instance of :class:`~_AbstractPrioritySemaphoreContextManager`.
|
|
389
|
+
|
|
390
|
+
Examples:
|
|
391
|
+
>>> cm1 = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
|
|
392
|
+
>>> cm2 = _AbstractPrioritySemaphoreContextManager(parent, priority=2)
|
|
393
|
+
>>> cm1 < cm2
|
|
394
|
+
"""
|
|
395
|
+
if op == 0: # Py_LT
|
|
396
|
+
return self._priority < other._priority
|
|
397
|
+
elif op == 1: # Py_LE
|
|
398
|
+
return self._priority <= other._priority
|
|
399
|
+
elif op == 2: # Py_EQ
|
|
400
|
+
return self is other
|
|
401
|
+
elif op == 3: # Py_NE
|
|
402
|
+
return self is not other
|
|
403
|
+
elif op == 4: # Py_GT
|
|
404
|
+
return self._priority > other._priority
|
|
405
|
+
elif op == 5: # Py_GE
|
|
406
|
+
return self._priority >= other._priority
|
|
407
|
+
return NotImplemented
|
|
408
|
+
|
|
409
|
+
cpdef object acquire(self):
|
|
410
|
+
"""Acquires the semaphore for this context manager.
|
|
411
|
+
|
|
412
|
+
If the internal counter is larger than zero on entry,
|
|
413
|
+
decrement it by one and return True immediately. If it is
|
|
414
|
+
zero on entry, block, waiting until some other coroutine has
|
|
415
|
+
called release() to make it larger than 0, and then return
|
|
416
|
+
True.
|
|
417
|
+
|
|
418
|
+
This method overrides :meth:`Semaphore.acquire` to handle priority-based logic.
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
>>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
|
|
422
|
+
>>> await context_manager.acquire()
|
|
423
|
+
"""
|
|
424
|
+
if self._parent._Semaphore__value <= 0:
|
|
425
|
+
self._c_ensure_debug_daemon((),{})
|
|
426
|
+
return self.__acquire()
|
|
427
|
+
|
|
428
|
+
async def __acquire(self) -> Literal[True]:
|
|
429
|
+
cdef object fut
|
|
430
|
+
cdef _AbstractPrioritySemaphore parent = self._parent
|
|
431
|
+
while parent._Semaphore__value <= 0:
|
|
432
|
+
if self._Semaphore__waiters is None:
|
|
433
|
+
self._Semaphore__waiters = deque()
|
|
434
|
+
fut = self._c_get_loop().create_future()
|
|
435
|
+
self._Semaphore__waiters.append(fut)
|
|
436
|
+
parent._potential_lost_waiters.add(fut)
|
|
437
|
+
try:
|
|
438
|
+
await fut
|
|
439
|
+
except:
|
|
440
|
+
# See the similar code in Queue.get.
|
|
441
|
+
fut.cancel()
|
|
442
|
+
if parent._Semaphore__value > 0 and _is_not_cancelled(fut):
|
|
443
|
+
parent._wake_up_next()
|
|
444
|
+
raise
|
|
445
|
+
parent._Semaphore__value -= 1
|
|
446
|
+
return True
|
|
447
|
+
|
|
448
|
+
cpdef void release(self):
|
|
449
|
+
"""Releases the semaphore for this context manager.
|
|
450
|
+
|
|
451
|
+
This method overrides :meth:`Semaphore.release` to handle priority-based logic.
|
|
452
|
+
|
|
453
|
+
Examples:
|
|
454
|
+
>>> context_manager = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
|
|
455
|
+
>>> context_manager.release()
|
|
456
|
+
"""
|
|
457
|
+
self._parent.release()
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
cdef inline bint _is_not_done(fut: asyncio.Future):
|
|
461
|
+
return PyUnicode_CompareWithASCIIString(fut._state, b"PENDING") == 0
|
|
462
|
+
|
|
463
|
+
cdef inline bint _is_not_cancelled(fut: asyncio.Future):
|
|
464
|
+
return PyUnicode_CompareWithASCIIString(fut._state, b"CANCELLED") != 0
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
del asyncio
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
cdef class _PrioritySemaphoreContextManager(_AbstractPrioritySemaphoreContextManager):
|
|
471
|
+
"""Context manager for numeric priority semaphores."""
|
|
472
|
+
|
|
473
|
+
def __cinit__(self):
|
|
474
|
+
self._priority_name = "priority"
|
|
475
|
+
# Semaphore.__cinit__(self)
|
|
476
|
+
self._Semaphore__waiters = deque()
|
|
477
|
+
self._decorated: Set[str] = set()
|
|
478
|
+
|
|
479
|
+
def __richcmp__(self, _PrioritySemaphoreContextManager other, int op) -> bint:
|
|
480
|
+
"""Rich comparison special method. Compares this context manager with another based on priority.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
other: The other context manager to compare with.
|
|
484
|
+
op: The operation being performed. One of:
|
|
485
|
+
0 -> Py_LT (a < b)
|
|
486
|
+
1 -> Py_LE (a <= b)
|
|
487
|
+
2 -> Py_EQ (a == b)
|
|
488
|
+
3 -> Py_NE (a != b)
|
|
489
|
+
4 -> Py_GT (a > b)
|
|
490
|
+
5 -> Py_GE (a >= b)
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
The boolean result of the comparison.
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
TypeError: If the other object is not an instance of :class:`~_PrioritySemaphoreContextManager`.
|
|
497
|
+
|
|
498
|
+
Examples:
|
|
499
|
+
>>> cm1 = _AbstractPrioritySemaphoreContextManager(parent, priority=1)
|
|
500
|
+
>>> cm2 = _AbstractPrioritySemaphoreContextManager(parent, priority=2)
|
|
501
|
+
>>> cm1 < cm2
|
|
502
|
+
"""
|
|
503
|
+
if op == 0: # Py_LT
|
|
504
|
+
return <int>self._priority < <int>other._priority
|
|
505
|
+
elif op == 1: # Py_LE
|
|
506
|
+
return <int>self._priority <= <int>other._priority
|
|
507
|
+
elif op == 2: # Py_EQ
|
|
508
|
+
return self is other
|
|
509
|
+
elif op == 3: # Py_NE
|
|
510
|
+
return self is not other
|
|
511
|
+
elif op == 4: # Py_GT
|
|
512
|
+
return <int>self._priority > <int>other._priority
|
|
513
|
+
elif op == 5: # Py_GE
|
|
514
|
+
return <int>self._priority >= <int>other._priority
|
|
515
|
+
return NotImplemented
|
|
516
|
+
|
|
517
|
+
cdef class PrioritySemaphore(_AbstractPrioritySemaphore):
|
|
518
|
+
"""Semaphore that uses numeric priorities for waiters.
|
|
519
|
+
|
|
520
|
+
This class extends :class:`_AbstractPrioritySemaphore` and provides a concrete implementation
|
|
521
|
+
using numeric priorities. The `_context_manager_class` is set to :class:`_PrioritySemaphoreContextManager`,
|
|
522
|
+
and the `_top_priority` is set to -1, which is the highest priority.
|
|
523
|
+
|
|
524
|
+
Examples:
|
|
525
|
+
The primary way to use this semaphore is by specifying a priority.
|
|
526
|
+
|
|
527
|
+
>>> priority_semaphore = PrioritySemaphore(10)
|
|
528
|
+
>>> async with priority_semaphore[priority]:
|
|
529
|
+
... await do_stuff()
|
|
530
|
+
|
|
531
|
+
You can also enter and exit this semaphore without specifying a priority, and it will use the top priority by default:
|
|
532
|
+
|
|
533
|
+
>>> priority_semaphore = PrioritySemaphore(10)
|
|
534
|
+
>>> async with priority_semaphore:
|
|
535
|
+
... await do_stuff()
|
|
536
|
+
|
|
537
|
+
See Also:
|
|
538
|
+
:class:`_AbstractPrioritySemaphore` for the base class implementation.
|
|
539
|
+
"""
|
|
540
|
+
|
|
541
|
+
def __cinit__(self):
|
|
542
|
+
# _AbstractPrioritySemaphore.__cinit__(self)
|
|
543
|
+
|
|
544
|
+
self._context_managers = {}
|
|
545
|
+
"""A dictionary mapping priorities to their context managers."""
|
|
546
|
+
|
|
547
|
+
self._Semaphore__waiters = []
|
|
548
|
+
"""A heap queue of context managers, sorted by priority."""
|
|
549
|
+
|
|
550
|
+
# NOTE: This should (hopefully) be temporary
|
|
551
|
+
self._potential_lost_waiters = set()
|
|
552
|
+
"""A list of futures representing waiters that might have been lost."""
|
|
553
|
+
|
|
554
|
+
def __init__(
|
|
555
|
+
self,
|
|
556
|
+
value: int = 1,
|
|
557
|
+
*,
|
|
558
|
+
name: Optional[str] = None,
|
|
559
|
+
) -> None:
|
|
560
|
+
# context manager class is some temporary hacky shit, just ignore this
|
|
561
|
+
_AbstractPrioritySemaphore.__init__(self, _PrioritySemaphoreContextManager, -1, value, name=name)
|
|
562
|
+
|
|
563
|
+
def __getitem__(
|
|
564
|
+
self, priority: Optional[PT]
|
|
565
|
+
) -> "_PrioritySemaphoreContextManager[PT]":
|
|
566
|
+
"""Gets the context manager for a given priority.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
priority: The priority for which to get the context manager. If None, uses the top priority.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
The context manager associated with the given priority.
|
|
573
|
+
|
|
574
|
+
Examples:
|
|
575
|
+
>>> semaphore = _AbstractPrioritySemaphore(5)
|
|
576
|
+
>>> context_manager = semaphore[priority]
|
|
577
|
+
"""
|
|
578
|
+
return self.c_getitem(priority or -1)
|
|
579
|
+
|
|
580
|
+
cdef _PrioritySemaphoreContextManager c_getitem(self, object priority):
|
|
581
|
+
if self._Semaphore__value is None:
|
|
582
|
+
raise ValueError(self._Semaphore__value)
|
|
583
|
+
|
|
584
|
+
cdef dict[int, _PrioritySemaphoreContextManager] context_managers
|
|
585
|
+
cdef _PrioritySemaphoreContextManager context_manager
|
|
586
|
+
|
|
587
|
+
context_managers = self._context_managers
|
|
588
|
+
context_manager = context_managers.get(priority)
|
|
589
|
+
if context_manager is None:
|
|
590
|
+
context_manager = _PrioritySemaphoreContextManager(self, <int>priority, name=self.name)
|
|
591
|
+
heappush(self._Semaphore__waiters, context_manager)
|
|
592
|
+
context_managers[<int>priority] = context_manager
|
|
593
|
+
return context_manager
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
cdef void log_debug(str message, tuple args):
|
|
597
|
+
_logger_log(DEBUG, message, args)
|