xoscar 0.3.2__tar.gz → 0.4.0__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 (83) hide show
  1. {xoscar-0.3.2 → xoscar-0.4.0}/PKG-INFO +2 -2
  2. {xoscar-0.3.2 → xoscar-0.4.0}/pyproject.toml +5 -4
  3. {xoscar-0.3.2 → xoscar-0.4.0}/setup.cfg +7 -7
  4. {xoscar-0.3.2 → xoscar-0.4.0}/setup.py +53 -0
  5. xoscar-0.4.0/xoscar/aio/__init__.py +16 -0
  6. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/socket.py +49 -10
  7. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/ucx.py +20 -9
  8. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/indigen/pool.py +12 -12
  9. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/message.pyx +8 -0
  10. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/pool.py +5 -2
  11. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/collective/core.py +1 -1
  12. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/core.pyx +1 -11
  13. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/utils.py +17 -2
  14. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar.egg-info/PKG-INFO +2 -2
  15. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar.egg-info/SOURCES.txt +0 -1
  16. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar.egg-info/requires.txt +3 -9
  17. xoscar-0.3.2/xoscar/aio/__init__.py +0 -25
  18. xoscar-0.3.2/xoscar/aio/_threads.py +0 -35
  19. {xoscar-0.3.2 → xoscar-0.4.0}/MANIFEST.in +0 -0
  20. {xoscar-0.3.2 → xoscar-0.4.0}/versioneer.py +0 -0
  21. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/__init__.py +0 -0
  22. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/_utils.pxd +0 -0
  23. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/_utils.pyx +0 -0
  24. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/_version.py +0 -0
  25. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/aio/base.py +0 -0
  26. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/aio/file.py +0 -0
  27. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/aio/lru.py +0 -0
  28. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/aio/parallelism.py +0 -0
  29. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/api.py +0 -0
  30. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backend.py +0 -0
  31. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/__init__.py +0 -0
  32. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/allocate_strategy.py +0 -0
  33. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/__init__.py +0 -0
  34. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/base.py +0 -0
  35. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/core.py +0 -0
  36. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/dummy.py +0 -0
  37. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/errors.py +0 -0
  38. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/communication/utils.py +0 -0
  39. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/config.py +0 -0
  40. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/context.py +0 -0
  41. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/core.py +0 -0
  42. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/indigen/__init__.py +0 -0
  43. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/indigen/backend.py +0 -0
  44. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/indigen/driver.py +0 -0
  45. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/router.py +0 -0
  46. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/test/__init__.py +0 -0
  47. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/test/backend.py +0 -0
  48. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/backends/test/pool.py +0 -0
  49. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/batch.py +0 -0
  50. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/collective/__init__.py +0 -0
  51. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/collective/common.py +0 -0
  52. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/collective/process_group.py +0 -0
  53. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/collective/utils.py +0 -0
  54. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/constants.py +0 -0
  55. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/context.pxd +0 -0
  56. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/context.pyx +0 -0
  57. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/core.pxd +0 -0
  58. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/core.pyx +0 -0
  59. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/debug.py +0 -0
  60. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/driver.py +0 -0
  61. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/errors.py +0 -0
  62. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/libcpp.pxd +0 -0
  63. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/__init__.py +0 -0
  64. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/api.py +0 -0
  65. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/__init__.py +0 -0
  66. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/console/__init__.py +0 -0
  67. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/console/console_metric.py +0 -0
  68. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/metric.py +0 -0
  69. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/prometheus/__init__.py +0 -0
  70. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/metrics/backends/prometheus/prometheus_metric.py +0 -0
  71. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/nvutils.py +0 -0
  72. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/profiling.py +0 -0
  73. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/__init__.py +0 -0
  74. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/aio.py +0 -0
  75. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/core.pxd +0 -0
  76. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/cuda.py +0 -0
  77. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/exception.py +0 -0
  78. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/numpy.py +0 -0
  79. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/pyfury.py +0 -0
  80. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar/serialization/scipy.py +0 -0
  81. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar.egg-info/dependency_links.txt +0 -0
  82. {xoscar-0.3.2 → xoscar-0.4.0}/xoscar.egg-info/not-zip-safe +0 -0
  83. {xoscar-0.3.2 → xoscar-0.4.0}/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.3.2
3
+ Version: 0.4.0
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -11,10 +11,10 @@ License: Apache License 2.0
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
19
  Classifier: Topic :: Software Development :: Libraries
20
20
  Description-Content-Type: text/markdown
@@ -1,6 +1,7 @@
1
1
  [build-system]
2
2
  requires = [
3
- "setuptools<64",
3
+ "setuptools<64; python_version<'3.12'",
4
+ "setuptools>=75; python_version>='3.12'",
4
5
  "packaging",
5
6
  "wheel",
6
7
  "oldest-supported-numpy",
@@ -21,7 +22,7 @@ requires = [
21
22
  "pandas==1.4.0; python_version>='3.10' and python_version<'3.11' and platform_machine=='arm64'",
22
23
  "pandas==1.5.1; python_version>='3.11' and python_version<'3.12'",
23
24
  "pandas>=2.1.1; python_version>'3.11'",
24
- "numpy<2.0.0",
25
+ "numpy",
25
26
  "cython>=0.29.33",
26
27
  "requests>=2.4.0",
27
28
  "cloudpickle>=2.2.1; python_version>='3.11'",
@@ -42,6 +43,6 @@ markers = [
42
43
  ]
43
44
 
44
45
  [tool.cibuildwheel]
45
- build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*"]
46
- skip = "pp* *musllinux* *i686 cp36* cp38-win32 cp39-win32 cp310-win32 cp311-win32"
46
+ build = ["cp39-*", "cp310-*", "cp311-*", "cp312-*"]
47
+ skip = "pp* *musllinux* *i686 cp36* cp38-win32 cp39-win32 cp310-win32 cp311-win32 cp312-win32"
47
48
  manylinux-x86_64-image = "manylinux2014"
@@ -7,15 +7,15 @@ maintainer = Qin Xuye
7
7
  maintainer_email = qinxuye@xprobe.io
8
8
  license = Apache License 2.0
9
9
  url = http://github.com/xorbitsai/xoscar
10
- python_requires = >=3.8
10
+ python_requires = >=3.9
11
11
  classifier =
12
12
  Operating System :: OS Independent
13
13
  Programming Language :: Python
14
14
  Programming Language :: Python :: 3
15
- Programming Language :: Python :: 3.8
16
15
  Programming Language :: Python :: 3.9
17
16
  Programming Language :: Python :: 3.10
18
17
  Programming Language :: Python :: 3.11
18
+ Programming Language :: Python :: 3.12
19
19
  Programming Language :: Python :: Implementation :: CPython
20
20
  Topic :: Software Development :: Libraries
21
21
 
@@ -24,14 +24,13 @@ zip_safe = False
24
24
  include_package_data = True
25
25
  packages = find:
26
26
  install_requires =
27
- numpy>=1.14.0,<2.0.0
27
+ numpy>=1.14.0
28
28
  pandas>=1.0.0
29
29
  scipy>=1.0.0; sys_platform!="win32" or python_version>="3.10"
30
30
  scipy>=1.0.0,<=1.9.1; sys_platform=="win32" and python_version<"3.10"
31
31
  cloudpickle>=1.5.0
32
32
  psutil>=5.9.0
33
33
  tblib>=1.7.0
34
- pickle5; python_version<"3.8"
35
34
  uvloop>=0.14.0; sys_platform!="win32"
36
35
  packaging
37
36
 
@@ -50,15 +49,14 @@ dev =
50
49
  pytest-forked>=1.0
51
50
  pytest-asyncio>=0.14.0
52
51
  ipython>=6.5.0
53
- sphinx>=3.0.0,<5.0.0
52
+ sphinx
54
53
  pydata-sphinx-theme>=0.3.0
55
54
  sphinx-intl>=0.9.9
56
- mock>=4.0.0; python_version<"3.8"
57
55
  flake8>=3.8.0
58
56
  black
59
57
  doc =
60
58
  ipython>=6.5.0
61
- sphinx>=3.0.0,<5.0.0
59
+ sphinx
62
60
  pydata-sphinx-theme>=0.3.0
63
61
  sphinx-intl>=0.9.9
64
62
  extra =
@@ -82,6 +80,8 @@ omit =
82
80
  xoscar/nvutils.py
83
81
  *.pxd
84
82
  */tests/*
83
+ disable_warnings =
84
+ include-ignored
85
85
 
86
86
  [coverage:report]
87
87
  exclude_lines =
@@ -17,6 +17,7 @@ import platform
17
17
  import re
18
18
  import subprocess
19
19
  import sys
20
+ import sysconfig
20
21
  from distutils.command.build_ext import build_ext as _du_build_ext
21
22
  from distutils.file_util import copy_file, move_file
22
23
  from pathlib import Path
@@ -28,6 +29,7 @@ from Cython.Build import cythonize
28
29
  from packaging.version import Version
29
30
  from setuptools import Extension, setup
30
31
  from setuptools.command.build_ext import build_ext
32
+ from setuptools.command.install_lib import install_lib
31
33
  from setuptools.extension import Library
32
34
 
33
35
  try:
@@ -143,6 +145,45 @@ PLAT_TO_CMAKE = {
143
145
  "win-arm64": "ARM64",
144
146
  }
145
147
 
148
+ TARGET_TO_PLAT = {
149
+ 'x86': 'win32',
150
+ 'x64': 'win-amd64',
151
+ 'arm': 'win-arm32',
152
+ 'arm64': 'win-arm64',
153
+ }
154
+
155
+
156
+ # Copied from https://github.com/pypa/setuptools/blob/main/setuptools/_distutils/util.py#L50
157
+ def get_host_platform():
158
+ """
159
+ Return a string that identifies the current platform. Use this
160
+ function to distinguish platform-specific build directories and
161
+ platform-specific built distributions.
162
+ """
163
+
164
+ # This function initially exposed platforms as defined in Python 3.9
165
+ # even with older Python versions when distutils was split out.
166
+ # Now it delegates to stdlib sysconfig, but maintains compatibility.
167
+ return sysconfig.get_platform()
168
+
169
+
170
+ def get_platform():
171
+ if os.name == 'nt':
172
+ target = os.environ.get('VSCMD_ARG_TGT_ARCH')
173
+ return TARGET_TO_PLAT.get(target) or get_host_platform()
174
+ return get_host_platform()
175
+
176
+
177
+ plat_specifier = ".{}-{}".format(get_platform(), sys.implementation.cache_tag)
178
+
179
+
180
+ def get_build_lib():
181
+ return os.path.join("build", "lib" + plat_specifier)
182
+
183
+
184
+ def get_build_temp():
185
+ return os.path.join("build", 'temp' + plat_specifier)
186
+
146
187
 
147
188
  # A CMakeExtension needs a sourcedir instead of a file list.
148
189
  # The name must be the _single_ output extension from the CMake build.
@@ -154,6 +195,18 @@ class XoscarCmakeExtension(Extension):
154
195
 
155
196
 
156
197
  class CMakeBuild(build_ext):
198
+ def finalize_options(self):
199
+ """
200
+ For python 3.12, the build_temp and build_lib dirs are temp dirs which are depended on your OS,
201
+ which leads to that cannot find the copy directory during C++ compiled process.
202
+ However, for Python < 3.12, these two dirs can be automatically located in the `build` directory of the project directory.
203
+ Therefore, in order to be compatible with all Python versions,
204
+ directly using fixed dirs here by coping source codes from `setuptools`.
205
+ """
206
+ self.build_temp = get_build_temp()
207
+ self.build_lib = get_build_lib()
208
+ super().finalize_options()
209
+
157
210
  def copy_extensions_to_source(self):
158
211
  build_py = self.get_finalized_command('build_py')
159
212
  for ext in self.extensions:
@@ -0,0 +1,16 @@
1
+ # Copyright 2022-2023 XProbe Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from .file import AioFileObject
15
+ from .lru import alru_cache
16
+ from .parallelism import AioEvent
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
 
18
18
  import asyncio
19
19
  import concurrent.futures as futures
20
+ import logging
20
21
  import os
21
22
  import socket
22
23
  import sys
@@ -30,7 +31,7 @@ from urllib.parse import urlparse
30
31
  from ..._utils import to_binary
31
32
  from ...constants import XOSCAR_UNIX_SOCKET_DIR
32
33
  from ...serialization import AioDeserializer, AioSerializer, deserialize
33
- from ...utils import classproperty, implements
34
+ from ...utils import classproperty, implements, is_py_312, is_v6_ip
34
35
  from .base import Channel, ChannelType, Client, Server
35
36
  from .core import register_client, register_server
36
37
  from .utils import read_buffers, write_buffers
@@ -38,6 +39,9 @@ from .utils import read_buffers, write_buffers
38
39
  _is_windows: bool = sys.platform.startswith("win")
39
40
 
40
41
 
42
+ logger = logging.getLogger(__name__)
43
+
44
+
41
45
  class SocketChannel(Channel):
42
46
  __slots__ = "reader", "writer", "_channel_type", "_send_lock", "_recv_lock"
43
47
 
@@ -131,11 +135,23 @@ class _BaseSocketServer(Server, metaclass=ABCMeta):
131
135
  if timeout is None:
132
136
  await self._aio_server.serve_forever()
133
137
  else:
134
- future = asyncio.create_task(self._aio_server.serve_forever())
135
- try:
136
- await asyncio.wait_for(future, timeout=timeout)
137
- except (futures.TimeoutError, asyncio.TimeoutError):
138
- future.cancel()
138
+ if is_py_312():
139
+ # For python 3.12, there's a bug for `serve_forever`:
140
+ # https://github.com/python/cpython/issues/123720,
141
+ # which is unable to be cancelled.
142
+ # Here is really a simulation of `wait_for`
143
+ task = asyncio.create_task(self._aio_server.serve_forever())
144
+ await asyncio.sleep(timeout)
145
+ if task.done():
146
+ logger.warning(f"`serve_forever` should never be done.")
147
+ else:
148
+ task.cancel()
149
+ else:
150
+ future = asyncio.create_task(self._aio_server.serve_forever())
151
+ try:
152
+ await asyncio.wait_for(future, timeout=timeout)
153
+ except (futures.TimeoutError, asyncio.TimeoutError, TimeoutError):
154
+ future.cancel()
139
155
 
140
156
  @implements(Server.on_connected)
141
157
  async def on_connected(self, *args, **kwargs):
@@ -161,7 +177,10 @@ class _BaseSocketServer(Server, metaclass=ABCMeta):
161
177
  @implements(Server.stop)
162
178
  async def stop(self):
163
179
  self._aio_server.close()
164
- await self._aio_server.wait_closed()
180
+ # Python 3.12: # https://github.com/python/cpython/issues/104344
181
+ # `wait_closed` leads to hang
182
+ if not is_py_312():
183
+ await self._aio_server.wait_closed()
165
184
  # close all channels
166
185
  await asyncio.gather(
167
186
  *(channel.close() for channel in self._channels if not channel.closed)
@@ -201,17 +220,37 @@ class SocketServer(_BaseSocketServer):
201
220
  def channel_type(self) -> int:
202
221
  return ChannelType.remote
203
222
 
223
+ @classmethod
224
+ def parse_config(cls, config: dict) -> dict:
225
+ if config is None or not config:
226
+ return dict()
227
+ # we only need the following config
228
+ keys = ["listen_elastic_ip"]
229
+ parsed_config = {key: config[key] for key in keys if key in config}
230
+
231
+ return parsed_config
232
+
204
233
  @staticmethod
205
234
  @implements(Server.create)
206
235
  async def create(config: Dict) -> "Server":
207
236
  config = config.copy()
208
237
  if "address" in config:
209
238
  address = config.pop("address")
210
- host, port = address.split(":", 1)
239
+ host, port = address.rsplit(":", 1)
211
240
  port = int(port)
212
241
  else:
213
242
  host = config.pop("host")
214
243
  port = int(config.pop("port"))
244
+ _host = host
245
+ if config.pop("listen_elastic_ip", False):
246
+ # The Actor.address will be announce to client, and is not on our host,
247
+ # cannot actually listen on it,
248
+ # so we have to keep SocketServer.host untouched to make sure Actor.address not changed
249
+ if is_v6_ip(host):
250
+ _host = "::"
251
+ else:
252
+ _host = "0.0.0.0"
253
+
215
254
  handle_channel = config.pop("handle_channel")
216
255
  if "start_serving" not in config:
217
256
  config["start_serving"] = False
@@ -224,7 +263,7 @@ class SocketServer(_BaseSocketServer):
224
263
 
225
264
  port = port if port != 0 else None
226
265
  aio_server = await asyncio.start_server(
227
- handle_connection, host=host, port=port, **config
266
+ handle_connection, host=_host, port=port, **config
228
267
  )
229
268
 
230
269
  # get port of the socket if not specified
@@ -250,7 +289,7 @@ class SocketClient(Client):
250
289
  async def connect(
251
290
  dest_address: str, local_address: str | None = None, **kwargs
252
291
  ) -> "Client":
253
- host, port_str = dest_address.split(":", 1)
292
+ host, port_str = dest_address.rsplit(":", 1)
254
293
  port = int(port_str)
255
294
  (reader, writer) = await asyncio.open_connection(host=host, port=port, **kwargs)
256
295
  channel = SocketChannel(
@@ -28,13 +28,13 @@ import numpy as np
28
28
  from ...nvutils import get_cuda_context, get_index_and_uuid
29
29
  from ...serialization import deserialize
30
30
  from ...serialization.aio import BUFFER_SIZES_NAME, AioSerializer, get_header_length
31
- from ...utils import classproperty, implements, is_cuda_buffer, lazy_import
31
+ from ...utils import classproperty, implements, is_cuda_buffer, is_v6_ip, lazy_import
32
32
  from ..message import _MessageBase
33
33
  from .base import Channel, ChannelType, Client, Server
34
34
  from .core import register_client, register_server
35
35
  from .errors import ChannelClosed
36
36
 
37
- ucp = lazy_import("ucp")
37
+ ucp = lazy_import("ucxx")
38
38
  numba_cuda = lazy_import("numba.cuda")
39
39
  rmm = lazy_import("rmm")
40
40
 
@@ -86,7 +86,7 @@ class UCXInitializer:
86
86
  tls += ",cuda_copy"
87
87
 
88
88
  if ucx_config.get("infiniband"): # pragma: no cover
89
- tls = "rc," + tls
89
+ tls = "ib," + tls
90
90
  if ucx_config.get("nvlink"): # pragma: no cover
91
91
  tls += ",cuda_ipc"
92
92
 
@@ -177,7 +177,8 @@ class UCXInitializer:
177
177
  new_environ.update(envs)
178
178
  os.environ = new_environ # type: ignore
179
179
  try:
180
- ucp.init(options=options, env_takes_precedence=True)
180
+ # let UCX determine the appropriate transports
181
+ ucp.init()
181
182
  finally:
182
183
  os.environ = original_environ
183
184
 
@@ -313,7 +314,7 @@ class UCXChannel(Channel):
313
314
  await self.ucp_endpoint.send(buf)
314
315
  for buffer in buffers:
315
316
  await self.ucp_endpoint.send(buffer)
316
- except ucp.exceptions.UCXBaseException: # pragma: no cover
317
+ except ucp.exceptions.UCXError: # pragma: no cover
317
318
  self.abort()
318
319
  raise ChannelClosed("While writing, the connection was closed")
319
320
 
@@ -401,11 +402,21 @@ class UCXServer(Server):
401
402
  prefix = f"{UCXServer.scheme}://"
402
403
  if address.startswith(prefix):
403
404
  address = address[len(prefix) :]
404
- host, port = address.split(":", 1)
405
+ host, port = address.rsplit(":", 1)
405
406
  port = int(port)
406
407
  else:
407
408
  host = config.pop("host")
408
409
  port = int(config.pop("port"))
410
+ _host = host
411
+ if config.pop("listen_elastic_ip", False):
412
+ # The Actor.address will be announce to client, and is not on our host,
413
+ # cannot actually listen on it,
414
+ # so we have to keep SocketServer.host untouched to make sure Actor.address not changed
415
+ if is_v6_ip(host):
416
+ _host = "::"
417
+ else:
418
+ _host = "0.0.0.0"
419
+
409
420
  handle_channel = config.pop("handle_channel")
410
421
 
411
422
  # init
@@ -414,7 +425,7 @@ class UCXServer(Server):
414
425
  async def serve_forever(client_ucp_endpoint: "ucp.Endpoint"): # type: ignore
415
426
  try:
416
427
  await server.on_connected(
417
- client_ucp_endpoint, local_address=server.address
428
+ client_ucp_endpoint, local_address="%s:%d" % (_host, port)
418
429
  )
419
430
  except ChannelClosed: # pragma: no cover
420
431
  logger.exception("Connection closed before handshake completed")
@@ -498,7 +509,7 @@ class UCXClient(Client):
498
509
  prefix = f"{UCXClient.scheme}://"
499
510
  if dest_address.startswith(prefix):
500
511
  dest_address = dest_address[len(prefix) :]
501
- host, port_str = dest_address.split(":", 1)
512
+ host, port_str = dest_address.rsplit(":", 1)
502
513
  port = int(port_str)
503
514
  kwargs = kwargs.copy()
504
515
  ucx_config = kwargs.pop("config", dict()).get("ucx", dict())
@@ -506,7 +517,7 @@ class UCXClient(Client):
506
517
 
507
518
  try:
508
519
  ucp_endpoint = await ucp.create_endpoint(host, port)
509
- except ucp.exceptions.UCXBaseException as e: # pragma: no cover
520
+ except ucp.exceptions.UCXError as e: # pragma: no cover
510
521
  raise ChannelClosed(
511
522
  f"Connection closed before handshake completed, "
512
523
  f"local address: {local_address}, dest address: {dest_address}"
@@ -132,7 +132,7 @@ class MainActorPool(MainActorPoolBase):
132
132
  """Get external address for every process"""
133
133
  assert n_process is not None
134
134
  if ":" in address:
135
- host, port_str = address.split(":", 1)
135
+ host, port_str = address.rsplit(":", 1)
136
136
  port = int(port_str)
137
137
  if ports:
138
138
  if len(ports) != n_process:
@@ -324,6 +324,7 @@ class MainActorPool(MainActorPoolBase):
324
324
  start_method: str | None = None,
325
325
  kwargs: dict | None = None,
326
326
  ):
327
+ # external_address has port 0, subprocess will bind random port.
327
328
  external_address = (
328
329
  external_address
329
330
  or MainActorPool.get_external_addresses(self.external_address, n_process=1)[
@@ -393,7 +394,7 @@ class MainActorPool(MainActorPoolBase):
393
394
  content=self._config,
394
395
  )
395
396
  await self.handle_control_command(control_message)
396
-
397
+ # The actual port will return in process_status.
397
398
  return process_status.external_addresses[0]
398
399
 
399
400
  async def remove_sub_pool(
@@ -416,22 +417,21 @@ class MainActorPool(MainActorPoolBase):
416
417
  async def kill_sub_pool(
417
418
  self, process: multiprocessing.Process, force: bool = False
418
419
  ):
419
- if (
420
- "COV_CORE_SOURCE" in os.environ and not force and not _is_windows
421
- ): # pragma: no cover
422
- # must shutdown gracefully, or coverage info lost
423
- try:
424
- os.kill(process.pid, signal.SIGINT) # type: ignore
425
- except OSError: # pragma: no cover
426
- pass
427
- process.terminate()
420
+ if not force: # pragma: no cover
421
+ # must shutdown gracefully, or subprocess created by model will not exit
422
+ if not _is_windows:
423
+ try:
424
+ os.kill(process.pid, signal.SIGINT) # type: ignore
425
+ except OSError: # pragma: no cover
426
+ pass
427
+ process.terminate() # SIGTERM
428
428
  wait_pool = futures.ThreadPoolExecutor(1)
429
429
  try:
430
430
  loop = asyncio.get_running_loop()
431
431
  await loop.run_in_executor(wait_pool, process.join, 3)
432
432
  finally:
433
433
  wait_pool.shutdown(False)
434
- process.kill()
434
+ process.kill() # SIGKILL
435
435
  await asyncio.to_thread(process.join, 5)
436
436
 
437
437
  async def is_sub_pool_alive(self, process: multiprocessing.Process):
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ import asyncio
16
17
  from enum import Enum
17
18
  from types import TracebackType
18
19
  from typing import Any, Type
@@ -21,7 +22,9 @@ from tblib import pickling_support
21
22
 
22
23
  from ..core cimport ActorRef, BufferRef
23
24
  from ..serialization.core cimport Serializer
25
+
24
26
  from ..utils import wrap_exception
27
+
25
28
  from .._utils cimport new_random_id
26
29
 
27
30
  # make sure traceback can be pickled
@@ -245,6 +248,11 @@ cdef class ErrorMessage(_MessageBase):
245
248
  if issubclass(self.error_type, _AsCauseBase):
246
249
  return self.error.with_traceback(self.traceback)
247
250
 
251
+ # for being compatible with Python 3.12 `asyncio.wait_for`
252
+ # https://github.com/python/cpython/pull/113850
253
+ if isinstance(self.error, asyncio.CancelledError):
254
+ return asyncio.CancelledError(f"[address={self.address}, pid={self.pid}]").with_traceback(self.traceback)
255
+
248
256
  return wrap_exception(
249
257
  self.error,
250
258
  (_AsCauseBase,),
@@ -41,7 +41,7 @@ from ..errors import (
41
41
  ServerClosed,
42
42
  )
43
43
  from ..metrics import init_metrics
44
- from ..utils import implements, register_asyncio_task_timeout_detector
44
+ from ..utils import implements, is_zero_ip, register_asyncio_task_timeout_detector
45
45
  from .allocate_strategy import AddressSpecified, allocated_type
46
46
  from .communication import (
47
47
  Channel,
@@ -164,7 +164,10 @@ class AbstractActorPool(ABC):
164
164
  ):
165
165
  # register local pool for local actor lookup.
166
166
  # The pool is weakrefed, so we don't need to unregister it.
167
- register_local_pool(external_address, self)
167
+ if not is_zero_ip(external_address):
168
+ # Only register_local_pool when we listen on non-zero ip (because all-zero ip is wildcard address),
169
+ # avoid mistaken with another remote service listen on non-zero ip with the same port.
170
+ register_local_pool(external_address, self)
168
171
  self.process_index = process_index
169
172
  self.label = label
170
173
  self.external_address = external_address
@@ -95,7 +95,7 @@ class RankActor(Actor):
95
95
  return self._backend
96
96
 
97
97
  def _get_ip(self) -> str:
98
- return self.address.split(":")[0]
98
+ return self.address.rsplit(":", 1)[0]
99
99
 
100
100
  def _process_group_name(self, ranks: List[int]) -> str:
101
101
  return hashlib.sha1(
@@ -55,7 +55,6 @@ from .pyfury import get_fury
55
55
 
56
56
  BUFFER_PICKLE_PROTOCOL = max(pickle.DEFAULT_PROTOCOL, 5)
57
57
  cdef bint HAS_PICKLE_BUFFER = pickle.HIGHEST_PROTOCOL >= 5
58
- cdef bint _PANDAS_HAS_MGR = hasattr(pd.Series([0]), "_mgr")
59
58
 
60
59
  cdef TypeDispatcher _serial_dispatcher = TypeDispatcher()
61
60
  cdef dict _deserializers = dict()
@@ -260,16 +259,7 @@ def unpickle_buffers(list buffers):
260
259
  else:
261
260
  result = cloudpickle.loads(buffers[0], buffers=buffers[1:])
262
261
 
263
- # as pandas prior to 1.1.0 use _data instead of _mgr to hold BlockManager,
264
- # deserializing from high versions may produce mal-functioned pandas objects,
265
- # thus the patch is needed
266
- if _PANDAS_HAS_MGR:
267
- return result
268
- else: # pragma: no cover
269
- if hasattr(result, "_mgr") and isinstance(result, (pd.DataFrame, pd.Series)):
270
- result._data = getattr(result, "_mgr")
271
- delattr(result, "_mgr")
272
- return result
262
+ return result
273
263
 
274
264
 
275
265
  cdef class PickleSerializer(Serializer):
@@ -30,6 +30,7 @@ import sys
30
30
  import time
31
31
  import uuid
32
32
  from abc import ABC
33
+ from functools import lru_cache
33
34
  from types import TracebackType
34
35
  from typing import Callable, Type, Union
35
36
 
@@ -464,13 +465,18 @@ def is_linux():
464
465
  return sys.platform.startswith("linux")
465
466
 
466
467
 
468
+ @lru_cache
469
+ def is_py_312():
470
+ return sys.version_info[:2] == (3, 12)
471
+
472
+
467
473
  def is_v4_zero_ip(ip_port_addr: str) -> bool:
468
- return ip_port_addr.startswith("0.0.0.0:")
474
+ return ip_port_addr.split("://")[-1].startswith("0.0.0.0:")
469
475
 
470
476
 
471
477
  def is_v6_zero_ip(ip_port_addr: str) -> bool:
472
478
  # tcp6 addr ":::123", ":: means all zero"
473
- arr = ip_port_addr.split(":")
479
+ arr = ip_port_addr.split("://")[-1].split(":")
474
480
  if len(arr) <= 2: # Not tcp6 or udp6
475
481
  return False
476
482
  for part in arr[0:-1]:
@@ -480,6 +486,15 @@ def is_v6_zero_ip(ip_port_addr: str) -> bool:
480
486
  return True
481
487
 
482
488
 
489
+ def is_zero_ip(ip_port_addr: str) -> bool:
490
+ return is_v4_zero_ip(ip_port_addr) or is_v6_zero_ip(ip_port_addr)
491
+
492
+
493
+ def is_v6_ip(ip_port_addr: str) -> bool:
494
+ arr = ip_port_addr.split("://", 1)[-1].split(":")
495
+ return len(arr) > 1
496
+
497
+
483
498
  def fix_all_zero_ip(remote_addr: str, connect_addr: str) -> str:
484
499
  """
485
500
  Use connect_addr to fix ActorRef.address return by remote server.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xoscar
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -11,10 +11,10 @@ License: Apache License 2.0
11
11
  Classifier: Operating System :: OS Independent
12
12
  Classifier: Programming Language :: Python
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
19
  Classifier: Topic :: Software Development :: Libraries
20
20
  Description-Content-Type: text/markdown
@@ -29,7 +29,6 @@ xoscar.egg-info/not-zip-safe
29
29
  xoscar.egg-info/requires.txt
30
30
  xoscar.egg-info/top_level.txt
31
31
  xoscar/aio/__init__.py
32
- xoscar/aio/_threads.py
33
32
  xoscar/aio/base.py
34
33
  xoscar/aio/file.py
35
34
  xoscar/aio/lru.py
@@ -1,13 +1,10 @@
1
- numpy<2.0.0,>=1.14.0
1
+ numpy>=1.14.0
2
2
  pandas>=1.0.0
3
3
  cloudpickle>=1.5.0
4
4
  psutil>=5.9.0
5
5
  tblib>=1.7.0
6
6
  packaging
7
7
 
8
- [:python_version < "3.8"]
9
- pickle5
10
-
11
8
  [:sys_platform != "win32"]
12
9
  uvloop>=0.14.0
13
10
 
@@ -25,18 +22,15 @@ pytest-timeout>=1.2.0
25
22
  pytest-forked>=1.0
26
23
  pytest-asyncio>=0.14.0
27
24
  ipython>=6.5.0
28
- sphinx<5.0.0,>=3.0.0
25
+ sphinx
29
26
  pydata-sphinx-theme>=0.3.0
30
27
  sphinx-intl>=0.9.9
31
28
  flake8>=3.8.0
32
29
  black
33
30
 
34
- [dev:python_version < "3.8"]
35
- mock>=4.0.0
36
-
37
31
  [doc]
38
32
  ipython>=6.5.0
39
- sphinx<5.0.0,>=3.0.0
33
+ sphinx
40
34
  pydata-sphinx-theme>=0.3.0
41
35
  sphinx-intl>=0.9.9
42
36
 
@@ -1,25 +0,0 @@
1
- # Copyright 2022-2023 XProbe Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import asyncio
16
- import sys
17
-
18
- from .file import AioFileObject
19
- from .lru import alru_cache
20
- from .parallelism import AioEvent
21
-
22
- if sys.version_info[:2] < (3, 9):
23
- from ._threads import to_thread
24
-
25
- asyncio.to_thread = to_thread
@@ -1,35 +0,0 @@
1
- # Copyright 2022-2023 XProbe Inc.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- import contextvars
16
- import functools
17
- from asyncio import events
18
-
19
- __all__ = ("to_thread",)
20
-
21
-
22
- async def to_thread(func, *args, **kwargs):
23
- """Asynchronously run function *func* in a separate thread.
24
-
25
- Any *args and **kwargs supplied for this function are directly passed
26
- to *func*. Also, the current :class:`contextvars.Context` is propagated,
27
- allowing context variables from the main thread to be accessed in the
28
- separate thread.
29
-
30
- Return a coroutine that can be awaited to get the eventual result of *func*.
31
- """
32
- loop = events.get_running_loop()
33
- ctx = contextvars.copy_context()
34
- func_call = functools.partial(ctx.run, func, *args, **kwargs)
35
- return await loop.run_in_executor(None, func_call)
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