xoscar 0.4.0__tar.gz → 0.4.2__tar.gz

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 (81) hide show
  1. {xoscar-0.4.0/xoscar.egg-info → xoscar-0.4.2}/PKG-INFO +1 -1
  2. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/dummy.py +28 -10
  3. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/socket.py +20 -6
  4. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/ucx.py +12 -5
  5. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/core.py +90 -42
  6. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/pool.py +24 -16
  7. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/constants.py +2 -0
  8. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/aio.py +5 -1
  9. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/utils.py +2 -2
  10. {xoscar-0.4.0 → xoscar-0.4.2/xoscar.egg-info}/PKG-INFO +1 -1
  11. {xoscar-0.4.0 → xoscar-0.4.2}/MANIFEST.in +0 -0
  12. {xoscar-0.4.0 → xoscar-0.4.2}/pyproject.toml +0 -0
  13. {xoscar-0.4.0 → xoscar-0.4.2}/setup.cfg +0 -0
  14. {xoscar-0.4.0 → xoscar-0.4.2}/setup.py +0 -0
  15. {xoscar-0.4.0 → xoscar-0.4.2}/versioneer.py +0 -0
  16. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/__init__.py +0 -0
  17. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/_utils.pxd +0 -0
  18. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/_utils.pyx +0 -0
  19. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/_version.py +0 -0
  20. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/aio/__init__.py +0 -0
  21. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/aio/base.py +0 -0
  22. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/aio/file.py +0 -0
  23. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/aio/lru.py +0 -0
  24. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/aio/parallelism.py +0 -0
  25. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/api.py +0 -0
  26. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backend.py +0 -0
  27. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/__init__.py +0 -0
  28. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/allocate_strategy.py +0 -0
  29. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/__init__.py +0 -0
  30. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/base.py +0 -0
  31. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/core.py +0 -0
  32. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/errors.py +0 -0
  33. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/communication/utils.py +0 -0
  34. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/config.py +0 -0
  35. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/context.py +0 -0
  36. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/indigen/__init__.py +0 -0
  37. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/indigen/backend.py +0 -0
  38. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/indigen/driver.py +0 -0
  39. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/indigen/pool.py +0 -0
  40. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/message.pyx +0 -0
  41. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/router.py +0 -0
  42. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/test/__init__.py +0 -0
  43. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/test/backend.py +0 -0
  44. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/backends/test/pool.py +0 -0
  45. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/batch.py +0 -0
  46. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/collective/__init__.py +0 -0
  47. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/collective/common.py +0 -0
  48. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/collective/core.py +0 -0
  49. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/collective/process_group.py +0 -0
  50. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/collective/utils.py +0 -0
  51. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/context.pxd +0 -0
  52. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/context.pyx +0 -0
  53. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/core.pxd +0 -0
  54. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/core.pyx +0 -0
  55. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/debug.py +0 -0
  56. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/driver.py +0 -0
  57. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/errors.py +0 -0
  58. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/libcpp.pxd +0 -0
  59. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/__init__.py +0 -0
  60. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/api.py +0 -0
  61. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/__init__.py +0 -0
  62. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/console/__init__.py +0 -0
  63. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/console/console_metric.py +0 -0
  64. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/metric.py +0 -0
  65. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/prometheus/__init__.py +0 -0
  66. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/metrics/backends/prometheus/prometheus_metric.py +0 -0
  67. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/nvutils.py +0 -0
  68. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/profiling.py +0 -0
  69. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/__init__.py +0 -0
  70. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/core.pxd +0 -0
  71. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/core.pyx +0 -0
  72. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/cuda.py +0 -0
  73. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/exception.py +0 -0
  74. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/numpy.py +0 -0
  75. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/pyfury.py +0 -0
  76. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar/serialization/scipy.py +0 -0
  77. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar.egg-info/SOURCES.txt +0 -0
  78. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar.egg-info/dependency_links.txt +0 -0
  79. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar.egg-info/not-zip-safe +0 -0
  80. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar.egg-info/requires.txt +0 -0
  81. {xoscar-0.4.0 → xoscar-0.4.2}/xoscar.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xoscar
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -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
@@ -29,7 +29,7 @@ from typing import Any, Callable, Coroutine, Dict, Type
29
29
  from urllib.parse import urlparse
