xoscar 0.9.0__cp312-cp312-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xoscar/__init__.py +61 -0
- xoscar/_utils.cpython-312-darwin.so +0 -0
- xoscar/_utils.pxd +36 -0
- xoscar/_utils.pyx +246 -0
- xoscar/_version.py +693 -0
- xoscar/aio/__init__.py +16 -0
- xoscar/aio/base.py +86 -0
- xoscar/aio/file.py +59 -0
- xoscar/aio/lru.py +228 -0
- xoscar/aio/parallelism.py +39 -0
- xoscar/api.py +527 -0
- xoscar/backend.py +67 -0
- xoscar/backends/__init__.py +14 -0
- xoscar/backends/allocate_strategy.py +160 -0
- xoscar/backends/communication/__init__.py +30 -0
- xoscar/backends/communication/base.py +315 -0
- xoscar/backends/communication/core.py +69 -0
- xoscar/backends/communication/dummy.py +253 -0
- xoscar/backends/communication/errors.py +20 -0
- xoscar/backends/communication/socket.py +444 -0
- xoscar/backends/communication/ucx.py +538 -0
- xoscar/backends/communication/utils.py +97 -0
- xoscar/backends/config.py +157 -0
- xoscar/backends/context.py +437 -0
- xoscar/backends/core.py +352 -0
- xoscar/backends/indigen/__init__.py +16 -0
- xoscar/backends/indigen/__main__.py +19 -0
- xoscar/backends/indigen/backend.py +51 -0
- xoscar/backends/indigen/driver.py +26 -0
- xoscar/backends/indigen/fate_sharing.py +221 -0
- xoscar/backends/indigen/pool.py +515 -0
- xoscar/backends/indigen/shared_memory.py +548 -0
- xoscar/backends/message.cpython-312-darwin.so +0 -0
- xoscar/backends/message.pyi +255 -0
- xoscar/backends/message.pyx +646 -0
- xoscar/backends/pool.py +1630 -0
- xoscar/backends/router.py +285 -0
- xoscar/backends/test/__init__.py +16 -0
- xoscar/backends/test/backend.py +38 -0
- xoscar/backends/test/pool.py +233 -0
- xoscar/batch.py +256 -0
- xoscar/collective/__init__.py +27 -0
- xoscar/collective/backend/__init__.py +13 -0
- xoscar/collective/backend/nccl_backend.py +160 -0
- xoscar/collective/common.py +102 -0
- xoscar/collective/core.py +737 -0
- xoscar/collective/process_group.py +687 -0
- xoscar/collective/utils.py +41 -0
- xoscar/collective/xoscar_pygloo.cpython-312-darwin.so +0 -0
- xoscar/collective/xoscar_pygloo.pyi +239 -0
- xoscar/constants.py +23 -0
- xoscar/context.cpython-312-darwin.so +0 -0
- xoscar/context.pxd +21 -0
- xoscar/context.pyx +368 -0
- xoscar/core.cpython-312-darwin.so +0 -0
- xoscar/core.pxd +51 -0
- xoscar/core.pyx +664 -0
- xoscar/debug.py +188 -0
- xoscar/driver.py +42 -0
- xoscar/errors.py +63 -0
- xoscar/libcpp.pxd +31 -0
- xoscar/metrics/__init__.py +21 -0
- xoscar/metrics/api.py +288 -0
- xoscar/metrics/backends/__init__.py +13 -0
- xoscar/metrics/backends/console/__init__.py +13 -0
- xoscar/metrics/backends/console/console_metric.py +82 -0
- xoscar/metrics/backends/metric.py +149 -0
- xoscar/metrics/backends/prometheus/__init__.py +13 -0
- xoscar/metrics/backends/prometheus/prometheus_metric.py +70 -0
- xoscar/nvutils.py +717 -0
- xoscar/profiling.py +260 -0
- xoscar/serialization/__init__.py +20 -0
- xoscar/serialization/aio.py +141 -0
- xoscar/serialization/core.cpython-312-darwin.so +0 -0
- xoscar/serialization/core.pxd +28 -0
- xoscar/serialization/core.pyi +57 -0
- xoscar/serialization/core.pyx +944 -0
- xoscar/serialization/cuda.py +111 -0
- xoscar/serialization/exception.py +48 -0
- xoscar/serialization/mlx.py +67 -0
- xoscar/serialization/numpy.py +82 -0
- xoscar/serialization/pyfury.py +37 -0
- xoscar/serialization/scipy.py +72 -0
- xoscar/serialization/torch.py +180 -0
- xoscar/utils.py +522 -0
- xoscar/virtualenv/__init__.py +34 -0
- xoscar/virtualenv/core.py +268 -0
- xoscar/virtualenv/platform.py +56 -0
- xoscar/virtualenv/utils.py +100 -0
- xoscar/virtualenv/uv.py +321 -0
- xoscar-0.9.0.dist-info/METADATA +230 -0
- xoscar-0.9.0.dist-info/RECORD +94 -0
- xoscar-0.9.0.dist-info/WHEEL +6 -0
- xoscar-0.9.0.dist-info/top_level.txt +2 -0
xoscar/utils.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
# Copyright 2022-2023 XProbe Inc.
|
|
2
|
+
# derived from copyright 1999-2021 Alibaba Group Holding Ltd.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
import dataclasses
|
|
20
|
+
import functools
|
|
21
|
+
import importlib
|
|
22
|
+
import importlib.util as importlib_utils
|
|
23
|
+
import inspect
|
|
24
|
+
import io
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import random
|
|
28
|
+
import socket
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
import uuid
|
|
32
|
+
from abc import ABC
|
|
33
|
+
from functools import lru_cache
|
|
34
|
+
from types import TracebackType
|
|
35
|
+
from typing import Callable, Type, Union
|
|
36
|
+
|
|
37
|
+
from ._utils import ( # noqa: F401 # pylint: disable=unused-import
|
|
38
|
+
NamedType,
|
|
39
|
+
Timer,
|
|
40
|
+
TypeDispatcher,
|
|
41
|
+
to_binary,
|
|
42
|
+
to_str,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Please refer to https://bugs.python.org/issue41451
|
|
46
|
+
try:
|
|
47
|
+
|
|
48
|
+
class _Dummy(ABC):
|
|
49
|
+
__slots__ = ("__weakref__",)
|
|
50
|
+
|
|
51
|
+
abc_type_require_weakref_slot = True
|
|
52
|
+
except TypeError:
|
|
53
|
+
abc_type_require_weakref_slot = False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_memory_size_indices = {"": 0, "k": 1, "m": 2, "g": 3, "t": 4}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_readable_size(value: str | int | float) -> tuple[float, bool]:
|
|
63
|
+
if isinstance(value, (int, float)):
|
|
64
|
+
return float(value), False
|
|
65
|
+
|
|
66
|
+
value = value.strip().lower()
|
|
67
|
+
num_pos = 0
|
|
68
|
+
while num_pos < len(value) and value[num_pos] in "0123456789.-":
|
|
69
|
+
num_pos += 1
|
|
70
|
+
|
|
71
|
+
value, suffix = value[:num_pos], value[num_pos:]
|
|
72
|
+
suffix = suffix.strip()
|
|
73
|
+
if suffix.endswith("%"):
|
|
74
|
+
return float(value) / 100, True
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
return float(value) * (1024 ** _memory_size_indices[suffix[:1]]), False
|
|
78
|
+
except (ValueError, KeyError):
|
|
79
|
+
raise ValueError(f"Unknown limitation value: {value}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def wrap_exception(
|
|
83
|
+
exc: BaseException,
|
|
84
|
+
bases: tuple[Type] | tuple | None = None,
|
|
85
|
+
wrap_name: str | None = None,
|
|
86
|
+
message: str | None = None,
|
|
87
|
+
traceback: TracebackType | None = None,
|
|
88
|
+
attr_dict: dict | None = None,
|
|
89
|
+
) -> BaseException:
|
|
90
|
+
"""Generate an exception wraps the cause exception."""
|
|
91
|
+
|
|
92
|
+
def __init__(self, *args, **kwargs):
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
def __getattr__(self, item):
|
|
96
|
+
return getattr(exc, item)
|
|
97
|
+
|
|
98
|
+
def __str__(self):
|
|
99
|
+
return message or super(type(self), self).__str__()
|
|
100
|
+
|
|
101
|
+
traceback = traceback or exc.__traceback__
|
|
102
|
+
bases = bases or ()
|
|
103
|
+
attr_dict = attr_dict or {}
|
|
104
|
+
attr_dict.update(
|
|
105
|
+
{
|
|
106
|
+
"__init__": __init__,
|
|
107
|
+
"__getattr__": __getattr__,
|
|
108
|
+
"__str__": __str__,
|
|
109
|
+
"__wrapname__": wrap_name,
|
|
110
|
+
"__wrapped__": exc,
|
|
111
|
+
"__module__": type(exc).__module__,
|
|
112
|
+
"__cause__": exc.__cause__,
|
|
113
|
+
"__context__": exc.__context__,
|
|
114
|
+
"__suppress_context__": exc.__suppress_context__,
|
|
115
|
+
"args": exc.args,
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
new_exc_type = type(type(exc).__name__, bases + (type(exc),), attr_dict)
|
|
119
|
+
return new_exc_type().with_traceback(traceback)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# from https://github.com/ericvsmith/dataclasses/blob/master/dataclass_tools.py
|
|
123
|
+
# released under Apache License 2.0
|
|
124
|
+
def dataslots(cls):
|
|
125
|
+
# Need to create a new class, since we can't set __slots__
|
|
126
|
+
# after a class has been created.
|
|
127
|
+
|
|
128
|
+
# Make sure __slots__ isn't already set.
|
|
129
|
+
if "__slots__" in cls.__dict__: # pragma: no cover
|
|
130
|
+
raise TypeError(f"{cls.__name__} already specifies __slots__")
|
|
131
|
+
|
|
132
|
+
# Create a new dict for our new class.
|
|
133
|
+
cls_dict = dict(cls.__dict__)
|
|
134
|
+
field_names = tuple(f.name for f in dataclasses.fields(cls))
|
|
135
|
+
cls_dict["__slots__"] = field_names
|
|
136
|
+
for field_name in field_names:
|
|
137
|
+
# Remove our attributes, if present. They'll still be
|
|
138
|
+
# available in _MARKER.
|
|
139
|
+
cls_dict.pop(field_name, None)
|
|
140
|
+
# Remove __dict__ itself.
|
|
141
|
+
cls_dict.pop("__dict__", None)
|
|
142
|
+
# And finally create the class.
|
|
143
|
+
qualname = getattr(cls, "__qualname__", None)
|
|
144
|
+
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
|
|
145
|
+
if qualname is not None:
|
|
146
|
+
cls.__qualname__ = qualname
|
|
147
|
+
return cls
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def implements(f: Callable):
|
|
151
|
+
def decorator(g):
|
|
152
|
+
g.__doc__ = f.__doc__
|
|
153
|
+
return g
|
|
154
|
+
|
|
155
|
+
return decorator
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class classproperty:
|
|
159
|
+
def __init__(self, f):
|
|
160
|
+
self.f = f
|
|
161
|
+
|
|
162
|
+
def __get__(self, obj, owner):
|
|
163
|
+
return self.f(owner)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
LOW_PORT_BOUND = 10000
|
|
167
|
+
HIGH_PORT_BOUND = 65535
|
|
168
|
+
_local_occupied_ports: set = set()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _get_ports_from_netstat() -> set[int]:
|
|
172
|
+
import subprocess
|
|
173
|
+
|
|
174
|
+
while True:
|
|
175
|
+
p = subprocess.Popen("netstat -a -n -p tcp".split(), stdout=subprocess.PIPE)
|
|
176
|
+
try:
|
|
177
|
+
outs, _ = p.communicate(timeout=5)
|
|
178
|
+
lines = outs.split(to_binary(os.linesep))
|
|
179
|
+
occupied = set()
|
|
180
|
+
for line in lines:
|
|
181
|
+
if b"." not in line:
|
|
182
|
+
continue
|
|
183
|
+
line_str: str = to_str(line)
|
|
184
|
+
for part in line_str.split():
|
|
185
|
+
# in windows, netstat uses ':' to separate host and port
|
|
186
|
+
part = part.replace(":", ".")
|
|
187
|
+
if "." in part:
|
|
188
|
+
_, port_str = part.rsplit(".", 1)
|
|
189
|
+
if port_str == "*":
|
|
190
|
+
continue
|
|
191
|
+
port = int(port_str)
|
|
192
|
+
if LOW_PORT_BOUND <= port <= HIGH_PORT_BOUND:
|
|
193
|
+
occupied.add(int(port_str))
|
|
194
|
+
break
|
|
195
|
+
return occupied
|
|
196
|
+
except subprocess.TimeoutExpired:
|
|
197
|
+
p.kill()
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_next_port(typ: int | None = None, occupy: bool = True) -> int:
|
|
202
|
+
import psutil
|
|
203
|
+
|
|
204
|
+
if sys.platform.lower().startswith("win"):
|
|
205
|
+
occupied = _get_ports_from_netstat()
|
|
206
|
+
else:
|
|
207
|
+
try:
|
|
208
|
+
conns = psutil.net_connections()
|
|
209
|
+
typ = typ or socket.SOCK_STREAM
|
|
210
|
+
occupied = set(
|
|
211
|
+
sc.laddr.port
|
|
212
|
+
for sc in conns
|
|
213
|
+
if sc.type == typ and LOW_PORT_BOUND <= sc.laddr.port <= HIGH_PORT_BOUND
|
|
214
|
+
)
|
|
215
|
+
except psutil.AccessDenied:
|
|
216
|
+
occupied = _get_ports_from_netstat()
|
|
217
|
+
|
|
218
|
+
occupied.update(_local_occupied_ports)
|
|
219
|
+
random.seed(uuid.uuid1().bytes)
|
|
220
|
+
randn = random.randint(0, 100000000)
|
|
221
|
+
|
|
222
|
+
idx = int(randn % (1 + HIGH_PORT_BOUND - LOW_PORT_BOUND - len(occupied)))
|
|
223
|
+
for i in range(LOW_PORT_BOUND, HIGH_PORT_BOUND + 1):
|
|
224
|
+
if i in occupied:
|
|
225
|
+
continue
|
|
226
|
+
if idx == 0:
|
|
227
|
+
if occupy:
|
|
228
|
+
_local_occupied_ports.add(i)
|
|
229
|
+
return i
|
|
230
|
+
idx -= 1
|
|
231
|
+
raise SystemError("No ports available.")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def lazy_import(
|
|
235
|
+
name: str,
|
|
236
|
+
package: str | None = None,
|
|
237
|
+
globals: dict | None = None, # pylint: disable=redefined-builtin
|
|
238
|
+
locals: dict | None = None, # pylint: disable=redefined-builtin
|
|
239
|
+
rename: str | None = None,
|
|
240
|
+
placeholder: bool = False,
|
|
241
|
+
):
|
|
242
|
+
rename = rename or name
|
|
243
|
+
prefix_name = name.split(".", 1)[0]
|
|
244
|
+
globals = globals or inspect.currentframe().f_back.f_globals # type: ignore
|
|
245
|
+
|
|
246
|
+
class LazyModule:
|
|
247
|
+
def __init__(self):
|
|
248
|
+
self._on_loads = []
|
|
249
|
+
|
|
250
|
+
def __getattr__(self, item):
|
|
251
|
+
if item.startswith("_pytest") or item in ("__bases__", "__test__"):
|
|
252
|
+
raise AttributeError(item)
|
|
253
|
+
|
|
254
|
+
real_mod = importlib.import_module(name, package=package)
|
|
255
|
+
if rename in globals:
|
|
256
|
+
globals[rename] = real_mod
|
|
257
|
+
elif locals is not None:
|
|
258
|
+
locals[rename] = real_mod
|
|
259
|
+
ret = getattr(real_mod, item)
|
|
260
|
+
for on_load_func in self._on_loads:
|
|
261
|
+
on_load_func()
|
|
262
|
+
# make sure on_load hooks only executed once
|
|
263
|
+
self._on_loads = []
|
|
264
|
+
return ret
|
|
265
|
+
|
|
266
|
+
def add_load_handler(self, func: Callable):
|
|
267
|
+
self._on_loads.append(func)
|
|
268
|
+
return func
|
|
269
|
+
|
|
270
|
+
if importlib_utils.find_spec(prefix_name) is not None:
|
|
271
|
+
return LazyModule()
|
|
272
|
+
elif placeholder:
|
|
273
|
+
return ModulePlaceholder(prefix_name)
|
|
274
|
+
else:
|
|
275
|
+
return None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def lazy_import_on_load(lazy_mod):
|
|
279
|
+
def wrapper(fun):
|
|
280
|
+
if lazy_mod is not None and hasattr(lazy_mod, "add_load_handler"):
|
|
281
|
+
lazy_mod.add_load_handler(fun)
|
|
282
|
+
return fun
|
|
283
|
+
|
|
284
|
+
return wrapper
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class ModulePlaceholder:
|
|
288
|
+
def __init__(self, mod_name: str):
|
|
289
|
+
self._mod_name = mod_name
|
|
290
|
+
|
|
291
|
+
def _raises(self):
|
|
292
|
+
raise AttributeError(f"{self._mod_name} is required but not installed.")
|
|
293
|
+
|
|
294
|
+
def __getattr__(self, key):
|
|
295
|
+
self._raises()
|
|
296
|
+
|
|
297
|
+
def __call__(self, *_args, **_kwargs):
|
|
298
|
+
self._raises()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def patch_asyncio_task_create_time(): # pragma: no cover
|
|
302
|
+
new_loop = False
|
|
303
|
+
try:
|
|
304
|
+
loop = asyncio.get_running_loop()
|
|
305
|
+
except RuntimeError:
|
|
306
|
+
loop = asyncio.new_event_loop()
|
|
307
|
+
new_loop = True
|
|
308
|
+
loop_class = loop.__class__
|
|
309
|
+
# Save raw loop_class.create_task and make multiple apply idempotent
|
|
310
|
+
loop_create_task = getattr(
|
|
311
|
+
patch_asyncio_task_create_time, "loop_create_task", loop_class.create_task
|
|
312
|
+
)
|
|
313
|
+
patch_asyncio_task_create_time.loop_create_task = loop_create_task
|
|
314
|
+
|
|
315
|
+
def new_loop_create_task(*args, **kwargs):
|
|
316
|
+
task = loop_create_task(*args, **kwargs)
|
|
317
|
+
task.__xoscar_asyncio_task_create_time__ = time.time()
|
|
318
|
+
return task
|
|
319
|
+
|
|
320
|
+
if loop_create_task is not new_loop_create_task:
|
|
321
|
+
loop_class.create_task = new_loop_create_task
|
|
322
|
+
if not new_loop and loop.create_task is not new_loop_create_task:
|
|
323
|
+
loop.create_task = functools.partial(new_loop_create_task, loop)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
async def asyncio_task_timeout_detector(
|
|
327
|
+
check_interval: int, task_timeout_seconds: int, task_exclude_filters: list[str]
|
|
328
|
+
):
|
|
329
|
+
task_exclude_filters.append("asyncio_task_timeout_detector")
|
|
330
|
+
while True: # pragma: no cover
|
|
331
|
+
await asyncio.sleep(check_interval)
|
|
332
|
+
loop = asyncio.get_running_loop()
|
|
333
|
+
current_time = (
|
|
334
|
+
time.time()
|
|
335
|
+
) # avoid invoke `time.time()` frequently if we have plenty of unfinished tasks.
|
|
336
|
+
for task in asyncio.all_tasks(loop=loop):
|
|
337
|
+
# Some task may be create before `patch_asyncio_task_create_time` applied, take them as never timeout.
|
|
338
|
+
create_time = getattr(
|
|
339
|
+
task, "__xoscar_asyncio_task_create_time__", current_time
|
|
340
|
+
)
|
|
341
|
+
if current_time - create_time >= task_timeout_seconds:
|
|
342
|
+
stack = io.StringIO()
|
|
343
|
+
task.print_stack(file=stack)
|
|
344
|
+
task_str = str(task)
|
|
345
|
+
if any(
|
|
346
|
+
excluded_task in task_str for excluded_task in task_exclude_filters
|
|
347
|
+
):
|
|
348
|
+
continue
|
|
349
|
+
logger.warning(
|
|
350
|
+
"""Task %s in event loop %s doesn't finish in %s seconds. %s""",
|
|
351
|
+
task,
|
|
352
|
+
loop,
|
|
353
|
+
time.time() - create_time,
|
|
354
|
+
stack.getvalue(),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def register_asyncio_task_timeout_detector(
|
|
359
|
+
check_interval: int | None = None,
|
|
360
|
+
task_timeout_seconds: int | None = None,
|
|
361
|
+
task_exclude_filters: list[str] | None = None,
|
|
362
|
+
) -> asyncio.Task | None: # pragma: no cover
|
|
363
|
+
"""Register a asyncio task which print timeout task periodically."""
|
|
364
|
+
check_interval = check_interval or int(
|
|
365
|
+
os.environ.get("XOSCAR_DEBUG_ASYNCIO_TASK_TIMEOUT_CHECK_INTERVAL", -1)
|
|
366
|
+
)
|
|
367
|
+
if check_interval > 0:
|
|
368
|
+
patch_asyncio_task_create_time()
|
|
369
|
+
task_timeout_seconds = task_timeout_seconds or int(
|
|
370
|
+
os.environ.get("XOSCAR_DEBUG_ASYNCIO_TASK_TIMEOUT_SECONDS", check_interval)
|
|
371
|
+
)
|
|
372
|
+
if not task_exclude_filters:
|
|
373
|
+
# Ignore Xoscar by default since it has some long-running coroutines.
|
|
374
|
+
task_exclude_filter = os.environ.get(
|
|
375
|
+
"XOSCAR_DEBUG_ASYNCIO_TASK_EXCLUDE_FILTERS", "xoscar"
|
|
376
|
+
)
|
|
377
|
+
task_exclude_filters = task_exclude_filter.split(";")
|
|
378
|
+
if sys.version_info[:2] < (3, 7):
|
|
379
|
+
logger.warning(
|
|
380
|
+
"asyncio tasks timeout detector is not supported under python %s",
|
|
381
|
+
sys.version,
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
loop = asyncio.get_running_loop()
|
|
385
|
+
logger.info(
|
|
386
|
+
"Create asyncio tasks timeout detector with check_interval %s task_timeout_seconds %s "
|
|
387
|
+
"task_exclude_filters %s",
|
|
388
|
+
check_interval,
|
|
389
|
+
task_timeout_seconds,
|
|
390
|
+
task_exclude_filters,
|
|
391
|
+
)
|
|
392
|
+
return loop.create_task(
|
|
393
|
+
asyncio_task_timeout_detector(
|
|
394
|
+
check_interval, task_timeout_seconds, task_exclude_filters
|
|
395
|
+
)
|
|
396
|
+
)
|
|
397
|
+
else:
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def ensure_coverage():
|
|
402
|
+
# make sure coverage is handled when starting with subprocess.Popen
|
|
403
|
+
if (
|
|
404
|
+
not sys.platform.startswith("win") and "COV_CORE_SOURCE" in os.environ
|
|
405
|
+
): # pragma: no cover
|
|
406
|
+
try:
|
|
407
|
+
from pytest_cov.embed import cleanup_on_sigterm
|
|
408
|
+
except ImportError:
|
|
409
|
+
pass
|
|
410
|
+
else:
|
|
411
|
+
cleanup_on_sigterm()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def retry_callable(
|
|
415
|
+
callable_,
|
|
416
|
+
ex_type: type = Exception,
|
|
417
|
+
wait_interval=1,
|
|
418
|
+
max_retries=-1,
|
|
419
|
+
sync: bool | None = None,
|
|
420
|
+
):
|
|
421
|
+
if inspect.iscoroutinefunction(callable_) or sync is False:
|
|
422
|
+
|
|
423
|
+
@functools.wraps(callable)
|
|
424
|
+
async def retry_call(*args, **kwargs):
|
|
425
|
+
num_retried = 0
|
|
426
|
+
while max_retries < 0 or num_retried < max_retries:
|
|
427
|
+
num_retried += 1
|
|
428
|
+
try:
|
|
429
|
+
return await callable_(*args, **kwargs)
|
|
430
|
+
except ex_type:
|
|
431
|
+
await asyncio.sleep(wait_interval)
|
|
432
|
+
|
|
433
|
+
else:
|
|
434
|
+
|
|
435
|
+
@functools.wraps(callable)
|
|
436
|
+
def retry_call(*args, **kwargs):
|
|
437
|
+
num_retried = 0
|
|
438
|
+
ex = None
|
|
439
|
+
while max_retries < 0 or num_retried < max_retries:
|
|
440
|
+
num_retried += 1
|
|
441
|
+
try:
|
|
442
|
+
return callable_(*args, **kwargs)
|
|
443
|
+
except ex_type as e:
|
|
444
|
+
ex = e
|
|
445
|
+
time.sleep(wait_interval)
|
|
446
|
+
assert ex is not None
|
|
447
|
+
raise ex # pylint: disable-msg=E0702
|
|
448
|
+
|
|
449
|
+
return retry_call
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
_cupy = lazy_import("cupy")
|
|
453
|
+
_rmm = lazy_import("rmm")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def is_cuda_buffer(cuda_buffer: Union["_cupy.ndarray", "_rmm.DeviceBuffer"]) -> bool: # type: ignore
|
|
457
|
+
return hasattr(cuda_buffer, "__cuda_array_interface__")
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def is_windows():
|
|
461
|
+
return sys.platform.startswith("win")
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def is_linux():
|
|
465
|
+
return sys.platform.startswith("linux")
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@lru_cache
|
|
469
|
+
def is_py_312():
|
|
470
|
+
return sys.version_info[:2] == (3, 12)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@lru_cache
|
|
474
|
+
def is_py_312_or_above():
|
|
475
|
+
return sys.version_info[:2] >= (3, 12)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def is_v4_zero_ip(ip_port_addr: str) -> bool:
|
|
479
|
+
return ip_port_addr.split("://")[-1].startswith("0.0.0.0:")
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def is_v6_zero_ip(ip_port_addr: str) -> bool:
|
|
483
|
+
# tcp6 addr ":::123", ":: means all zero"
|
|
484
|
+
arr = ip_port_addr.split("://")[-1].split(":")
|
|
485
|
+
if len(arr) <= 2: # Not tcp6 or udp6
|
|
486
|
+
return False
|
|
487
|
+
for part in arr[0:-1]:
|
|
488
|
+
if part != "":
|
|
489
|
+
if int(part, 16) != 0:
|
|
490
|
+
return False
|
|
491
|
+
return True
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def is_zero_ip(ip_port_addr: str) -> bool:
|
|
495
|
+
return is_v4_zero_ip(ip_port_addr) or is_v6_zero_ip(ip_port_addr)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def is_v6_ip(ip_port_addr: str) -> bool:
|
|
499
|
+
arr = ip_port_addr.split("://", 1)[-1].split(":")
|
|
500
|
+
return len(arr) > 1
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def fix_all_zero_ip(remote_addr: str, connect_addr: str) -> str:
|
|
504
|
+
"""
|
|
505
|
+
Use connect_addr to fix ActorRef.address return by remote server.
|
|
506
|
+
When remote server listen on "0.0.0.0:port" or ":::port", it will return ActorRef.address set to listening addr,
|
|
507
|
+
it cannot be use by client for the following interaction unless we fix it.
|
|
508
|
+
(client will treat 0.0.0.0 as 127.0.0.1)
|
|
509
|
+
|
|
510
|
+
NOTE: Server might return a different addr from a pool for load-balance purpose.
|
|
511
|
+
"""
|
|
512
|
+
if remote_addr == connect_addr:
|
|
513
|
+
return remote_addr
|
|
514
|
+
if not is_v4_zero_ip(remote_addr) and not is_v6_zero_ip(remote_addr):
|
|
515
|
+
# Remote server returns on non-zero ip
|
|
516
|
+
return remote_addr
|
|
517
|
+
if is_v4_zero_ip(connect_addr) or is_v6_zero_ip(connect_addr):
|
|
518
|
+
# Client connect to local server
|
|
519
|
+
return remote_addr
|
|
520
|
+
remote_port = remote_addr.split(":")[-1]
|
|
521
|
+
connect_ip = ":".join(connect_addr.split(":")[0:-1]) # Remote the port
|
|
522
|
+
return f"{connect_ip}:{remote_port}"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Copyright 2022-2025 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
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .core import VirtualEnvManager
|
|
20
|
+
from .uv import UVVirtualEnvManager
|
|
21
|
+
|
|
22
|
+
_name_to_managers = {"uv": UVVirtualEnvManager}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_virtual_env_manager(env_name: str, env_path: str | Path) -> VirtualEnvManager:
|
|
26
|
+
try:
|
|
27
|
+
manager_cls = _name_to_managers[env_name]
|
|
28
|
+
except KeyError:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"Unknown virtualenv manager {env_name}, available: {list(_name_to_managers)}"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
path = Path(env_path)
|
|
34
|
+
return manager_cls(path)
|