xoscar 0.4.1__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.4.3__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.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.

@@ -17,8 +17,9 @@ from __future__ import annotations
17
17
 
18
18
  import asyncio
19
19
  import concurrent.futures as futures
20
+ import logging
20
21
  import weakref
21
- from typing import Any, Callable, Coroutine, Dict, Type
22
+ from typing import Any, Callable, Coroutine, Dict, Optional, Type
22
23
  from urllib.parse import urlparse
23
24
 
24
25
  from ...errors import ServerClosed
@@ -29,13 +30,15 @@ from .errors import ChannelClosed
29
30
 
30
31
  DEFAULT_DUMMY_ADDRESS = "dummy://0"
31
32
 
33
+ logger = logging.getLogger(__name__)
34
+
32
35
 
33
36
  class DummyChannel(Channel):
34
37
  """
35
38
  Channel for communications in same process.
36
39
  """
37
40
 
38
- __slots__ = "_in_queue", "_out_queue", "_closed"
41
+ __slots__ = "__weakref__", "_in_queue", "_out_queue", "_closed"
39
42
 
40
43
  name = "dummy"
41
44
 
@@ -100,8 +103,8 @@ class DummyServer(Server):
100
103
  _address_to_instances: weakref.WeakValueDictionary[str, "DummyServer"] = (
101
104
  weakref.WeakValueDictionary()
102
105
  )
103
- _channels: list[ChannelType]
104
- _tasks: list[asyncio.Task]
106
+ _channels: weakref.WeakSet[Channel]
107
+ _tasks: set[asyncio.Task]
105
108
  scheme: str | None = "dummy"
106
109
 
107
110
  def __init__(
@@ -111,8 +114,8 @@ class DummyServer(Server):
111
114
  ):
112
115
  super().__init__(address, channel_handler)
113
116
  self._closed = asyncio.Event()
114
- self._channels = []
115
- self._tasks = []
117
+ self._channels = weakref.WeakSet()
118
+ self._tasks = set()
116
119
 
117
120
  @classmethod
118
121
  def get_instance(cls, address: str):
@@ -178,7 +181,7 @@ class DummyServer(Server):
178
181
  f"{type(self).__name__} got unexpected "
179
182
  f'arguments: {",".join(kwargs)}'
180
183
  )
181
- self._channels.append(channel)
184
+ self._channels.add(channel)
182
185
  await self.channel_handler(channel)
183
186
 
184
187
  @implements(Server.stop)
@@ -203,6 +206,7 @@ class DummyClient(Client):
203
206
  self, local_address: str | None, dest_address: str | None, channel: Channel
204
207
  ):
205
208
  super().__init__(local_address, dest_address, channel)
209
+ self._task: Optional[asyncio.Task] = None
206
210
 
207
211
  @staticmethod
208
212
  @implements(Client.connect)
@@ -232,11 +236,25 @@ class DummyClient(Client):
232
236
  task = asyncio.create_task(conn_coro)
233
237
  client = DummyClient(local_address, dest_address, client_channel)
234
238
  client._task = task
235
- server._tasks.append(task)
239
+ server._tasks.add(task)
240
+
241
+ def _discard(t):
242
+ server._tasks.discard(t)
243
+ logger.info("Channel exit: %s", server_channel.info)
244
+
245
+ task.add_done_callback(_discard)
236
246
  return client
237
247
 
238
248
  @implements(Client.close)
239
249
  async def close(self):
240
250
  await super().close()
241
- self._task.cancel()
242
- self._task = None
251
+ if self._task is not None:
252
+ task_loop = self._task.get_loop()
253
+ if task_loop is not None:
254
+ if not task_loop.is_running():
255
+ logger.warning(
256
+ "Dummy channel cancel task on a stopped loop, dest address: %s.",
257
+ self.dest_address,
258
+ )
259
+ self._task.cancel()
260
+ self._task = None
@@ -113,7 +113,7 @@ class SocketChannel(Channel):
113
113
  class _BaseSocketServer(Server, metaclass=ABCMeta):
114
114
  __slots__ = "_aio_server", "_channels"
115
115
 
116
- _channels: list[ChannelType]
116
+ _channels: set[Channel]
117
117
 
118
118
  def __init__(
119
119
  self,
@@ -124,7 +124,7 @@ class _BaseSocketServer(Server, metaclass=ABCMeta):
124
124
  super().__init__(address, channel_handler)
125
125
  # asyncio.Server
126
126
  self._aio_server = aio_server
127
- self._channels = []
127
+ self._channels = set()
128
128
 
129
129
  @implements(Server.start)
130
130
  async def start(self):
@@ -170,9 +170,16 @@ class _BaseSocketServer(Server, metaclass=ABCMeta):
170
170
  dest_address=dest_address,
171
171
  channel_type=self.channel_type,
172
172
  )
173
- self._channels.append(channel)
173
+ self._channels.add(channel)
174
174
  # handle over channel to some handlers
175
- await self.channel_handler(channel)
175
+ try:
176
+ await self.channel_handler(channel)
177
+ finally:
178
+ if not channel.closed:
179
+ await channel.close()
180
+ # Remove channel if channel exit
181
+ self._channels.discard(channel)
182
+ logger.debug("Channel exit: %s", channel.info)
176
183
 
177
184
  @implements(Server.stop)
178
185
  async def stop(self):
@@ -185,6 +192,7 @@ class _BaseSocketServer(Server, metaclass=ABCMeta):
185
192
  await asyncio.gather(
186
193
  *(channel.close() for channel in self._channels if not channel.closed)
187
194
  )
195
+ self._channels.clear()
188
196
 
189
197
  @property
190
198
  @implements(Server.stopped)
@@ -368,7 +368,7 @@ class UCXServer(Server):
368
368
  scheme = "ucx"
369
369
 
370
370
  _ucp_listener: "ucp.Listener" # type: ignore
371
- _channels: List[UCXChannel]
371
+ _channels: set[UCXChannel]
372
372
 
373
373
  def __init__(
374
374
  self,
@@ -381,7 +381,7 @@ class UCXServer(Server):
381
381
  self.host = host
382
382
  self.port = port
383
383
  self._ucp_listener = ucp_listener
384
- self._channels = []
384
+ self._channels = set()
385
385
  self._closed = asyncio.Event()
386
386
 
387
387
  @classproperty
@@ -469,9 +469,16 @@ class UCXServer(Server):
469
469
  channel = UCXChannel(
470
470
  ucp_endpoint, local_address=local_address, dest_address=dest_address
471
471
  )
472
- self._channels.append(channel)
472
+ self._channels.add(channel)
473
473
  # handle over channel to some handlers
474
- await self.channel_handler(channel)
474
+ try:
475
+ await self.channel_handler(channel)
476
+ finally:
477
+ if not channel.closed:
478
+ await channel.close()
479
+ # Remove channel if channel exit
480
+ self._channels.discard(channel)
481
+ logger.debug("Channel exit: %s", channel.info)
475
482
 
476
483
  @implements(Server.stop)
477
484
  async def stop(self):
@@ -480,7 +487,7 @@ class UCXServer(Server):
480
487
  await asyncio.gather(
481
488
  *(channel.close() for channel in self._channels if not channel.closed)
482
489
  )
483
- self._channels = []
490
+ self._channels.clear()
484
491
  self._ucp_listener = None
485
492
  self._closed.set()
486
493
 
xoscar/backends/core.py CHANGED
@@ -16,8 +16,11 @@
16
16
  from __future__ import annotations
17
17
 
18
18
  import asyncio
19
+ import atexit
19
20
  import copy
20
21
  import logging
22
+ import threading
23
+ import weakref
21
24
  from typing import Type, Union
22
25
 
23
26
  from .._utils import Timer
@@ -31,8 +34,8 @@ ResultMessageType = Union[ResultMessage, ErrorMessage]
31
34
  logger = logging.getLogger(__name__)
32
35
 
33
36
 
34
- class ActorCaller:
35
- __slots__ = "_client_to_message_futures", "_clients", "_profiling_data"
37
+ class ActorCallerThreadLocal:
38
+ __slots__ = ("_client_to_message_futures", "_clients", "_profiling_data")
36
39
 
37
40
  _client_to_message_futures: dict[Client, dict[bytes, asyncio.Future]]
38
41
  _clients: dict[Client, asyncio.Task]
@@ -193,6 +196,7 @@ class ActorCaller:
193
196
  return await self.call_with_client(client, message, wait)
194
197
 
195
198
  async def stop(self):
199
+ logger.debug("Actor caller stop.")
196
200
  try:
197
201
  await asyncio.gather(*[client.close() for client in self._clients])
198
202
  except (ConnectionError, ServerClosed):
@@ -202,3 +206,38 @@ class ActorCaller:
202
206
  def cancel_tasks(self):
203
207
  # cancel listening for all clients
204
208
  _ = [task.cancel() for task in self._clients.values()]
209
+
210
+
211
+ class ActorCaller:
212
+ __slots__ = "_thread_local"
213
+
214
+ class _RefHolder:
215
+ pass
216
+
217
+ _close_loop = asyncio.new_event_loop()
218
+ _close_thread = threading.Thread(target=_close_loop.run_forever, daemon=True)
219
+ _close_thread.start()
220
+ atexit.register(_close_loop.call_soon_threadsafe, _close_loop.stop)
221
+
222
+ def __init__(self):
223
+ self._thread_local = threading.local()
224
+
225
+ def __getattr__(self, item):
226
+ try:
227
+ actor_caller = self._thread_local.actor_caller
228
+ except AttributeError:
229
+ thread_info = str(threading.current_thread())
230
+ logger.debug("Creating a new actor caller for thread: %s", thread_info)
231
+ actor_caller = self._thread_local.actor_caller = ActorCallerThreadLocal()
232
+ ref = self._thread_local.ref = ActorCaller._RefHolder()
233
+ # If the thread exit, we clean the related actor callers and channels.
234
+
235
+ def _cleanup():
236
+ asyncio.run_coroutine_threadsafe(actor_caller.stop(), self._close_loop)
237
+ logger.debug(
238
+ "Clean up the actor caller due to thread exit: %s", thread_info
239
+ )
240
+
241
+ weakref.finalize(ref, _cleanup)
242
+
243
+ return getattr(actor_caller, item)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xoscar
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -1,7 +1,7 @@
1
- xoscar-0.4.1.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
2
- xoscar-0.4.1.dist-info/RECORD,,
3
- xoscar-0.4.1.dist-info/METADATA,sha256=EM22rICxUdVBATkQoIRlCZgvAQakKY9lKIK0GYEPCb0,9042
4
- xoscar-0.4.1.dist-info/WHEEL,sha256=sLizzaCm5RO_U5e_F5zOShQNJ3TeuZ7GEwyo10nwIfE,153
1
+ xoscar-0.4.3.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
2
+ xoscar-0.4.3.dist-info/RECORD,,
3
+ xoscar-0.4.3.dist-info/METADATA,sha256=EZv1PBHlaLn83QR6PNDr8BJEIVe28FOJW579DR7BtYo,9042
4
+ xoscar-0.4.3.dist-info/WHEEL,sha256=sLizzaCm5RO_U5e_F5zOShQNJ3TeuZ7GEwyo10nwIfE,153
5
5
  xoscar/errors.py,sha256=wBlQOKsXf0Fc4skN39tDie0YZT-VIAuLNRgoDl2pZcA,1241
6
6
  xoscar/nvutils.py,sha256=qmW4mKLU0WB2yCs198ccQOgLL02zB7Fsa-AotO3NOmg,20412
7
7
  xoscar/utils.py,sha256=jUw6OICZUPBbmS1b3GE4vLctJf6fCKXrYtLtBuK-Oqc,16483
@@ -42,7 +42,7 @@ xoscar/metrics/backends/console/__init__.py,sha256=h_JgzSqV5lP6vQ6XX_17kE4IY4BRn
42
42
  xoscar/backends/message.cpython-312-aarch64-linux-gnu.so,sha256=PKUmScWty4eTytL1G8YV2F54xTpeiBqTtkpwY75bHz0,3488072
43
43
  xoscar/backends/context.py,sha256=Vr_PibRxYCDQ_gYK7r-BOlw9TXw8VQbFsVTH7K7mHPk,15470
44
44
  xoscar/backends/__init__.py,sha256=VHEBQcUWM5bj027W8EUf9PiJUAP7JoMrRw3Tsvy5ySw,643
45
- xoscar/backends/core.py,sha256=YcXVrMrTUjnrnH-RhrtUyxnCbwon8miq2UigCQv-y_Q,8039
45
+ xoscar/backends/core.py,sha256=YpFOwjD45Hrdv7zk_SELCC-Ni38aYsWTLigowenwjig,9380
46
46
  xoscar/backends/allocate_strategy.py,sha256=tC1Nbq2tJohahUwd-zoRYHEDX65wyuX8tmeY45uWj_w,4845
47
47
  xoscar/backends/pool.py,sha256=Z7Wdab9dBF3SdQpmzgZhY0d09oTvg5gpFgzYH7vuc4w,59841
48
48
  xoscar/backends/router.py,sha256=mhSvM5KVfV882jricVcpyxAqHEvhS4zL6ivczC6fOTE,7746
@@ -52,14 +52,14 @@ xoscar/backends/message.pyi,sha256=__2piPWLUQBmkDzx_nsHMOC2wHG53IKVGU_47zwgdKM,6
52
52
  xoscar/backends/test/__init__.py,sha256=j2ZfD6prD9WjUxRUDC7Eq5Z7N7TkL6fFr59oNyc_vY4,682
53
53
  xoscar/backends/test/pool.py,sha256=TW4X6J-92Pti66103poQBNDBznX6CBD3RLOc_zixjTo,7257
54
54
  xoscar/backends/test/backend.py,sha256=nv9WFhH5Bbq4Q1HB9yfpciZBaeHT4IQAtzugBWESrUY,1263
55
- xoscar/backends/communication/dummy.py,sha256=gaKPNiN4x2aGZV3IGaaa8eaweBVjRh8B19jU1B5t2yw,7798
55
+ xoscar/backends/communication/dummy.py,sha256=sKBqoFkftm6ynV3p5UNGI4VYFGlrOHJgJlqj35PGAgk,8479
56
56
  xoscar/backends/communication/errors.py,sha256=V3CdBe2xX9Rwv32f2dH2Msc84yaUhlyerZ42-739o1Q,723
57
57
  xoscar/backends/communication/utils.py,sha256=AmovE-hmWLXNCPwHafYuaRjOk8m42BUyT3XBqfXQRVI,3664
58
58
  xoscar/backends/communication/__init__.py,sha256=tB05BlK63iWQnfJgRzKt4mFKRtmWUki5hUGSZQwAotc,1050
59
59
  xoscar/backends/communication/core.py,sha256=sJeE3foRIqVPXldzYpFKHDSsabfAIFBU4JuXY4OyklY,2130
60
60
  xoscar/backends/communication/base.py,sha256=0P4Tr35GSWpRp394e9jVWUUoKKa-gIk177eYPw1BnSU,7421
61
- xoscar/backends/communication/socket.py,sha256=ZSDqS_Z9-Oxs2Q2fqWUuUJ9hNf4w8lNJ8gUv5r6Ji_Y,13691
62
- xoscar/backends/communication/ucx.py,sha256=7GAKIzlbxy-NpaDE9VLPde90tKxeU8lVZH8c_3ByuZ0,19703
61
+ xoscar/backends/communication/socket.py,sha256=R9jF_Iy3HTSPXkBGrws5ckK-EjYnaPBSTdMqMZYqxqc,13972
62
+ xoscar/backends/communication/ucx.py,sha256=_Dp9Ld2MWIa1txSGMnmfYwJDT0esxS-GOd2FQ4BdHiM,19960
63
63
  xoscar/backends/indigen/driver.py,sha256=VGzkacYKykegW5qhCuhx01gdgBZEKJjNIyfNCnA6Nm8,952
64
64
  xoscar/backends/indigen/__init__.py,sha256=tKHP5ClzedBRBpZsLRVErR3EUNbbDm4CY4u0rCFJr44,685
65
65
  xoscar/backends/indigen/pool.py,sha256=mWYkOP4VVoUsXFgfpwruPuWblF6Waan5vxit8B-9_oQ,16852
File without changes