30
30
 
31
31
  from ..._utils import to_binary
32
- from ...constants import XOSCAR_UNIX_SOCKET_DIR
32
+ from ...constants import XOSCAR_CONNECT_TIMEOUT, XOSCAR_UNIX_SOCKET_DIR
33
33
  from ...serialization import AioDeserializer, AioSerializer, deserialize
34
34
  from ...utils import classproperty, implements, is_py_312, is_v6_ip
35
35
  from .base import Channel, ChannelType, Client, Server
@@ -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)
@@ -291,7 +299,13 @@ class SocketClient(Client):
291
299
  ) -> "Client":
292
300
  host, port_str = dest_address.rsplit(":", 1)
293
301
  port = int(port_str)
294
- (reader, writer) = await asyncio.open_connection(host=host, port=port, **kwargs)
302
+ config = kwargs.get("config", {})
303
+ connect_timeout = config.get("connect_timeout", XOSCAR_CONNECT_TIMEOUT)
304
+ fut = asyncio.open_connection(host=host, port=port)
305
+ try:
306
+ reader, writer = await asyncio.wait_for(fut, timeout=connect_timeout)
307
+ except asyncio.TimeoutError:
308
+ raise ConnectionError("connect timeout")
295
309
  channel = SocketChannel(
296
310
  reader, writer, local_address=local_address, dest_address=dest_address
297
311
  )
@@ -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
 
@@ -18,6 +18,8 @@ from __future__ import annotations
18
18
  import asyncio
19
19
  import copy
20
20
  import logging
21
+ import threading
22
+ import weakref
21
23
  from typing import Type, Union
22
24
 
23
25
  from .._utils import Timer
@@ -31,8 +33,8 @@ ResultMessageType = Union[ResultMessage, ErrorMessage]
31
33
  logger = logging.getLogger(__name__)
32
34
 
33
35
 
34
- class ActorCaller:
35
- __slots__ = "_client_to_message_futures", "_clients", "_profiling_data"
36
+ class ActorCallerThreadLocal:
37
+ __slots__ = ("_client_to_message_futures", "_clients", "_profiling_data")
36
38
 
37
39
  _client_to_message_futures: dict[Client, dict[bytes, asyncio.Future]]
38
40
  _clients: dict[Client, asyncio.Task]
@@ -70,50 +72,61 @@ class ActorCaller:
70
72
  return client
71
73
 
72
74
  async def _listen(self, client: Client):
73
- while not client.closed:
74
- try:
75
+ try:
76
+ while not client.closed:
75
77
  try:
76
- message: _MessageBase = await client.recv()
77
- except (EOFError, ConnectionError, BrokenPipeError):
78
- # remote server closed, close client and raise ServerClosed
79
78
  try:
80
- await client.close()
81
- except (ConnectionError, BrokenPipeError):
82
- # close failed, ignore it
79
+ message: _MessageBase = await client.recv()
80
+ except (EOFError, ConnectionError, BrokenPipeError) as e:
81
+ # AssertionError is from get_header
82
+ # remote server closed, close client and raise ServerClosed
83
+ logger.debug(f"{client.dest_address} close due to {e}")
84
+ try:
85
+ await client.close()
86
+ except (ConnectionError, BrokenPipeError):
87
+ # close failed, ignore it
88
+ pass
89
+ raise ServerClosed(
90
+ f"Remote server {client.dest_address} closed: {e}"
91
+ ) from None
92
+ future = self._client_to_message_futures[client].pop(
93
+ message.message_id
94
+ )
95
+ if not future.done():
96
+ future.set_result(message)
97
+ except DeserializeMessageFailed as e:
98
+ message_id = e.message_id
99
+ future = self._client_to_message_futures[client].pop(message_id)
100
+ future.set_exception(e.__cause__) # type: ignore
101
+ except Exception as e: # noqa: E722 # pylint: disable=bare-except
102
+ message_futures = self._client_to_message_futures[client]
103
+ self._client_to_message_futures[client] = dict()
104
+ for future in message_futures.values():
105
+ future.set_exception(copy.copy(e))
106
+ finally:
107
+ # message may have Ray ObjectRef, delete it early in case next loop doesn't run
108
+ # as soon as expected.
109
+ try:
110
+ del message
111
+ except NameError:
83
112
  pass
