xoscar 0.9.0__cp312-cp312-macosx_10_13_x86_64.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.
- xoscar/__init__.py +61 -0
- xoscar/_utils.cpython-312-darwin.so +0 -0
- xoscar/_utils.pxd +36 -0
- xoscar/_utils.pyx +246 -0
- xoscar/_version.py +693 -0
- xoscar/aio/__init__.py +16 -0
- xoscar/aio/base.py +86 -0
- xoscar/aio/file.py +59 -0
- xoscar/aio/lru.py +228 -0
- xoscar/aio/parallelism.py +39 -0
- xoscar/api.py +527 -0
- xoscar/backend.py +67 -0
- xoscar/backends/__init__.py +14 -0
- xoscar/backends/allocate_strategy.py +160 -0
- xoscar/backends/communication/__init__.py +30 -0
- xoscar/backends/communication/base.py +315 -0
- xoscar/backends/communication/core.py +69 -0
- xoscar/backends/communication/dummy.py +253 -0
- xoscar/backends/communication/errors.py +20 -0
- xoscar/backends/communication/socket.py +444 -0
- xoscar/backends/communication/ucx.py +538 -0
- xoscar/backends/communication/utils.py +97 -0
- xoscar/backends/config.py +157 -0
- xoscar/backends/context.py +437 -0
- xoscar/backends/core.py +352 -0
- xoscar/backends/indigen/__init__.py +16 -0
- xoscar/backends/indigen/__main__.py +19 -0
- xoscar/backends/indigen/backend.py +51 -0
- xoscar/backends/indigen/driver.py +26 -0
- xoscar/backends/indigen/fate_sharing.py +221 -0
- xoscar/backends/indigen/pool.py +515 -0
- xoscar/backends/indigen/shared_memory.py +548 -0
- xoscar/backends/message.cpython-312-darwin.so +0 -0
- xoscar/backends/message.pyi +255 -0
- xoscar/backends/message.pyx +646 -0
- xoscar/backends/pool.py +1630 -0
- xoscar/backends/router.py +285 -0
- xoscar/backends/test/__init__.py +16 -0
- xoscar/backends/test/backend.py +38 -0
- xoscar/backends/test/pool.py +233 -0
- xoscar/batch.py +256 -0
- xoscar/collective/__init__.py +27 -0
- xoscar/collective/backend/__init__.py +13 -0
- xoscar/collective/backend/nccl_backend.py +160 -0
- xoscar/collective/common.py +102 -0
- xoscar/collective/core.py +737 -0
- xoscar/collective/process_group.py +687 -0
- xoscar/collective/utils.py +41 -0
- xoscar/collective/xoscar_pygloo.cpython-312-darwin.so +0 -0
- xoscar/collective/xoscar_pygloo.pyi +239 -0
- xoscar/constants.py +23 -0
- xoscar/context.cpython-312-darwin.so +0 -0
- xoscar/context.pxd +21 -0
- xoscar/context.pyx +368 -0
- xoscar/core.cpython-312-darwin.so +0 -0
- xoscar/core.pxd +51 -0
- xoscar/core.pyx +664 -0
- xoscar/debug.py +188 -0
- xoscar/driver.py +42 -0
- xoscar/errors.py +63 -0
- xoscar/libcpp.pxd +31 -0
- xoscar/metrics/__init__.py +21 -0
- xoscar/metrics/api.py +288 -0
- xoscar/metrics/backends/__init__.py +13 -0
- xoscar/metrics/backends/console/__init__.py +13 -0
- xoscar/metrics/backends/console/console_metric.py +82 -0
- xoscar/metrics/backends/metric.py +149 -0
- xoscar/metrics/backends/prometheus/__init__.py +13 -0
- xoscar/metrics/backends/prometheus/prometheus_metric.py +70 -0
- xoscar/nvutils.py +717 -0
- xoscar/profiling.py +260 -0
- xoscar/serialization/__init__.py +20 -0
- xoscar/serialization/aio.py +141 -0
- xoscar/serialization/core.cpython-312-darwin.so +0 -0
- xoscar/serialization/core.pxd +28 -0
- xoscar/serialization/core.pyi +57 -0
- xoscar/serialization/core.pyx +944 -0
- xoscar/serialization/cuda.py +111 -0
- xoscar/serialization/exception.py +48 -0
- xoscar/serialization/mlx.py +67 -0
- xoscar/serialization/numpy.py +82 -0
- xoscar/serialization/pyfury.py +37 -0
- xoscar/serialization/scipy.py +72 -0
- xoscar/serialization/torch.py +180 -0
- xoscar/utils.py +522 -0
- xoscar/virtualenv/__init__.py +34 -0
- xoscar/virtualenv/core.py +268 -0
- xoscar/virtualenv/platform.py +56 -0
- xoscar/virtualenv/utils.py +100 -0
- xoscar/virtualenv/uv.py +321 -0
- xoscar-0.9.0.dist-info/METADATA +230 -0
- xoscar-0.9.0.dist-info/RECORD +94 -0
- xoscar-0.9.0.dist-info/WHEEL +6 -0
- xoscar-0.9.0.dist-info/top_level.txt +2 -0
xoscar/core.pxd
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright 2022-2023 XProbe Inc.
|
|
2
|
+
# derived from copyright 1999-2021 Alibaba Group Holding Ltd.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
cdef class ActorRef:
|
|
18
|
+
cdef object __weakref__
|
|
19
|
+
cdef public str address
|
|
20
|
+
cdef public object uid
|
|
21
|
+
cdef public list proxy_addresses
|
|
22
|
+
cdef dict _methods
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
cdef class LocalActorRef(ActorRef):
|
|
26
|
+
cdef object _actor_weakref
|
|
27
|
+
cdef _weakref_local_actor(self)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
cdef class BufferRef:
|
|
31
|
+
cdef public str address
|
|
32
|
+
cdef public bytes uid
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
cdef class FileObjectRef:
|
|
36
|
+
cdef public str address
|
|
37
|
+
cdef public bytes uid
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
cdef class _BaseActor:
|
|
41
|
+
cdef object __weakref__
|
|
42
|
+
cdef str _address
|
|
43
|
+
cdef object _lock
|
|
44
|
+
cdef object _uid
|
|
45
|
+
|
|
46
|
+
cpdef ActorRef ref(self)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
cdef class ActorEnvironment:
|
|
50
|
+
cdef public dict actor_locks
|
|
51
|
+
cdef public object address
|
xoscar/core.pyx
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
# Copyright 2022-2023 XProbe Inc.
|
|
2
|
+
# derived from copyright 1999-2021 Alibaba Group Holding Ltd.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import inspect
|
|
18
|
+
import logging
|
|
19
|
+
import sys
|
|
20
|
+
import weakref
|
|
21
|
+
from typing import Any, AsyncGenerator, Callable
|
|
22
|
+
|
|
23
|
+
cimport cython
|
|
24
|
+
|
|
25
|
+
from .aio import AioFileObject
|
|
26
|
+
from .context cimport get_context
|
|
27
|
+
|
|
28
|
+
from .errors import ActorNotExist, Return
|
|
29
|
+
|
|
30
|
+
from ._utils cimport is_async_generator
|
|
31
|
+
|
|
32
|
+
CALL_METHOD_DEFAULT = 0
|
|
33
|
+
CALL_METHOD_BATCH = 1
|
|
34
|
+
NO_LOCK_ATTRIBUTE_HINT = "__XOSCAR_ACTOR_METHOD_NO_LOCK__"
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
cdef:
|
|
39
|
+
bint _log_unhandled_errors = False
|
|
40
|
+
bint _log_cycle_send = False
|
|
41
|
+
dict _local_pool_map = dict()
|
|
42
|
+
object _actor_method_wrapper
|
|
43
|
+
|
|
44
|
+
def no_lock(func: Callable):
|
|
45
|
+
setattr(func, NO_LOCK_ATTRIBUTE_HINT, True)
|
|
46
|
+
return func
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def set_debug_options(options):
|
|
50
|
+
global _log_unhandled_errors, _log_cycle_send
|
|
51
|
+
if options is None:
|
|
52
|
+
_log_unhandled_errors = _log_cycle_send = False
|
|
53
|
+
else:
|
|
54
|
+
_log_unhandled_errors = options.log_unhandled_errors
|
|
55
|
+
_log_cycle_send = options.log_cycle_send
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
cdef _get_local_actor(address, uid):
|
|
59
|
+
# Do not expose this method to Python to avoid actor being
|
|
60
|
+
# referenced everywhere.
|
|
61
|
+
#
|
|
62
|
+
# The cycle send detection relies on send message, so we
|
|
63
|
+
# disabled the local actor proxy if the debug option is on.
|
|
64
|
+
if _log_cycle_send:
|
|
65
|
+
return None
|
|
66
|
+
pool_ref = _local_pool_map.get(address)
|
|
67
|
+
pool = None if pool_ref is None else pool_ref()
|
|
68
|
+
if pool is not None:
|
|
69
|
+
actor = pool._actors.get(uid)
|
|
70
|
+
if actor is not None:
|
|
71
|
+
return actor
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def register_local_pool(address, pool):
|
|
76
|
+
"""
|
|
77
|
+
Register local actor pool for local actor lookup.
|
|
78
|
+
"""
|
|
79
|
+
_local_pool_map[address] = weakref.ref(
|
|
80
|
+
pool, lambda _: _local_pool_map.pop(address, None)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
cpdef create_local_actor_ref(address, uid):
|
|
85
|
+
"""
|
|
86
|
+
Create a reference to local actor.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
LocalActorRef or None
|
|
91
|
+
"""
|
|
92
|
+
actor = _get_local_actor(address, uid)
|
|
93
|
+
if actor is not None:
|
|
94
|
+
return LocalActorRef(actor)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
cpdef create_actor_ref(address, uid, list proxy_addresses = None):
|
|
99
|
+
"""
|
|
100
|
+
Create an actor reference.
|
|
101
|
+
TODO(fyrestone): Remove the create_actor_ref in _utils.pyx
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
ActorRef or LocalActorRef
|
|
106
|
+
"""
|
|
107
|
+
actor = _get_local_actor(address, uid)
|
|
108
|
+
return ActorRef(address, uid, proxy_addresses=proxy_addresses) \
|
|
109
|
+
if actor is None else LocalActorRef(actor)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
cdef class ActorRef:
|
|
113
|
+
"""
|
|
114
|
+
Reference of an Actor at user side
|
|
115
|
+
"""
|
|
116
|
+
def __init__(self, str address, object uid, list proxy_addresses = None):
|
|
117
|
+
if isinstance(uid, str):
|
|
118
|
+
uid = uid.encode()
|
|
119
|
+
self.uid = uid
|
|
120
|
+
self.address = address
|
|
121
|
+
self.proxy_addresses = proxy_addresses
|
|
122
|
+
self._methods = dict()
|
|
123
|
+
|
|
124
|
+
def destroy(self, object callback=None):
|
|
125
|
+
ctx = get_context()
|
|
126
|
+
return ctx.destroy_actor(self)
|
|
127
|
+
|
|
128
|
+
def __reduce__(self):
|
|
129
|
+
return create_actor_ref, (self.address, self.uid, self.proxy_addresses)
|
|
130
|
+
|
|
131
|
+
def __getattr__(self, item):
|
|
132
|
+
if item.startswith('_') and item not in ["__xoscar_next__", "__xoscar_destroy_generator__"]:
|
|
133
|
+
return object.__getattribute__(self, item)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
return self._methods[item]
|
|
137
|
+
except KeyError:
|
|
138
|
+
method = self._methods[item] = ActorRefMethod(self, item)
|
|
139
|
+
return method
|
|
140
|
+
|
|
141
|
+
def __hash__(self):
|
|
142
|
+
return hash((self.address, self.uid))
|
|
143
|
+
|
|
144
|
+
def __eq__(self, other):
|
|
145
|
+
other_type = type(other)
|
|
146
|
+
if other_type is ActorRef or other_type is LocalActorRef:
|
|
147
|
+
return self.address == other.address and self.uid == other.uid
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def __repr__(self):
|
|
151
|
+
if not self.proxy_addresses:
|
|
152
|
+
return 'ActorRef(uid={!r}, address={!r})'.format(self.uid, self.address)
|
|
153
|
+
else:
|
|
154
|
+
return (f"ActorRef(uid={self.uid}, address={self.address}, "
|
|
155
|
+
f"proxy_addresses={self.proxy_addresses})")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
cdef class _DelayedArgument:
|
|
159
|
+
cdef readonly tuple arguments
|
|
160
|
+
|
|
161
|
+
def __init__(self, tuple arguments):
|
|
162
|
+
self.arguments = arguments
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
cdef class ActorRefMethod:
|
|
166
|
+
"""
|
|
167
|
+
Wrapper for an Actor method at client
|
|
168
|
+
"""
|
|
169
|
+
cdef ActorRef ref
|
|
170
|
+
cdef object method_name
|
|
171
|
+
cdef object _options
|
|
172
|
+
|
|
173
|
+
def __init__(self, ref, method_name, options=None):
|
|
174
|
+
self.ref = ref
|
|
175
|
+
self.method_name = method_name
|
|
176
|
+
self._options = options or {}
|
|
177
|
+
|
|
178
|
+
def __call__(self, *args, **kwargs):
|
|
179
|
+
return self.send(*args, **kwargs)
|
|
180
|
+
|
|
181
|
+
def options(self, **options):
|
|
182
|
+
return ActorRefMethod(self.ref, self.method_name, options)
|
|
183
|
+
|
|
184
|
+
def send(self, *args, **kwargs):
|
|
185
|
+
arg_tuple = (self.method_name, CALL_METHOD_DEFAULT, args, kwargs)
|
|
186
|
+
return get_context().send(self.ref, arg_tuple, **self._options)
|
|
187
|
+
|
|
188
|
+
def tell(self, *args, **kwargs):
|
|
189
|
+
arg_tuple = (self.method_name, CALL_METHOD_DEFAULT, args, kwargs)
|
|
190
|
+
return get_context().send(self.ref, arg_tuple, wait_response=False, **self._options)
|
|
191
|
+
|
|
192
|
+
def delay(self, *args, **kwargs):
|
|
193
|
+
arg_tuple = (self.method_name, CALL_METHOD_DEFAULT, args, kwargs)
|
|
194
|
+
return _DelayedArgument(arg_tuple)
|
|
195
|
+
|
|
196
|
+
def batch(self, *delays, send=True):
|
|
197
|
+
cdef:
|
|
198
|
+
long n_delays = len(delays)
|
|
199
|
+
bint has_kw = False
|
|
200
|
+
list args_list
|
|
201
|
+
list kwargs_list
|
|
202
|
+
_DelayedArgument delay
|
|
203
|
+
|
|
204
|
+
args_list = [None] * n_delays
|
|
205
|
+
kwargs_list = [None] * n_delays
|
|
206
|
+
|
|
207
|
+
last_method = None
|
|
208
|
+
for idx in range(n_delays):
|
|
209
|
+
delay = delays[idx]
|
|
210
|
+
method, _call_method, args, kwargs = delay.arguments
|
|
211
|
+
if last_method is not None and method != last_method:
|
|
212
|
+
raise ValueError('Does not support calling multiple methods in batch')
|
|
213
|
+
last_method = method
|
|
214
|
+
|
|
215
|
+
args_list[idx] = args
|
|
216
|
+
kwargs_list[idx] = kwargs
|
|
217
|
+
if kwargs:
|
|
218
|
+
has_kw = True
|
|
219
|
+
|
|
220
|
+
if not has_kw:
|
|
221
|
+
kwargs_list = None
|
|
222
|
+
if last_method is None:
|
|
223
|
+
last_method = self.method_name
|
|
224
|
+
|
|
225
|
+
message = (last_method, CALL_METHOD_BATCH, (args_list, kwargs_list), None)
|
|
226
|
+
return get_context().send(self.ref, message, wait_response=send, **self._options)
|
|
227
|
+
|
|
228
|
+
def tell_delay(self, *args, delay=None, ignore_conn_fail=True, **kwargs):
|
|
229
|
+
async def delay_fun():
|
|
230
|
+
try:
|
|
231
|
+
await asyncio.sleep(delay)
|
|
232
|
+
message = (self.method_name, CALL_METHOD_DEFAULT, args, kwargs)
|
|
233
|
+
await get_context().send(self.ref, message, wait_response=False, **self._options)
|
|
234
|
+
except Exception as ex:
|
|
235
|
+
if ignore_conn_fail and isinstance(ex, ConnectionRefusedError):
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
logger.error(f'Error {type(ex)} occurred when calling {self.method_name} '
|
|
239
|
+
f'on {self.ref.uid} at {self.ref.address} with tell_delay')
|
|
240
|
+
raise
|
|
241
|
+
|
|
242
|
+
return asyncio.create_task(delay_fun())
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
cdef class LocalActorRef(ActorRef):
|
|
246
|
+
def __init__(self, _BaseActor actor):
|
|
247
|
+
# Make sure the input actor is an instance of _BaseActor.
|
|
248
|
+
super().__init__(actor._address, actor._uid)
|
|
249
|
+
self._actor_weakref = weakref.ref(actor, lambda _: self._methods.clear())
|
|
250
|
+
|
|
251
|
+
cdef _weakref_local_actor(self):
|
|
252
|
+
actor = _get_local_actor(self.address, self.uid)
|
|
253
|
+
# Make sure the input actor is an instance of _BaseActor.
|
|
254
|
+
if actor is not None and isinstance(actor, _BaseActor):
|
|
255
|
+
self._actor_weakref = weakref.ref(actor, lambda _: self._methods.clear())
|
|
256
|
+
return actor
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def __getattr__(self, item):
|
|
260
|
+
try:
|
|
261
|
+
return self._methods[item]
|
|
262
|
+
except KeyError:
|
|
263
|
+
actor = self._actor_weakref() or self._weakref_local_actor()
|
|
264
|
+
if actor is None:
|
|
265
|
+
raise ActorNotExist(f"Actor {self.uid} does not exist") from None
|
|
266
|
+
# For detecting the attribute error.
|
|
267
|
+
getattr(actor, item)
|
|
268
|
+
method = self._methods[item] = LocalActorRefMethod(self, item)
|
|
269
|
+
return method
|
|
270
|
+
|
|
271
|
+
def __repr__(self):
|
|
272
|
+
return 'LocalActorRef(uid={!r}, address={!r}), actor_weakref={!r}'.format(
|
|
273
|
+
self.uid, self.address, self._actor_weakref)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _has_no_lock_hint_for_method(method) -> bool:
|
|
277
|
+
if getattr(method, NO_LOCK_ATTRIBUTE_HINT, False) is True:
|
|
278
|
+
return True
|
|
279
|
+
if hasattr(method, "__self__"):
|
|
280
|
+
return getattr(method.__self__, NO_LOCK_ATTRIBUTE_HINT, False) is True
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
async def __pyx_actor_method_wrapper(method, result_handler, lock, args, kwargs):
|
|
285
|
+
if _has_no_lock_hint_for_method(method):
|
|
286
|
+
result = method(*args, **kwargs)
|
|
287
|
+
if asyncio.iscoroutine(result):
|
|
288
|
+
result = await result
|
|
289
|
+
else:
|
|
290
|
+
async with lock:
|
|
291
|
+
result = method(*args, **kwargs)
|
|
292
|
+
if asyncio.iscoroutine(result):
|
|
293
|
+
result = await result
|
|
294
|
+
return await result_handler(result)
|
|
295
|
+
|
|
296
|
+
# Avoid global lookup.
|
|
297
|
+
_actor_method_wrapper = __pyx_actor_method_wrapper
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
cdef class LocalActorRefMethod:
|
|
301
|
+
cdef LocalActorRef _local_actor_ref
|
|
302
|
+
cdef object _method_name
|
|
303
|
+
|
|
304
|
+
def __init__(self, LocalActorRef local_actor_ref, method_name):
|
|
305
|
+
self._local_actor_ref = local_actor_ref
|
|
306
|
+
self._method_name = method_name
|
|
307
|
+
|
|
308
|
+
cdef tuple _get_referent(self):
|
|
309
|
+
actor = self._local_actor_ref._actor_weakref() or self._local_actor_ref._weakref_local_actor()
|
|
310
|
+
if actor is None:
|
|
311
|
+
raise ActorNotExist(f"Actor {self._local_actor_ref.uid} does not exist.")
|
|
312
|
+
method = getattr(actor, self._method_name)
|
|
313
|
+
return actor, method
|
|
314
|
+
|
|
315
|
+
def __call__(self, *args, **kwargs):
|
|
316
|
+
actor, method = self._get_referent()
|
|
317
|
+
return _actor_method_wrapper(
|
|
318
|
+
method, actor._handle_actor_result, (<_BaseActor>actor)._lock, args, kwargs)
|
|
319
|
+
|
|
320
|
+
def options(self, **options):
|
|
321
|
+
return self
|
|
322
|
+
|
|
323
|
+
def send(self, *args, **kwargs):
|
|
324
|
+
actor, method = self._get_referent()
|
|
325
|
+
return _actor_method_wrapper(
|
|
326
|
+
method, actor._handle_actor_result, (<_BaseActor>actor)._lock, args, kwargs)
|
|
327
|
+
|
|
328
|
+
def tell(self, *args, **kwargs):
|
|
329
|
+
actor, method = self._get_referent()
|
|
330
|
+
coro = _actor_method_wrapper(
|
|
331
|
+
method, actor._handle_actor_result, (<_BaseActor>actor)._lock, args, kwargs)
|
|
332
|
+
asyncio.create_task(coro)
|
|
333
|
+
return asyncio.sleep(0)
|
|
334
|
+
|
|
335
|
+
def delay(self, *args, **kwargs):
|
|
336
|
+
actor, method = self._get_referent()
|
|
337
|
+
return method.delay(*args, **kwargs)
|
|
338
|
+
|
|
339
|
+
def batch(self, *delays, send=True):
|
|
340
|
+
actor, method = self._get_referent()
|
|
341
|
+
coro = _actor_method_wrapper(
|
|
342
|
+
method.batch, actor._handle_actor_result, (<_BaseActor>actor)._lock, delays, dict())
|
|
343
|
+
if send:
|
|
344
|
+
return coro
|
|
345
|
+
else:
|
|
346
|
+
asyncio.create_task(coro)
|
|
347
|
+
return asyncio.sleep(0)
|
|
348
|
+
|
|
349
|
+
def tell_delay(self, *args, delay=None, ignore_conn_fail=True, **kwargs):
|
|
350
|
+
async def delay_fun():
|
|
351
|
+
await asyncio.sleep(delay)
|
|
352
|
+
await self.tell(*args, **kwargs)
|
|
353
|
+
|
|
354
|
+
return asyncio.create_task(delay_fun())
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
cdef class _BaseActor:
|
|
358
|
+
"""
|
|
359
|
+
Base Indigen actor class, user methods implemented as methods
|
|
360
|
+
"""
|
|
361
|
+
def __cinit__(self, *args, **kwargs):
|
|
362
|
+
self._lock = self._create_lock()
|
|
363
|
+
|
|
364
|
+
def _create_lock(self):
|
|
365
|
+
raise NotImplementedError
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def uid(self):
|
|
369
|
+
return self._uid
|
|
370
|
+
|
|
371
|
+
@uid.setter
|
|
372
|
+
def uid(self, uid):
|
|
373
|
+
self._uid = uid
|
|
374
|
+
|
|
375
|
+
def _set_uid(self, uid):
|
|
376
|
+
self._uid = uid
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def address(self):
|
|
380
|
+
return self._address
|
|
381
|
+
|
|
382
|
+
@address.setter
|
|
383
|
+
def address(self, addr):
|
|
384
|
+
self._address = addr
|
|
385
|
+
|
|
386
|
+
def _set_address(self, addr):
|
|
387
|
+
self._address = addr
|
|
388
|
+
|
|
389
|
+
cpdef ActorRef ref(self):
|
|
390
|
+
return create_actor_ref(self._address, self._uid)
|
|
391
|
+
|
|
392
|
+
async def _handle_actor_result(self, result):
|
|
393
|
+
cdef int idx
|
|
394
|
+
cdef tuple res_tuple
|
|
395
|
+
cdef list tasks, coros, coro_poses, values
|
|
396
|
+
cdef object coro
|
|
397
|
+
cdef bint extract_tuple = False
|
|
398
|
+
cdef bint cancelled = False
|
|
399
|
+
cdef set dones, pending
|
|
400
|
+
|
|
401
|
+
if inspect.isawaitable(result):
|
|
402
|
+
result = await result
|
|
403
|
+
elif is_async_generator(result):
|
|
404
|
+
result = (result,)
|
|
405
|
+
extract_tuple = True
|
|
406
|
+
|
|
407
|
+
if type(result) is tuple:
|
|
408
|
+
res_tuple = result
|
|
409
|
+
coros = []
|
|
410
|
+
coro_poses = []
|
|
411
|
+
values = []
|
|
412
|
+
for idx, res_item in enumerate(res_tuple):
|
|
413
|
+
if is_async_generator(res_item):
|
|
414
|
+
value = self._run_actor_async_generator(res_item)
|
|
415
|
+
coros.append(value)
|
|
416
|
+
coro_poses.append(idx)
|
|
417
|
+
elif inspect.isawaitable(res_item):
|
|
418
|
+
value = res_item
|
|
419
|
+
coros.append(value)
|
|
420
|
+
coro_poses.append(idx)
|
|
421
|
+
else:
|
|
422
|
+
value = res_item
|
|
423
|
+
values.append(value)
|
|
424
|
+
|
|
425
|
+
# when there is only one coroutine, we do not need to use
|
|
426
|
+
# asyncio.wait as it introduces much overhead
|
|
427
|
+
if len(coros) == 1:
|
|
428
|
+
task_result = await coros[0]
|
|
429
|
+
if extract_tuple:
|
|
430
|
+
result = task_result
|
|
431
|
+
else:
|
|
432
|
+
result = tuple(task_result if t is coros[0] else t for t in values)
|
|
433
|
+
elif len(coros) > 0:
|
|
434
|
+
tasks = [asyncio.create_task(t) for t in coros]
|
|
435
|
+
try:
|
|
436
|
+
dones, pending = await asyncio.wait(tasks)
|
|
437
|
+
except asyncio.CancelledError:
|
|
438
|
+
cancelled = True
|
|
439
|
+
for task in tasks:
|
|
440
|
+
task.cancel()
|
|
441
|
+
# wait till all tasks return cancelled
|
|
442
|
+
dones, pending = await asyncio.wait(tasks)
|
|
443
|
+
|
|
444
|
+
if extract_tuple:
|
|
445
|
+
result = list(dones)[0].result()
|
|
446
|
+
else:
|
|
447
|
+
for pos in coro_poses:
|
|
448
|
+
task = tasks[pos]
|
|
449
|
+
values[pos] = task.result()
|
|
450
|
+
result = tuple(values)
|
|
451
|
+
|
|
452
|
+
if cancelled:
|
|
453
|
+
# raise in case no CancelledError raised
|
|
454
|
+
raise asyncio.CancelledError
|
|
455
|
+
|
|
456
|
+
return result
|
|
457
|
+
|
|
458
|
+
async def _run_actor_async_generator(self, gen: AsyncGenerator):
|
|
459
|
+
"""
|
|
460
|
+
Run an async generator under Actor lock
|
|
461
|
+
"""
|
|
462
|
+
cdef tuple res_tuple
|
|
463
|
+
cdef bint is_exception = False
|
|
464
|
+
cdef object res
|
|
465
|
+
cdef object message_trace = None, pop_message_trace = None, set_message_trace = None
|
|
466
|
+
|
|
467
|
+
from .debug import debug_async_timeout, pop_message_trace, set_message_trace
|
|
468
|
+
try:
|
|
469
|
+
res = None
|
|
470
|
+
while True:
|
|
471
|
+
async with self._lock:
|
|
472
|
+
with debug_async_timeout('actor_lock_timeout',
|
|
473
|
+
'async_generator %r hold lock timeout', gen):
|
|
474
|
+
if not is_exception:
|
|
475
|
+
res = await gen.asend(res)
|
|
476
|
+
else:
|
|
477
|
+
res = await gen.athrow(*res)
|
|
478
|
+
try:
|
|
479
|
+
if _log_cycle_send:
|
|
480
|
+
message_trace = pop_message_trace()
|
|
481
|
+
|
|
482
|
+
res = await self._handle_actor_result(res)
|
|
483
|
+
is_exception = False
|
|
484
|
+
except:
|
|
485
|
+
res = sys.exc_info()
|
|
486
|
+
is_exception = True
|
|
487
|
+
finally:
|
|
488
|
+
if _log_cycle_send:
|
|
489
|
+
set_message_trace(message_trace)
|
|
490
|
+
except Return as ex:
|
|
491
|
+
return ex.value
|
|
492
|
+
except StopAsyncIteration as ex:
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
async def __post_create__(self):
|
|
496
|
+
"""
|
|
497
|
+
Method called after actor creation
|
|
498
|
+
"""
|
|
499
|
+
pass
|
|
500
|
+
|
|
501
|
+
async def __pre_destroy__(self):
|
|
502
|
+
"""
|
|
503
|
+
Method called before actor destroy
|
|
504
|
+
"""
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
async def __on_receive__(self, tuple message):
|
|
508
|
+
"""
|
|
509
|
+
Handle message from other actors and dispatch them to user methods
|
|
510
|
+
|
|
511
|
+
Parameters
|
|
512
|
+
----------
|
|
513
|
+
message : tuple
|
|
514
|
+
Message shall be (method_name,) + args + (kwargs,)
|
|
515
|
+
"""
|
|
516
|
+
from .debug import debug_async_timeout
|
|
517
|
+
try:
|
|
518
|
+
method, call_method, args, kwargs = message
|
|
519
|
+
if call_method == CALL_METHOD_DEFAULT:
|
|
520
|
+
func = getattr(self, method)
|
|
521
|
+
if _has_no_lock_hint_for_method(func):
|
|
522
|
+
result = func(*args, **kwargs)
|
|
523
|
+
if asyncio.iscoroutine(result):
|
|
524
|
+
result = await result
|
|
525
|
+
else:
|
|
526
|
+
async with self._lock:
|
|
527
|
+
with debug_async_timeout('actor_lock_timeout',
|
|
528
|
+
"Method %s of actor %s hold lock timeout.",
|
|
529
|
+
method, self.uid):
|
|
530
|
+
result = func(*args, **kwargs)
|
|
531
|
+
if asyncio.iscoroutine(result):
|
|
532
|
+
result = await result
|
|
533
|
+
elif call_method == CALL_METHOD_BATCH:
|
|
534
|
+
func = getattr(self, method)
|
|
535
|
+
if _has_no_lock_hint_for_method(func):
|
|
536
|
+
args_list, kwargs_list = args
|
|
537
|
+
if kwargs_list is None:
|
|
538
|
+
kwargs_list = [{}] * len(args_list)
|
|
539
|
+
result = func.call_with_lists(args_list, kwargs_list)
|
|
540
|
+
if asyncio.iscoroutine(result):
|
|
541
|
+
result = await result
|
|
542
|
+
else:
|
|
543
|
+
async with self._lock:
|
|
544
|
+
with debug_async_timeout('actor_lock_timeout',
|
|
545
|
+
"Batch method %s of actor %s hold lock timeout, batch size %s.",
|
|
546
|
+
method, self.uid, len(args)):
|
|
547
|
+
args_list, kwargs_list = args
|
|
548
|
+
if kwargs_list is None:
|
|
549
|
+
kwargs_list = [{}] * len(args_list)
|
|
550
|
+
result = func.call_with_lists(args_list, kwargs_list)
|
|
551
|
+
if asyncio.iscoroutine(result):
|
|
552
|
+
result = await result
|
|
553
|
+
else: # pragma: no cover
|
|
554
|
+
raise ValueError(f'call_method {call_method} not valid')
|
|
555
|
+
|
|
556
|
+
return await self._handle_actor_result(result)
|
|
557
|
+
except Exception as ex:
|
|
558
|
+
if _log_unhandled_errors:
|
|
559
|
+
from .debug import logger as debug_logger
|
|
560
|
+
|
|
561
|
+
# use `%.500` to avoid print too long messages
|
|
562
|
+
debug_logger.exception('Got unhandled error when handling message %.500r '
|
|
563
|
+
'in actor %s at %s', message, self.uid, self.address)
|
|
564
|
+
raise ex
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
# The @cython.binding(True) is for ray getting members.
|
|
568
|
+
# The value is True by default after cython >= 3.0.0
|
|
569
|
+
@cython.binding(True)
|
|
570
|
+
cdef class _Actor(_BaseActor):
|
|
571
|
+
def _create_lock(self):
|
|
572
|
+
return asyncio.locks.Lock()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
cdef class _FakeLock:
|
|
576
|
+
async def __aenter__(self):
|
|
577
|
+
pass
|
|
578
|
+
|
|
579
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
580
|
+
pass
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
# The @cython.binding(True) is for ray getting members.
|
|
584
|
+
# The value is True by default after cython >= 3.0.0
|
|
585
|
+
@cython.binding(True)
|
|
586
|
+
cdef class _StatelessActor(_BaseActor):
|
|
587
|
+
def _create_lock(self):
|
|
588
|
+
return _FakeLock()
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
cdef class BufferRef:
|
|
592
|
+
"""
|
|
593
|
+
Reference of a buffer
|
|
594
|
+
"""
|
|
595
|
+
_ref_to_buffers = weakref.WeakValueDictionary()
|
|
596
|
+
|
|
597
|
+
def __init__(self, str address, bytes uid):
|
|
598
|
+
self.uid = uid
|
|
599
|
+
self.address = address
|
|
600
|
+
|
|
601
|
+
@classmethod
|
|
602
|
+
def create(cls, buffer: Any, address: str, uid: bytes) -> "BufferRef":
|
|
603
|
+
ref = BufferRef(address, uid)
|
|
604
|
+
cls._ref_to_buffers[ref] = buffer
|
|
605
|
+
return ref
|
|
606
|
+
|
|
607
|
+
@classmethod
|
|
608
|
+
def get_buffer(cls, ref: "BufferRef"):
|
|
609
|
+
return cls._ref_to_buffers[ref]
|
|
610
|
+
|
|
611
|
+
def __getstate__(self):
|
|
612
|
+
return self.uid, self.address
|
|
613
|
+
|
|
614
|
+
def __setstate__(self, state):
|
|
615
|
+
self.uid, self.address = state
|
|
616
|
+
|
|
617
|
+
def __hash__(self):
|
|
618
|
+
return hash((self.address, self.uid))
|
|
619
|
+
|
|
620
|
+
def __eq__(self, other):
|
|
621
|
+
if type(other) != BufferRef:
|
|
622
|
+
return False
|
|
623
|
+
return self.address == other.address and self.uid == other.uid
|
|
624
|
+
|
|
625
|
+
def __repr__(self):
|
|
626
|
+
return f'BufferRef(uid={self.uid.hex()}, address={self.address})'
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
cdef class FileObjectRef:
|
|
630
|
+
"""
|
|
631
|
+
Reference of a file obj
|
|
632
|
+
"""
|
|
633
|
+
_ref_to_fileobjs = weakref.WeakValueDictionary()
|
|
634
|
+
|
|
635
|
+
def __init__(self, str address, bytes uid):
|
|
636
|
+
self.uid = uid
|
|
637
|
+
self.address = address
|
|
638
|
+
|
|
639
|
+
@classmethod
|
|
640
|
+
def create(cls, fileobj: AioFileObject, address: str, uid: bytes) -> "FileObjectRef":
|
|
641
|
+
ref = FileObjectRef(address, uid)
|
|
642
|
+
cls._ref_to_fileobjs[ref] = fileobj
|
|
643
|
+
return ref
|
|
644
|
+
|
|
645
|
+
@classmethod
|
|
646
|
+
def get_local_file_object(cls, ref: "FileObjectRef") -> AioFileObject:
|
|
647
|
+
return cls._ref_to_fileobjs[ref]
|
|
648
|
+
|
|
649
|
+
def __getstate__(self):
|
|
650
|
+
return self.uid, self.address
|
|
651
|
+
|
|
652
|
+
def __setstate__(self, state):
|
|
653
|
+
self.uid, self.address = state
|
|
654
|
+
|
|
655
|
+
def __hash__(self):
|
|
656
|
+
return hash((self.address, self.uid))
|
|
657
|
+
|
|
658
|
+
def __eq__(self, other):
|
|
659
|
+
if type(other) != FileObjectRef:
|
|
660
|
+
return False
|
|
661
|
+
return self.address == other.address and self.uid == other.uid
|
|
662
|
+
|
|
663
|
+
def __repr__(self):
|
|
664
|
+
return f'FileObjectRef(uid={self.uid.hex()}, address={self.address})'
|