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/api.py
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import functools
|
|
20
|
+
import inspect
|
|
21
|
+
import logging
|
|
22
|
+
import threading
|
|
23
|
+
import uuid
|
|
24
|
+
from collections import defaultdict
|
|
25
|
+
from numbers import Number
|
|
26
|
+
from typing import (
|
|
27
|
+
TYPE_CHECKING,
|
|
28
|
+
Any,
|
|
29
|
+
Awaitable,
|
|
30
|
+
Dict,
|
|
31
|
+
Generic,
|
|
32
|
+
List,
|
|
33
|
+
Optional,
|
|
34
|
+
Tuple,
|
|
35
|
+
Type,
|
|
36
|
+
TypeVar,
|
|
37
|
+
Union,
|
|
38
|
+
)
|
|
39
|
+
from urllib.parse import urlparse
|
|
40
|
+
|
|
41
|
+
from .aio import AioFileObject
|
|
42
|
+
from .backend import get_backend
|
|
43
|
+
from .context import get_context
|
|
44
|
+
from .core import ActorRef, BufferRef, FileObjectRef, _Actor, _StatelessActor
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from .backends.config import ActorPoolConfig
|
|
48
|
+
from .backends.pool import MainActorPoolType
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def create_actor(
|
|
54
|
+
actor_cls: Type, *args, uid=None, address=None, **kwargs
|
|
55
|
+
) -> ActorRef:
|
|
56
|
+
# TODO: explain default values.
|
|
57
|
+
"""
|
|
58
|
+
Create an actor.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
actor_cls : Actor
|
|
63
|
+
Actor class.
|
|
64
|
+
args : tuple
|
|
65
|
+
Positional arguments for ``actor_cls.__init__``.
|
|
66
|
+
uid : identifier, default=None
|
|
67
|
+
Actor identifier.
|
|
68
|
+
address : str, default=None
|
|
69
|
+
Address to locate the actor.
|
|
70
|
+
kwargs : dict
|
|
71
|
+
Keyword arguments for ``actor_cls.__init__``.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
ActorRef
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
ctx = get_context()
|
|
79
|
+
return await ctx.create_actor(actor_cls, *args, uid=uid, address=address, **kwargs)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def has_actor(actor_ref: ActorRef) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Check if the given actor exists.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
actor_ref : ActorRef
|
|
89
|
+
Reference to an actor.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
bool
|
|
94
|
+
"""
|
|
95
|
+
ctx = get_context()
|
|
96
|
+
return await ctx.has_actor(actor_ref)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def destroy_actor(actor_ref: ActorRef):
|
|
100
|
+
"""
|
|
101
|
+
Destroy an actor by its reference.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
actor_ref : ActorRef
|
|
106
|
+
Reference to an actor.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
bool
|
|
111
|
+
"""
|
|
112
|
+
ctx = get_context()
|
|
113
|
+
return await ctx.destroy_actor(actor_ref)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
async def actor_ref(*args, **kwargs) -> ActorRef:
|
|
117
|
+
"""
|
|
118
|
+
Create a reference to an actor.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
ActorRef
|
|
123
|
+
"""
|
|
124
|
+
# TODO: refine the argument list for better user experience.
|
|
125
|
+
ctx = get_context()
|
|
126
|
+
return await ctx.actor_ref(*args, **kwargs)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def kill_actor(actor_ref: ActorRef):
|
|
130
|
+
# TODO: explain the meaning of 'kill'
|
|
131
|
+
"""
|
|
132
|
+
Forcefully kill an actor.
|
|
133
|
+
|
|
134
|
+
It's important to note that this operation is potentially
|
|
135
|
+
dangerous as it may result in the termination of other
|
|
136
|
+
associated actors. Only proceed if you understand the
|
|
137
|
+
potential impact on associated actors and can handle any
|
|
138
|
+
resulting consequences.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
actor_ref : ActorRef
|
|
143
|
+
Reference to an actor.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
bool
|
|
148
|
+
"""
|
|
149
|
+
ctx = get_context()
|
|
150
|
+
return await ctx.kill_actor(actor_ref)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def create_actor_pool(
|
|
154
|
+
address: str, n_process: int | None = None, **kwargs
|
|
155
|
+
) -> "MainActorPoolType":
|
|
156
|
+
# TODO: explain default values.
|
|
157
|
+
"""
|
|
158
|
+
Create an actor pool.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
address: str
|
|
163
|
+
Address of the actor pool.
|
|
164
|
+
n_process: Optional[int], default=None
|
|
165
|
+
Number of processes.
|
|
166
|
+
kwargs : dict
|
|
167
|
+
Other keyword arguments for the actor pool.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
MainActorPoolType
|
|
172
|
+
"""
|
|
173
|
+
if address is None:
|
|
174
|
+
raise ValueError("address has to be provided")
|
|
175
|
+
if "://" not in address:
|
|
176
|
+
scheme = None
|
|
177
|
+
else:
|
|
178
|
+
scheme = urlparse(address).scheme or None
|
|
179
|
+
|
|
180
|
+
return await get_backend(scheme).create_actor_pool(
|
|
181
|
+
address, n_process=n_process, **kwargs
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
async def wait_for(fut: Awaitable[Any], timeout: int | float | None = None) -> Any:
|
|
186
|
+
# asyncio.wait_for() on Xoscar actor call cannot work as expected,
|
|
187
|
+
# because when time out, the future will be cancelled, but an actor call will catch this error,
|
|
188
|
+
# and send a CancelMessage to the dest pool, if the CancelMessage cannot be processed correctly(e.g. the dest pool hangs),
|
|
189
|
+
# the time out will never happen. Thus this PR added a new API so that no matter the CancelMessage delivered or not,
|
|
190
|
+
# the timeout will happen as expected.
|
|
191
|
+
loop = asyncio.get_running_loop()
|
|
192
|
+
new_fut = loop.create_future()
|
|
193
|
+
task = asyncio.ensure_future(fut)
|
|
194
|
+
|
|
195
|
+
def on_done(f: asyncio.Future):
|
|
196
|
+
if new_fut.done():
|
|
197
|
+
return
|
|
198
|
+
if f.cancelled():
|
|
199
|
+
new_fut.cancel()
|
|
200
|
+
elif f.exception():
|
|
201
|
+
new_fut.set_exception(f.exception()) # type: ignore
|
|
202
|
+
else:
|
|
203
|
+
new_fut.set_result(f.result())
|
|
204
|
+
|
|
205
|
+
task.add_done_callback(on_done)
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
return await asyncio.wait_for(new_fut, timeout)
|
|
209
|
+
except asyncio.TimeoutError:
|
|
210
|
+
if not task.done():
|
|
211
|
+
try:
|
|
212
|
+
task.cancel() # Try to cancel without waiting
|
|
213
|
+
except Exception:
|
|
214
|
+
logger.warning("Failed to cancel task", exc_info=True)
|
|
215
|
+
raise
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def buffer_ref(address: str, buffer: Any) -> BufferRef:
|
|
219
|
+
"""
|
|
220
|
+
Init buffer ref according address and buffer.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
address
|
|
225
|
+
The address of the buffer.
|
|
226
|
+
buffer
|
|
227
|
+
CPU / GPU buffer. Need to support for slicing and retrieving the length.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
----------
|
|
231
|
+
BufferRef obj.
|
|
232
|
+
"""
|
|
233
|
+
ctx = get_context()
|
|
234
|
+
return ctx.buffer_ref(address, buffer)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def file_object_ref(address: str, fileobj: AioFileObject) -> FileObjectRef:
|
|
238
|
+
"""
|
|
239
|
+
Init file object ref according to address and aio file obj.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
address
|
|
244
|
+
The address of the file obj.
|
|
245
|
+
fileobj
|
|
246
|
+
Aio file object.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
----------
|
|
250
|
+
FileObjectRef obj.
|
|
251
|
+
"""
|
|
252
|
+
ctx = get_context()
|
|
253
|
+
return ctx.file_object_ref(address, fileobj)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def copy_to(
|
|
257
|
+
local_buffers_or_fileobjs: list,
|
|
258
|
+
remote_refs: List[Union[BufferRef, FileObjectRef]],
|
|
259
|
+
block_size: Optional[int] = None,
|
|
260
|
+
):
|
|
261
|
+
"""
|
|
262
|
+
Copy data from local buffers to remote buffers or copy local file objects to remote file objects.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
local_buffers_or_fileobjs
|
|
267
|
+
Local buffers or file objects.
|
|
268
|
+
remote_refs
|
|
269
|
+
Remote buffer refs or file object refs.
|
|
270
|
+
block_size
|
|
271
|
+
Transfer block size when non-ucx
|
|
272
|
+
"""
|
|
273
|
+
ctx = get_context()
|
|
274
|
+
return await ctx.copy_to(local_buffers_or_fileobjs, remote_refs, block_size)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
async def wait_actor_pool_recovered(address: str, main_pool_address: str | None = None):
|
|
278
|
+
"""
|
|
279
|
+
Wait until the specified actor pool has recovered from failure.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
address: str
|
|
284
|
+
Address of the actor pool.
|
|
285
|
+
main_pool_address: Optional[str], default=None
|
|
286
|
+
Address of corresponding main actor pool.
|
|
287
|
+
|
|
288
|
+
Returns
|
|
289
|
+
-------
|
|
290
|
+
"""
|
|
291
|
+
ctx = get_context()
|
|
292
|
+
return await ctx.wait_actor_pool_recovered(address, main_pool_address)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
async def get_pool_config(address: str) -> "ActorPoolConfig":
|
|
296
|
+
"""
|
|
297
|
+
Get the configuration of specified actor pool.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
address: str
|
|
302
|
+
Address of the actor pool.
|
|
303
|
+
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
ActorPoolConfig
|
|
307
|
+
"""
|
|
308
|
+
ctx = get_context()
|
|
309
|
+
return await ctx.get_pool_config(address)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def setup_cluster(address_to_resources: Dict[str, Dict[str, Number]]):
|
|
313
|
+
scheme_to_address_resources: defaultdict[str | None, dict] = defaultdict(dict)
|
|
314
|
+
for address, resources in address_to_resources.items():
|
|
315
|
+
if address is None:
|
|
316
|
+
raise ValueError("address has to be provided")
|
|
317
|
+
if "://" not in address:
|
|
318
|
+
scheme = None
|
|
319
|
+
else:
|
|
320
|
+
scheme = urlparse(address).scheme or None
|
|
321
|
+
|
|
322
|
+
scheme_to_address_resources[scheme][address] = resources
|
|
323
|
+
for scheme, address_resources in scheme_to_address_resources.items():
|
|
324
|
+
get_backend(scheme).get_driver_cls().setup_cluster(address_resources)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
T = TypeVar("T")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class IteratorWrapper(Generic[T]):
|
|
331
|
+
def __init__(self, uid: str, actor_addr: str, actor_uid: str):
|
|
332
|
+
self._uid = uid
|
|
333
|
+
self._actor_addr = actor_addr
|
|
334
|
+
self._actor_uid = actor_uid
|
|
335
|
+
self._actor_ref = None
|
|
336
|
+
self._gc_destroy = True
|
|
337
|
+
|
|
338
|
+
async def destroy(self):
|
|
339
|
+
if self._actor_ref is None:
|
|
340
|
+
self._actor_ref = await actor_ref(
|
|
341
|
+
address=self._actor_addr, uid=self._actor_uid
|
|
342
|
+
)
|
|
343
|
+
assert self._actor_ref is not None
|
|
344
|
+
return await self._actor_ref.__xoscar_destroy_generator__(self._uid)
|
|
345
|
+
|
|
346
|
+
def __del__(self):
|
|
347
|
+
# It's not a good idea to spawn a new thread and join in __del__,
|
|
348
|
+
# but currently it's the only way to GC the generator.
|
|
349
|
+
# TODO(codingl2k1): This __del__ may hangs if the program is exiting.
|
|
350
|
+
if self._gc_destroy:
|
|
351
|
+
thread = threading.Thread(
|
|
352
|
+
target=asyncio.run, args=(self.destroy(),), daemon=True
|
|
353
|
+
)
|
|
354
|
+
thread.start()
|
|
355
|
+
thread.join()
|
|
356
|
+
|
|
357
|
+
def __aiter__(self):
|
|
358
|
+
return self
|
|
359
|
+
|
|
360
|
+
def __getstate__(self):
|
|
361
|
+
# Transfer gc destroy during serialization.
|
|
362
|
+
state = self.__dict__.copy()
|
|
363
|
+
state["_gc_destroy"] = True
|
|
364
|
+
self._gc_destroy = False
|
|
365
|
+
return state
|
|
366
|
+
|
|
367
|
+
async def __anext__(self) -> T:
|
|
368
|
+
if self._actor_ref is None:
|
|
369
|
+
self._actor_ref = await actor_ref(
|
|
370
|
+
address=self._actor_addr, uid=self._actor_uid
|
|
371
|
+
)
|
|
372
|
+
try:
|
|
373
|
+
assert self._actor_ref is not None
|
|
374
|
+
return await self._actor_ref.__xoscar_next__(self._uid)
|
|
375
|
+
except Exception as e:
|
|
376
|
+
if "StopIteration" in str(e):
|
|
377
|
+
raise StopAsyncIteration
|
|
378
|
+
else:
|
|
379
|
+
raise
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class AsyncActorMixin:
|
|
383
|
+
@classmethod
|
|
384
|
+
def default_uid(cls):
|
|
385
|
+
return cls.__name__
|
|
386
|
+
|
|
387
|
+
def __new__(cls, *args, **kwargs):
|
|
388
|
+
try:
|
|
389
|
+
return _actor_implementation[cls](*args, **kwargs)
|
|
390
|
+
except KeyError:
|
|
391
|
+
return super().__new__(cls, *args, **kwargs)
|
|
392
|
+
|
|
393
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
394
|
+
super().__init__()
|
|
395
|
+
self._generators: Dict[str, IteratorWrapper] = {}
|
|
396
|
+
|
|
397
|
+
async def __post_create__(self):
|
|
398
|
+
"""
|
|
399
|
+
Method called after actor creation
|
|
400
|
+
"""
|
|
401
|
+
return await super().__post_create__()
|
|
402
|
+
|
|
403
|
+
async def __pre_destroy__(self):
|
|
404
|
+
"""
|
|
405
|
+
Method called before actor destroy
|
|
406
|
+
"""
|
|
407
|
+
return await super().__pre_destroy__()
|
|
408
|
+
|
|
409
|
+
async def __on_receive__(self, message: Tuple[Any]):
|
|
410
|
+
"""
|
|
411
|
+
Handle message from other actors and dispatch them to user methods
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
message : tuple
|
|
416
|
+
Message shall be (method_name,) + args + (kwargs,)
|
|
417
|
+
"""
|
|
418
|
+
return await super().__on_receive__(message) # type: ignore
|
|
419
|
+
|
|
420
|
+
async def __xoscar_next__(self, generator_uid: str) -> Any:
|
|
421
|
+
"""
|
|
422
|
+
Iter the next of generator.
|
|
423
|
+
|
|
424
|
+
Parameters
|
|
425
|
+
----------
|
|
426
|
+
generator_uid: str
|
|
427
|
+
The uid of generator
|
|
428
|
+
|
|
429
|
+
Returns
|
|
430
|
+
-------
|
|
431
|
+
The next value of generator
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
def _wrapper(_gen):
|
|
435
|
+
try:
|
|
436
|
+
return next(_gen)
|
|
437
|
+
except StopIteration:
|
|
438
|
+
return stop
|
|
439
|
+
|
|
440
|
+
async def _async_wrapper(_gen):
|
|
441
|
+
try:
|
|
442
|
+
# anext is only available for Python >= 3.10
|
|
443
|
+
return await _gen.__anext__() # noqa: F821
|
|
444
|
+
except StopAsyncIteration:
|
|
445
|
+
return stop
|
|
446
|
+
|
|
447
|
+
if gen := self._generators.get(generator_uid):
|
|
448
|
+
stop = object()
|
|
449
|
+
try:
|
|
450
|
+
if inspect.isgenerator(gen):
|
|
451
|
+
r = await asyncio.to_thread(_wrapper, gen)
|
|
452
|
+
elif inspect.isasyncgen(gen):
|
|
453
|
+
r = await asyncio.create_task(_async_wrapper(gen))
|
|
454
|
+
else:
|
|
455
|
+
raise Exception(
|
|
456
|
+
f"The generator {generator_uid} should be a generator or an async generator, "
|
|
457
|
+
f"but a {type(gen)} is got."
|
|
458
|
+
)
|
|
459
|
+
except Exception as e:
|
|
460
|
+
logger.exception(
|
|
461
|
+
f"Destroy generator {generator_uid} due to an error encountered."
|
|
462
|
+
)
|
|
463
|
+
await self.__xoscar_destroy_generator__(generator_uid)
|
|
464
|
+
del gen # Avoid exception hold generator reference.
|
|
465
|
+
raise e
|
|
466
|
+
if r is stop:
|
|
467
|
+
await self.__xoscar_destroy_generator__(generator_uid)
|
|
468
|
+
del gen # Avoid exception hold generator reference.
|
|
469
|
+
raise Exception("StopIteration")
|
|
470
|
+
else:
|
|
471
|
+
return r
|
|
472
|
+
else:
|
|
473
|
+
raise RuntimeError(f"No iterator with id: {generator_uid}")
|
|
474
|
+
|
|
475
|
+
async def __xoscar_destroy_generator__(self, generator_uid: str):
|
|
476
|
+
"""
|
|
477
|
+
Destroy the generator.
|
|
478
|
+
|
|
479
|
+
Parameters
|
|
480
|
+
----------
|
|
481
|
+
generator_uid: str
|
|
482
|
+
The uid of generator
|
|
483
|
+
"""
|
|
484
|
+
logger.debug("Destroy generator: %s", generator_uid)
|
|
485
|
+
self._generators.pop(generator_uid, None)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def generator(func):
|
|
489
|
+
need_to_thread = not asyncio.iscoroutinefunction(func)
|
|
490
|
+
|
|
491
|
+
@functools.wraps(func)
|
|
492
|
+
async def _wrapper(self, *args, **kwargs):
|
|
493
|
+
if need_to_thread:
|
|
494
|
+
r = await asyncio.to_thread(func, self, *args, **kwargs)
|
|
495
|
+
else:
|
|
496
|
+
r = await func(self, *args, **kwargs)
|
|
497
|
+
if inspect.isgenerator(r) or inspect.isasyncgen(r):
|
|
498
|
+
gen_uid = uuid.uuid1().hex
|
|
499
|
+
logger.debug("Create generator: %s", gen_uid)
|
|
500
|
+
self._generators[gen_uid] = r
|
|
501
|
+
return IteratorWrapper(gen_uid, self.address, self.uid)
|
|
502
|
+
else:
|
|
503
|
+
return r
|
|
504
|
+
|
|
505
|
+
return _wrapper
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class Actor(AsyncActorMixin, _Actor):
|
|
509
|
+
pass
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class StatelessActor(AsyncActorMixin, _StatelessActor):
|
|
513
|
+
pass
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
_actor_implementation: Dict[Type[Actor], Type[Actor]] = dict()
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def register_actor_implementation(actor_cls: Type[Actor], impl_cls: Type[Actor]):
|
|
520
|
+
_actor_implementation[actor_cls] = impl_cls
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def unregister_actor_implementation(actor_cls: Type[Actor]):
|
|
524
|
+
try:
|
|
525
|
+
del _actor_implementation[actor_cls]
|
|
526
|
+
except KeyError:
|
|
527
|
+
pass
|
xoscar/backend.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Dict, Type
|
|
20
|
+
|
|
21
|
+
from .context import register_backend_context
|
|
22
|
+
from .driver import register_backend_driver
|
|
23
|
+
|
|
24
|
+
__all__ = ["BaseActorBackend", "register_backend", "get_backend"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaseActorBackend(ABC):
|
|
28
|
+
@staticmethod
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def name():
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def get_context_cls():
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
async def create_actor_pool(
|
|
40
|
+
cls, address: str, n_process: int | None = None, **kwargs
|
|
41
|
+
):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def get_driver_cls():
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_scheme_to_backend_cls: Dict[str, Type[BaseActorBackend]] = dict()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register_backend(backend_cls: Type[BaseActorBackend]):
|
|
54
|
+
name = backend_cls.name()
|
|
55
|
+
if isinstance(name, (list, tuple)):
|
|
56
|
+
names = name
|
|
57
|
+
else:
|
|
58
|
+
names = [name]
|
|
59
|
+
for name in names:
|
|
60
|
+
_scheme_to_backend_cls[name] = backend_cls
|
|
61
|
+
register_backend_context(name, backend_cls.get_context_cls())
|
|
62
|
+
register_backend_driver(name, backend_cls.get_driver_cls())
|
|
63
|
+
return backend_cls
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_backend(name):
|
|
67
|
+
return _scheme_to_backend_cls[name]
|
|
@@ -0,0 +1,14 @@
|
|
|
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.
|