xoscar 0.4.0__cp312-cp312-macosx_10_9_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.

Potentially problematic release.


This version of xoscar might be problematic. Click here for more details.

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