84
- raise ServerClosed(
85
- f"Remote server {client.dest_address} closed"
86
- ) from None
87
- future = self._client_to_message_futures[client].pop(message.message_id)
88
- if not future.done():
89
- future.set_result(message)
90
- except DeserializeMessageFailed as e:
91
- message_id = e.message_id
92
- future = self._client_to_message_futures[client].pop(message_id)
93
- future.set_exception(e.__cause__) # type: ignore
94
- except Exception as e: # noqa: E722 # pylint: disable=bare-except
95
- message_futures = self._client_to_message_futures[client]
96
- self._client_to_message_futures[client] = dict()
97
- for future in message_futures.values():
98
- future.set_exception(copy.copy(e))
99
- finally:
100
- # message may have Ray ObjectRef, delete it early in case next loop doesn't run
101
- # as soon as expected.
102
- try:
103
- del message
104
- except NameError:
105
- pass
106
- try:
107
- del future
108
- except NameError:
109
- pass
110
- await asyncio.sleep(0)
113
+ try:
114
+ del future
115
+ except NameError:
116
+ pass
117
+ await asyncio.sleep(0)
111
118
 
112
- message_futures = self._client_to_message_futures[client]
113
- self._client_to_message_futures[client] = dict()
114
- error = ServerClosed(f"Remote server {client.dest_address} closed")
115
- for future in message_futures.values():
116
- future.set_exception(copy.copy(error))
119
+ message_futures = self._client_to_message_futures[client]
120
+ self._client_to_message_futures[client] = dict()
121
+ error = ServerClosed(f"Remote server {client.dest_address} closed")
122
+ for future in message_futures.values():
123
+ future.set_exception(copy.copy(error))
124
+ finally:
125
+ try:
126
+ await client.close()
127
+ except: # noqa: E722 # nosec # pylint: disable=bare-except
128
+ # ignore all error if fail to close at last
129
+ pass
117
130
 
