xoscar 0.3.2__cp310-cp310-macosx_11_0_arm64.whl → 0.4.1__cp310-cp310-macosx_11_0_arm64.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.

Binary file
xoscar/aio/__init__.py CHANGED
@@ -11,15 +11,6 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
-
15
- import asyncio
16
- import sys
17
-
18
14
  from .file import AioFileObject
19
15
  from .lru import alru_cache
20
16
  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
@@ -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
@@ -28,9 +29,9 @@ from typing import Any, Callable, Coroutine, Dict, Type
28
29
  from urllib.parse import urlparse
29
30
 
30
31
  from ..._utils import to_binary
31
- from ...constants import XOSCAR_UNIX_SOCKET_DIR
32
+ from ...constants import XOSCAR_CONNECT_TIMEOUT, 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,9 +289,15 @@ 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
- (reader, writer) = await asyncio.open_connection(host=host, port=port, **kwargs)
294
+ config = kwargs.get("config", {})
295
+ connect_timeout = config.get("connect_timeout", XOSCAR_CONNECT_TIMEOUT)
296
+ fut = asyncio.open_connection(host=host, port=port)
297
+ try:
298
+ reader, writer = await asyncio.wait_for(fut, timeout=connect_timeout)
299
+ except asyncio.TimeoutError:
300
+ raise ConnectionError("connect timeout")
256
301
  channel = SocketChannel(
257
302
  reader, writer, local_address=local_address, dest_address=dest_address
258
303
  )
@@ -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}"
xoscar/backends/core.py CHANGED
@@ -70,50 +70,61 @@ class ActorCaller:
70
70
  return client
71
71
 
72
72
  async def _listen(self, client: Client):
73
- while not client.closed:
74
- try:
73
+ try:
74
+ while not client.closed:
75
75
  try:
76
- message: _MessageBase = await client.recv()
77
- except (EOFError, ConnectionError, BrokenPipeError):
78
- # remote server closed, close client and raise ServerClosed
79
76
  try:
80
- await client.close()
81
- except (ConnectionError, BrokenPipeError):
82
- # close failed, ignore it
77
+ message: _MessageBase = await client.recv()
78
+ except (EOFError, ConnectionError, BrokenPipeError) as e:
79
+ # AssertionError is from get_header
80
+ # remote server closed, close client and raise ServerClosed
81
+ logger.debug(f"{client.dest_address} close due to {e}")
82
+ try:
83
+ await client.close()
84
+ except (ConnectionError, BrokenPipeError):
85
+ # close failed, ignore it
86
+ pass
87
+ raise ServerClosed(
88
+ f"Remote server {client.dest_address} closed: {e}"
89
+ ) from None
90
+ future = self._client_to_message_futures[client].pop(
91
+ message.message_id
92
+ )
93
+ if not future.done():
94
+ future.set_result(message)
95
+ except DeserializeMessageFailed as e:
96
+ message_id = e.message_id
97
+ future = self._client_to_message_futures[client].pop(message_id)
98
+ future.set_exception(e.__cause__) # type: ignore
99
+ except Exception as e: # noqa: E722 # pylint: disable=bare-except
100
+ message_futures = self._client_to_message_futures[client]
101
+ self._client_to_message_futures[client] = dict()
102
+ for future in message_futures.values():
103
+ future.set_exception(copy.copy(e))
104
+ finally:
105
+ # message may have Ray ObjectRef, delete it early in case next loop doesn't run
106
+ # as soon as expected.
107
+ try:
108
+ del message
109
+ except NameError:
83
110
  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)
111
+ try:
112
+ del future
113
+ except NameError:
114
+ pass
115
+ await asyncio.sleep(0)
111
116
 
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))
117
+ message_futures = self._client_to_message_futures[client]
118
+ self._client_to_message_futures[client] = dict()
119
+ error = ServerClosed(f"Remote server {client.dest_address} closed")
120
+ for future in message_futures.values():
121
+ future.set_exception(copy.copy(error))
122
+ finally:
123
+ try:
124
+ await client.close()
125
+ except: # noqa: E722 # nosec # pylint: disable=bare-except
126
+ # ignore all error if fail to close at last
127
+ pass
117
128
 
118
129
  async def call_with_client(
119
130
  self, client: Client, message: _MessageBase, wait: bool = True
@@ -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,),
xoscar/backends/pool.py CHANGED
@@ -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
@@ -548,23 +551,31 @@ class AbstractActorPool(ABC):
548
551
  return False
549
552
 
550
553
  async def on_new_channel(self, channel: Channel):
551
- while not self._stopped.is_set():
552
- try:
553
- message = await channel.recv()
554
- except EOFError:
555
- # no data to read, check channel
554
+ try:
555
+ while not self._stopped.is_set():
556
556
  try:
557
- await channel.close()
558
- except (ConnectionError, EOFError):
559
- # close failed, ignore
560
- pass
561
- return
562
- if await self._handle_ucx_meta_message(message, channel):
563
- continue
564
- asyncio.create_task(self.process_message(message, channel))
565
- # delete to release the reference of message
566
- del message
567
- 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
568
579
 
569
580
  async def __aenter__(self):
570
581
  await self.start()
xoscar/collective/core.py CHANGED
@@ -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(
xoscar/constants.py CHANGED
@@ -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
Binary file
Binary file
@@ -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
@@ -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):
xoscar/utils.py CHANGED
@@ -19,17 +19,18 @@ 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
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
 
@@ -266,7 +267,7 @@ def lazy_import(
266
267
  self._on_loads.append(func)
267
268
  return func
268
269
 
269
- if pkgutil.find_loader(prefix_name) is not None:
270
+ if importlib_utils.find_spec(prefix_name) is not None:
270
271
  return LazyModule()
271
272
  elif placeholder:
272
273
  return ModulePlaceholder(prefix_name)
@@ -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.1
4
4
  Summary: Python actor framework for heterogeneous computing.
5
5
  Home-page: http://github.com/xorbitsai/xoscar
6
6
  Author: Qin Xuye
@@ -11,48 +11,46 @@ 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
21
- Requires-Dist: numpy <2.0.0,>=1.14.0
22
- Requires-Dist: pandas >=1.0.0
23
- Requires-Dist: cloudpickle >=1.5.0
24
- Requires-Dist: psutil >=5.9.0
25
- Requires-Dist: tblib >=1.7.0
21
+ Requires-Dist: numpy>=1.14.0
22
+ Requires-Dist: pandas>=1.0.0
23
+ Requires-Dist: cloudpickle>=1.5.0
24
+ Requires-Dist: psutil>=5.9.0
25
+ Requires-Dist: tblib>=1.7.0
26
26
  Requires-Dist: packaging
27
- Requires-Dist: pickle5 ; python_version < "3.8"
28
- Requires-Dist: uvloop >=0.14.0 ; sys_platform != "win32"
29
- Requires-Dist: scipy >=1.0.0 ; sys_platform != "win32" or python_version >= "3.10"
30
- Requires-Dist: scipy <=1.9.1,>=1.0.0 ; sys_platform == "win32" and python_version < "3.10"
27
+ Requires-Dist: uvloop>=0.14.0; sys_platform != "win32"
28
+ Requires-Dist: scipy>=1.0.0; sys_platform != "win32" or python_version >= "3.10"
29
+ Requires-Dist: scipy<=1.9.1,>=1.0.0; sys_platform == "win32" and python_version < "3.10"
31
30
  Provides-Extra: dev
32
- Requires-Dist: cython >=0.29 ; extra == 'dev'
33
- Requires-Dist: pytest >=3.5.0 ; extra == 'dev'
34
- Requires-Dist: pytest-cov >=2.5.0 ; extra == 'dev'
35
- Requires-Dist: pytest-timeout >=1.2.0 ; extra == 'dev'
36
- Requires-Dist: pytest-forked >=1.0 ; extra == 'dev'
37
- Requires-Dist: pytest-asyncio >=0.14.0 ; extra == 'dev'
38
- Requires-Dist: ipython >=6.5.0 ; extra == 'dev'
39
- Requires-Dist: sphinx <5.0.0,>=3.0.0 ; extra == 'dev'
40
- Requires-Dist: pydata-sphinx-theme >=0.3.0 ; extra == 'dev'
41
- Requires-Dist: sphinx-intl >=0.9.9 ; extra == 'dev'
42
- Requires-Dist: flake8 >=3.8.0 ; extra == 'dev'
43
- Requires-Dist: black ; extra == 'dev'
44
- Requires-Dist: mock >=4.0.0 ; (python_version < "3.8") and extra == 'dev'
31
+ Requires-Dist: cython>=0.29; extra == "dev"
32
+ Requires-Dist: pytest>=3.5.0; extra == "dev"
33
+ Requires-Dist: pytest-cov>=2.5.0; extra == "dev"
34
+ Requires-Dist: pytest-timeout>=1.2.0; extra == "dev"
35
+ Requires-Dist: pytest-forked>=1.0; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.14.0; extra == "dev"
37
+ Requires-Dist: ipython>=6.5.0; extra == "dev"
38
+ Requires-Dist: sphinx; extra == "dev"
39
+ Requires-Dist: pydata-sphinx-theme>=0.3.0; extra == "dev"
40
+ Requires-Dist: sphinx-intl>=0.9.9; extra == "dev"
41
+ Requires-Dist: flake8>=3.8.0; extra == "dev"
42
+ Requires-Dist: black; extra == "dev"
45
43
  Provides-Extra: doc
46
- Requires-Dist: ipython >=6.5.0 ; extra == 'doc'
47
- Requires-Dist: sphinx <5.0.0,>=3.0.0 ; extra == 'doc'
48
- Requires-Dist: pydata-sphinx-theme >=0.3.0 ; extra == 'doc'
49
- Requires-Dist: sphinx-intl >=0.9.9 ; extra == 'doc'
44
+ Requires-Dist: ipython>=6.5.0; extra == "doc"
45
+ Requires-Dist: sphinx; extra == "doc"
46
+ Requires-Dist: pydata-sphinx-theme>=0.3.0; extra == "doc"
47
+ Requires-Dist: sphinx-intl>=0.9.9; extra == "doc"
50
48
  Provides-Extra: extra
51
- Requires-Dist: pyarrow >=5.0.0 ; extra == 'extra'
49
+ Requires-Dist: pyarrow>=5.0.0; extra == "extra"
52
50
  Provides-Extra: kubernetes
53
- Requires-Dist: kubernetes >=10.0.0 ; extra == 'kubernetes'
51
+ Requires-Dist: kubernetes>=10.0.0; extra == "kubernetes"
54
52
  Provides-Extra: ray
55
- Requires-Dist: xoscar-ray >=0.0.1 ; extra == 'ray'
53
+ Requires-Dist: xoscar-ray>=0.0.1; extra == "ray"
56
54
 
57
55
  <div align="center">
58
56
  <img width="77%" alt="" src="https://raw.githubusercontent.com/xprobe-inc/xoscar/main/doc/source/_static/Xoscar.svg"><br>
@@ -1,7 +1,7 @@
1
- xoscar-0.3.2.dist-info/RECORD,,
2
- xoscar-0.3.2.dist-info/WHEEL,sha256=E2L8cNry8_qENFWMb8KxRWLe-RhZW94hLc32Xo3HiTg,110
3
- xoscar-0.3.2.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
4
- xoscar-0.3.2.dist-info/METADATA,sha256=Sw1MUXmjiJDvY37wnlzYTfiAyRYM3pqIMfefEvjPr94,9246
1
+ xoscar-0.4.1.dist-info/RECORD,,
2
+ xoscar-0.4.1.dist-info/WHEEL,sha256=2EkzLoFb7so8tT6oZZZ9qCoSorU44itt69ft8KTz84A,110
3
+ xoscar-0.4.1.dist-info/top_level.txt,sha256=vYlqqY4Nys8Thm1hePIuUv8eQePdULVWMmt7lXtX_ZA,21
4
+ xoscar-0.4.1.dist-info/METADATA,sha256=s1flP4I8DrL3z4TI3mPEAZpBJTlNTM9UwVVmRNXB1-Y,9042
5
5
  xoscar/_utils.pyx,sha256=UR1FtYXAYKIdEWR9HulEpMbSOrkQWi6xGz63d4IQmG0,7059
6
6
  xoscar/backend.py,sha256=is436OPkZfSpQXaoqTRVta5eoye_pp45RFgCstAk2hU,1850
7
7
  xoscar/core.pxd,sha256=4lBq8J0kjcXcsGuvN7Kv4xcL5liHwTTFWlqyK7XAEnw,1280
@@ -9,19 +9,19 @@ xoscar/_version.py,sha256=ClSPrUjgGRGHIkVMQV9XQnkQ-n0akJMnq_rh819nqFE,23719
9
9
  xoscar/context.pxd,sha256=qKa0OyDPZtVymftSh447m-RzFZgmz8rGqQBa7qlauvc,725
10
10
  xoscar/batch.py,sha256=DpArS0L3WYJ_HVPG-6hSYEwoAFY1mY2-mlC4Jp5M_Dw,7872
11
11
  xoscar/nvutils.py,sha256=qmW4mKLU0WB2yCs198ccQOgLL02zB7Fsa-AotO3NOmg,20412
12
- xoscar/constants.py,sha256=Yn59lRIOvE1VFwyuZB5G2-gxYIyhIZ1rVovbdFAR2NM,759
13
- xoscar/_utils.cpython-310-darwin.so,sha256=JDJSsLPX1G2EtvD92my6Em1dPx_hBZAeW_QEPZB0CLU,167616
12
+ xoscar/constants.py,sha256=QHHSREw6uWBBjQDCFqlNfTvBZgniJPGy42KSIsR8Fqw,787
13
+ xoscar/_utils.cpython-310-darwin.so,sha256=L7yQM7lAT1oEOPr_74DOnX1ixaZFG-BF5xDXPCxhYl0,167616
14
14
  xoscar/__init__.py,sha256=0zX8kKaio3ZIrlzB79WybcravMJw1OxPWjDspTgJFyQ,1608
15
15
  xoscar/api.py,sha256=3hztPoOxg8A_mlhWyWgVP7FMXG0PATA1TP4Rbaj7A-g,13327
16
- xoscar/utils.py,sha256=TYp6wC8xx2AjKcoKt6Xk0bwhFeccBJKCK50YQE1XOV4,16076
16
+ xoscar/utils.py,sha256=jUw6OICZUPBbmS1b3GE4vLctJf6fCKXrYtLtBuK-Oqc,16483
17
17
  xoscar/debug.py,sha256=9Z8SgE2WaKYQcyDo-5-DxEJQ533v7kWjrvCd28pSx3E,5069
18
18
  xoscar/libcpp.pxd,sha256=DJqBxLFOKL4iRr9Kale5UH3rbvPRD1x5bTSOPHFpz9I,1147
19
19
  xoscar/context.pyx,sha256=8CdgPnWcE9eOp3N600WgDQ03MCi8P73eUOGcfV7Zksg,10942
20
20
  xoscar/errors.py,sha256=wBlQOKsXf0Fc4skN39tDie0YZT-VIAuLNRgoDl2pZcA,1241
21
21
  xoscar/core.pyx,sha256=Aqc2i8Fetsd5wRAPF4kL0ddnBZn3E2HRNCvup79BbQc,21730
22
22
  xoscar/driver.py,sha256=498fowtJr6b3FE8FIOA_Tc1Vwx88nfZw7p0FxrML0h4,1372
23
- xoscar/context.cpython-310-darwin.so,sha256=8akzNtrNTHrWNxHGA1I14Ig0pENi-n0O0_A88MS2oes,210160
24
- xoscar/core.cpython-310-darwin.so,sha256=HpKjl6lbmBOXHAsPDjTibwzfzJa5d6hkL3PbKEqu3vY,424856
23
+ xoscar/context.cpython-310-darwin.so,sha256=iQnZpcGfyiRB-JeWCuzlukuHeHtHshXrRI7qCMbKMp0,210160
24
+ xoscar/core.cpython-310-darwin.so,sha256=77Djano2hA2LMJAV36OS9wGmVj2pjWM5ZsMEKHIJsbk,424856
25
25
  xoscar/profiling.py,sha256=BC5OF0HzSaXv8V7w-y-B8r5gV5DgxHFoTEIF6jCMioQ,8015
26
26
  xoscar/_utils.pxd,sha256=5KYAL3jfPdejsHnrGGT2s--ZUX5SXznQWpHVSno429k,1157
27
27
  xoscar/metrics/__init__.py,sha256=9Badi7rxYikGm2dQiNCrj9GgMRBxwuR3JaEKcFZmfak,705
@@ -32,9 +32,9 @@ xoscar/metrics/backends/prometheus/__init__.py,sha256=h_JgzSqV5lP6vQ6XX_17kE4IY4
32
32
  xoscar/metrics/backends/prometheus/prometheus_metric.py,sha256=MxoMvVrg0pOkKpkjJ0PcAuEaaEJR2FZljmPrLjQ1-oc,2050
33
33
  xoscar/metrics/backends/console/console_metric.py,sha256=y5CCtH33j3AqI5_Uhwi4mgOcAhyhb4cWv_YvR6fxcbQ,2082
34
34
  xoscar/metrics/backends/console/__init__.py,sha256=h_JgzSqV5lP6vQ6XX_17kE4IY4BRnvKta_7VLQAL1ms,581
35
- xoscar/collective/xoscar_pygloo.cpython-310-darwin.so,sha256=h9LvFxBOi_FjQUxk3SpXwA5GCPaNqIZ-ZwOMHq7FVzA,1159104
35
+ xoscar/collective/xoscar_pygloo.cpython-310-darwin.so,sha256=k_vzUB-mwxF6uDWLM1owJuA42afYGeUFZZgQG8eu2X0,1020576
36
36
  xoscar/collective/__init__.py,sha256=XsClIkO_3Jd8GDifYuAbZCmJLAo9ZqGvnjUn9iuogmU,774
37
- xoscar/collective/core.py,sha256=WfMJZloiRiqsLlIMhU4Pa47eo0jE-hoXdbTBwZPM6TM,23498
37
+ xoscar/collective/core.py,sha256=NVR-7Iaq3aDPCN6fgXcq9Ew6uFEszRwxYqmUG9FLcws,23502
38
38
  xoscar/collective/common.py,sha256=INAnISbfnRicbbbDHTqbSr9ITb89ZphH5BUkSpEdXXU,3561
39
39
  xoscar/collective/utils.py,sha256=3S4qF4JEnAUD3RiWVBUj-ZptL83CBSwGYyVZyIasAsE,1178
40
40
  xoscar/collective/process_group.py,sha256=zy7LcIFnEcmrcxuECI89v0bQlUbSqQMkVyBw468WBnk,22599
@@ -45,35 +45,34 @@ xoscar/serialization/__init__.py,sha256=5Y_C3cYbQJIZ09LRjeCf-jrkLma7mfN8I5bznHrd
45
45
  xoscar/serialization/numpy.py,sha256=5Kem87CvpJmzUMp3QHk4WeHU30FoQWTJJP2SwIcaQG0,2919
46
46
  xoscar/serialization/cuda.py,sha256=iFUEnN4SiquBIhyieyOrfw3TnKnW-tU_vYgqOxO_DrA,3758
47
47
  xoscar/serialization/scipy.py,sha256=yOEi0NB8cqQ6e2UnCZ1w006RsB7T725tIL-DM_hNcsU,2482
48
- xoscar/serialization/aio.py,sha256=S9e3rHMBwqqKmJtDz7KzYAqWc8w9bttA0Dj83IBfEU0,4577
49
- xoscar/serialization/core.pyx,sha256=E3xIKmdI2gn99JduR3yuU_YTm-lOyG0Tkc7fZVBWCho,30131
50
- xoscar/serialization/core.cpython-310-darwin.so,sha256=E09XXCDdZ03eKZaHTxrq0tjH2HpSswDVNraah7qgYQQ,379208
48
+ xoscar/serialization/aio.py,sha256=5DySPgDxU43ec7_5Ct44-Oqt7YNSJBfuf8VdQgQlChA,4731
49
+ xoscar/serialization/core.pyx,sha256=bjR-zXGm9qersk7kYPzpjpMIxDl_Auur4BCubRfKmfA,29626
50
+ xoscar/serialization/core.cpython-310-darwin.so,sha256=kKi7k6G3NJoGYeo3yyXtFy15WO_KyDQtDLxdc8Mx3s4,378952
51
51
  xoscar/backends/config.py,sha256=EG26f0GwX_f4dAhwTW77RBjiK9h8R_3JrD-rBF1bAq8,4984
52
52
  xoscar/backends/allocate_strategy.py,sha256=tC1Nbq2tJohahUwd-zoRYHEDX65wyuX8tmeY45uWj_w,4845
53
- xoscar/backends/message.cpython-310-darwin.so,sha256=K0muMTcS_VgQKJ6jf_Mzb-Qfw0tow5nyKMl-yAV0OY0,344512
53
+ xoscar/backends/message.cpython-310-darwin.so,sha256=oxLCon0ha0Mh2wyg79R1IXCDUVloG5HFXQnCLmvTJbY,345264
54
54
  xoscar/backends/__init__.py,sha256=VHEBQcUWM5bj027W8EUf9PiJUAP7JoMrRw3Tsvy5ySw,643
55
- xoscar/backends/core.py,sha256=aHb3mMZ9vJe6pxg0P8kSOKvjXF1IaqgOgyhKVhHpNLM,7432
55
+ xoscar/backends/core.py,sha256=YcXVrMrTUjnrnH-RhrtUyxnCbwon8miq2UigCQv-y_Q,8039
56
56
  xoscar/backends/context.py,sha256=Vr_PibRxYCDQ_gYK7r-BOlw9TXw8VQbFsVTH7K7mHPk,15470
57
57
  xoscar/backends/router.py,sha256=mhSvM5KVfV882jricVcpyxAqHEvhS4zL6ivczC6fOTE,7746
58
- xoscar/backends/message.pyx,sha256=_rXcsWPcWu77Z_38rvjDBdQojpY5xJoaHQrt57_LVyo,17612
59
- xoscar/backends/pool.py,sha256=bvS1r31O01E8jTdoWOhSqcFymksNqO2nX3Fkqary8Ro,59149
58
+ xoscar/backends/message.pyx,sha256=uyzilPc_7SqNwGUL4U-Zbfqku8bfZyRW_Lt_S3I_LEU,17930
59
+ xoscar/backends/pool.py,sha256=Z7Wdab9dBF3SdQpmzgZhY0d09oTvg5gpFgzYH7vuc4w,59841
60
60
  xoscar/backends/indigen/backend.py,sha256=znl_fZzWGEtLH8hZ9j9Kkf0fva25jEem2_KO7I1RVvc,1612
61
61
  xoscar/backends/indigen/__init__.py,sha256=tKHP5ClzedBRBpZsLRVErR3EUNbbDm4CY4u0rCFJr44,685
62
62
  xoscar/backends/indigen/driver.py,sha256=VGzkacYKykegW5qhCuhx01gdgBZEKJjNIyfNCnA6Nm8,952
63
- xoscar/backends/indigen/pool.py,sha256=3C1N2sbq02maUjl7jDhRkyYAoYmZD8hZBct6wxblq_Y,16709
63
+ xoscar/backends/indigen/pool.py,sha256=mWYkOP4VVoUsXFgfpwruPuWblF6Waan5vxit8B-9_oQ,16852
64
64
  xoscar/backends/test/backend.py,sha256=nv9WFhH5Bbq4Q1HB9yfpciZBaeHT4IQAtzugBWESrUY,1263
65
65
  xoscar/backends/test/__init__.py,sha256=j2ZfD6prD9WjUxRUDC7Eq5Z7N7TkL6fFr59oNyc_vY4,682
66
66
  xoscar/backends/test/pool.py,sha256=TW4X6J-92Pti66103poQBNDBznX6CBD3RLOc_zixjTo,7257
67
- xoscar/backends/communication/ucx.py,sha256=eidp4l-YAzFMCYaeUcvpK4ecapg-92fXFKO-t_bBkTU,19267
67
+ xoscar/backends/communication/ucx.py,sha256=7GAKIzlbxy-NpaDE9VLPde90tKxeU8lVZH8c_3ByuZ0,19703
68
68
  xoscar/backends/communication/__init__.py,sha256=tB05BlK63iWQnfJgRzKt4mFKRtmWUki5hUGSZQwAotc,1050
69
69
  xoscar/backends/communication/core.py,sha256=sJeE3foRIqVPXldzYpFKHDSsabfAIFBU4JuXY4OyklY,2130
70
70
  xoscar/backends/communication/utils.py,sha256=AmovE-hmWLXNCPwHafYuaRjOk8m42BUyT3XBqfXQRVI,3664
71
71
  xoscar/backends/communication/errors.py,sha256=V3CdBe2xX9Rwv32f2dH2Msc84yaUhlyerZ42-739o1Q,723
72
- xoscar/backends/communication/socket.py,sha256=VBPiesyjX8c3ECWn8kv8qGwK3xCBqh_CHPrNDapYH6w,11819
72
+ xoscar/backends/communication/socket.py,sha256=ZSDqS_Z9-Oxs2Q2fqWUuUJ9hNf4w8lNJ8gUv5r6Ji_Y,13691
73
73
  xoscar/backends/communication/dummy.py,sha256=gaKPNiN4x2aGZV3IGaaa8eaweBVjRh8B19jU1B5t2yw,7798
74
74
  xoscar/backends/communication/base.py,sha256=0P4Tr35GSWpRp394e9jVWUUoKKa-gIk177eYPw1BnSU,7421
75
- xoscar/aio/__init__.py,sha256=4Rv9V_wDIKlg7VcJeo1GVlvobwskYb1jYXef-0GQOaY,809
76
- xoscar/aio/_threads.py,sha256=WE9_NZY3K9n5bAzXRbj1Bc4dxS-1m1erMfZsUu-ULU4,1313
75
+ xoscar/aio/__init__.py,sha256=kViDKR_kJe59VQViHITKEfBcIgN4ZJblUyd8zl0E3ZI,675
77
76
  xoscar/aio/file.py,sha256=PBtkLp-Q7XtYl-zk00s18TtgIrkNr60J3Itf66ctO1o,1486
78
77
  xoscar/aio/lru.py,sha256=rpXCqSLtPV5xnWtd6uDwQQFGgIPEgvmWEQDkPNUx9cM,6311
79
78
  xoscar/aio/parallelism.py,sha256=VSsjk8wP-Bw7tLeUsTyLVNgp91thjxEfE3pCrw_vF5Q,1293
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-cp310-macosx_11_0_arm64
5
5
 
xoscar/aio/_threads.py DELETED
@@ -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)