118
131
  async def call_with_client(
119
132
  self, client: Client, message: _MessageBase, wait: bool = True
@@ -182,6 +195,7 @@ class ActorCaller:
182
195
  return await self.call_with_client(client, message, wait)
183
196
 
184
197
  async def stop(self):
198
+ logger.debug("Actor caller stop.")
185
199
  try:
186
200
  await asyncio.gather(*[client.close() for client in self._clients])
187
201
  except (ConnectionError, ServerClosed):
@@ -191,3 +205,37 @@ class ActorCaller:
191
205
  def cancel_tasks(self):
192
206
  # cancel listening for all clients
193
207
  _ = [task.cancel() for task in self._clients.values()]
208
+
209
+
210
+ class ActorCaller:
211
+ __slots__ = "_thread_local"
212
+
213
+ class _RefHolder:
214
+ pass
215
+
216
+ _close_loop = asyncio.new_event_loop()
217
+ _close_thread = threading.Thread(target=_close_loop.run_forever, daemon=True)
218
+ _close_thread.start()
219
+
220
+ def __init__(self):
221
+ self._thread_local = threading.local()
222
+
223
+ def __getattr__(self, item):
224
+ try:
225
+ actor_caller = self._thread_local.actor_caller
226
+ except AttributeError:
227
+ thread_info = str(threading.current_thread())
228
+ logger.debug("Creating a new actor caller for thread: %s", thread_info)
229
+ actor_caller = self._thread_local.actor_caller = ActorCallerThreadLocal()
230
+ ref = self._thread_local.ref = ActorCaller._RefHolder()
231
+ # If the thread exit, we clean the related actor callers and channels.
232
+
233
+ def _cleanup():
234
+ asyncio.run_coroutine_threadsafe(actor_caller.stop(), self._close_loop)
235
+ logger.debug(
236
+ "Clean up the actor caller due to thread exit: %s", thread_info
237
+ )
238
+
239
+ weakref.finalize(ref, _cleanup)
240
+
241
+ return getattr(actor_caller, item)
@@ -551,23 +551,31 @@ class AbstractActorPool(ABC):
551
551
  return False
552
552
 
553
553
  async def on_new_channel(self, channel: Channel):
554
- while not self._stopped.is_set():
555
- try:
556
- message = await channel.recv()
557
- except EOFError:
558
- # no data to read, check channel
554
+ try:
555
+ while not self._stopped.is_set():
559
556
  try:
560
- await channel.close()
561
- except (ConnectionError, EOFError):
562
- # close failed, ignore
563
- pass
564
- return
565
- if await self._handle_ucx_meta_message(message, channel):
566
- continue
567
- asyncio.create_task(self.process_message(message, channel))
568
- # delete to release the reference of message
569
- del message
570
- await asyncio.sleep(0)
557
+ message = await channel.recv()
558
+ except (EOFError, ConnectionError, BrokenPipeError) as e:
559
+ logger.debug(f"pool: close connection due to {e}")
560
+ # no data to read, check channel
561
+ try:
562
+ await channel.close()
563
+ except (ConnectionError, EOFError):
564
+ # close failed, ignore
565
+ pass
566
+ return
567
+ if await self._handle_ucx_meta_message(message, channel):
568
+ continue
569
+ asyncio.create_task(self.process_message(message, channel))
570
+ # delete to release the reference of message
571
+ del message
572
+ await asyncio.sleep(0)
573
+ finally:
574
+ try:
575
+ await channel.close()
576
+ except: # noqa: E722 # nosec # pylint: disable=bare-except
577
+ # ignore all error if fail to close at last
578
+ pass
571
579
 
572
580
  async def __aenter__(self):
573
581
  await self.start()
@@ -19,3 +19,5 @@ XOSCAR_TEMP_DIR = Path(os.getenv("XOSCAR_DIR", Path.home())) / ".xoscar"
19
19
 
20
20
  # unix socket.
21
21
  XOSCAR_UNIX_SOCKET_DIR = XOSCAR_TEMP_DIR / "socket"
22
+
23
+ XOSCAR_CONNECT_TIMEOUT = 8
@@ -77,7 +77,11 @@ MALFORMED_MSG = "Received malformed data, please check Xoscar version on both si
77
77
  def get_header_length(header_bytes: bytes):
78
78
  version = struct.unpack("B", header_bytes[:1])[0]
79
79
  # now we only have default version
80
- assert version == DEFAULT_SERIALIZATION_VERSION, MALFORMED_MSG
80
+ if version != DEFAULT_SERIALIZATION_VERSION:
81
+ # when version not matched,
82
+ # we will immediately abort the connection
83
+ # EOFError will be captured by channel
84
+ raise EOFError(MALFORMED_MSG)
81
85
  # header length
82
86
  header_length = struct.unpack("<Q", header_bytes[1:9])[0]
83
87
  # compress
@@ -19,11 +19,11 @@ import asyncio
19
19
  import dataclasses
20
20
  import functools
21
21
  import importlib
22
+ import importlib.util as importlib_utils
22
23
  import inspect
23
24
  import io
24
25
  import logging
25
26
  import os
26
- import pkgutil
27
27
  import random
28
28
  import socket
29
29
  import sys
@@ -267,7 +267,7 @@ def lazy_import(
267
267
  self._on_loads.append(func)
268
268
  return func
269
269
 
270
- if pkgutil.find_loader(prefix_name) is not None:
270
+ if importlib_utils.find_spec(prefix_name) is not None:
271
271
  return LazyModule()
272
272
  elif placeholder:
273
273
  return ModulePlaceholder(prefix_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xoscar
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes