opengris-scaler 1.12.28__cp313-cp313-musllinux_1_2_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.
Potentially problematic release.
This version of opengris-scaler might be problematic. Click here for more details.
- opengris_scaler-1.12.28.dist-info/METADATA +728 -0
- opengris_scaler-1.12.28.dist-info/RECORD +187 -0
- opengris_scaler-1.12.28.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.28.dist-info/entry_points.txt +10 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.28.dist-info/licenses/NOTICE +8 -0
- opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
- opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
- opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
- opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
- scaler/__init__.py +14 -0
- scaler/about.py +5 -0
- scaler/client/__init__.py +0 -0
- scaler/client/agent/__init__.py +0 -0
- scaler/client/agent/client_agent.py +210 -0
- scaler/client/agent/disconnect_manager.py +27 -0
- scaler/client/agent/future_manager.py +112 -0
- scaler/client/agent/heartbeat_manager.py +74 -0
- scaler/client/agent/mixins.py +89 -0
- scaler/client/agent/object_manager.py +98 -0
- scaler/client/agent/task_manager.py +64 -0
- scaler/client/client.py +658 -0
- scaler/client/future.py +252 -0
- scaler/client/object_buffer.py +129 -0
- scaler/client/object_reference.py +25 -0
- scaler/client/serializer/__init__.py +0 -0
- scaler/client/serializer/default.py +16 -0
- scaler/client/serializer/mixins.py +38 -0
- scaler/cluster/__init__.py +0 -0
- scaler/cluster/cluster.py +115 -0
- scaler/cluster/combo.py +150 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +86 -0
- scaler/config/__init__.py +0 -0
- scaler/config/defaults.py +94 -0
- scaler/config/loader.py +96 -0
- scaler/config/mixins.py +20 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +55 -0
- scaler/config/section/ecs_worker_adapter.py +85 -0
- scaler/config/section/native_worker_adapter.py +43 -0
- scaler/config/section/object_storage_server.py +8 -0
- scaler/config/section/scheduler.py +54 -0
- scaler/config/section/symphony_worker_adapter.py +47 -0
- scaler/config/section/top.py +13 -0
- scaler/config/section/webui.py +21 -0
- scaler/config/types/__init__.py +0 -0
- scaler/config/types/network_backend.py +12 -0
- scaler/config/types/object_storage_server.py +45 -0
- scaler/config/types/worker.py +62 -0
- scaler/config/types/zmq.py +83 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +133 -0
- scaler/entry_points/object_storage_server.py +45 -0
- scaler/entry_points/scheduler.py +144 -0
- scaler/entry_points/top.py +286 -0
- scaler/entry_points/webui.py +48 -0
- scaler/entry_points/worker_adapter_ecs.py +191 -0
- scaler/entry_points/worker_adapter_native.py +137 -0
- scaler/entry_points/worker_adapter_symphony.py +98 -0
- scaler/io/__init__.py +0 -0
- scaler/io/async_binder.py +89 -0
- scaler/io/async_connector.py +95 -0
- scaler/io/async_object_storage_connector.py +225 -0
- scaler/io/mixins.py +154 -0
- scaler/io/sync_connector.py +68 -0
- scaler/io/sync_object_storage_connector.py +247 -0
- scaler/io/sync_subscriber.py +83 -0
- scaler/io/utility.py +80 -0
- scaler/io/ymq/__init__.py +0 -0
- scaler/io/ymq/_ymq.pyi +95 -0
- scaler/io/ymq/ymq.py +138 -0
- scaler/io/ymq_async_object_storage_connector.py +184 -0
- scaler/io/ymq_sync_object_storage_connector.py +184 -0
- scaler/object_storage/__init__.py +0 -0
- scaler/protocol/__init__.py +0 -0
- scaler/protocol/capnp/__init__.py +0 -0
- scaler/protocol/capnp/_python.py +6 -0
- scaler/protocol/capnp/common.capnp +68 -0
- scaler/protocol/capnp/message.capnp +218 -0
- scaler/protocol/capnp/object_storage.capnp +57 -0
- scaler/protocol/capnp/status.capnp +73 -0
- scaler/protocol/introduction.md +105 -0
- scaler/protocol/python/__init__.py +0 -0
- scaler/protocol/python/common.py +140 -0
- scaler/protocol/python/message.py +751 -0
- scaler/protocol/python/mixins.py +13 -0
- scaler/protocol/python/object_storage.py +118 -0
- scaler/protocol/python/status.py +279 -0
- scaler/protocol/worker.md +228 -0
- scaler/scheduler/__init__.py +0 -0
- scaler/scheduler/allocate_policy/__init__.py +0 -0
- scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
- scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
- scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
- scaler/scheduler/allocate_policy/mixins.py +55 -0
- scaler/scheduler/controllers/__init__.py +0 -0
- scaler/scheduler/controllers/balance_controller.py +65 -0
- scaler/scheduler/controllers/client_controller.py +131 -0
- scaler/scheduler/controllers/config_controller.py +31 -0
- scaler/scheduler/controllers/graph_controller.py +424 -0
- scaler/scheduler/controllers/information_controller.py +81 -0
- scaler/scheduler/controllers/mixins.py +194 -0
- scaler/scheduler/controllers/object_controller.py +147 -0
- scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
- scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
- scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
- scaler/scheduler/controllers/scaling_policies/null.py +14 -0
- scaler/scheduler/controllers/scaling_policies/types.py +9 -0
- scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
- scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
- scaler/scheduler/controllers/task_controller.py +376 -0
- scaler/scheduler/controllers/worker_controller.py +169 -0
- scaler/scheduler/object_usage/__init__.py +0 -0
- scaler/scheduler/object_usage/object_tracker.py +131 -0
- scaler/scheduler/scheduler.py +251 -0
- scaler/scheduler/task/__init__.py +0 -0
- scaler/scheduler/task/task_state_machine.py +92 -0
- scaler/scheduler/task/task_state_manager.py +61 -0
- scaler/ui/__init__.py +0 -0
- scaler/ui/constants.py +9 -0
- scaler/ui/live_display.py +147 -0
- scaler/ui/memory_window.py +146 -0
- scaler/ui/setting_page.py +40 -0
- scaler/ui/task_graph.py +832 -0
- scaler/ui/task_log.py +107 -0
- scaler/ui/utility.py +66 -0
- scaler/ui/webui.py +147 -0
- scaler/ui/worker_processors.py +104 -0
- scaler/utility/__init__.py +0 -0
- scaler/utility/debug.py +19 -0
- scaler/utility/event_list.py +63 -0
- scaler/utility/event_loop.py +58 -0
- scaler/utility/exceptions.py +42 -0
- scaler/utility/formatter.py +44 -0
- scaler/utility/graph/__init__.py +0 -0
- scaler/utility/graph/optimization.py +27 -0
- scaler/utility/graph/topological_sorter.py +11 -0
- scaler/utility/graph/topological_sorter_graphblas.py +174 -0
- scaler/utility/identifiers.py +107 -0
- scaler/utility/logging/__init__.py +0 -0
- scaler/utility/logging/decorators.py +25 -0
- scaler/utility/logging/scoped_logger.py +33 -0
- scaler/utility/logging/utility.py +183 -0
- scaler/utility/many_to_many_dict.py +123 -0
- scaler/utility/metadata/__init__.py +0 -0
- scaler/utility/metadata/profile_result.py +31 -0
- scaler/utility/metadata/task_flags.py +30 -0
- scaler/utility/mixins.py +13 -0
- scaler/utility/network_util.py +7 -0
- scaler/utility/one_to_many_dict.py +72 -0
- scaler/utility/queues/__init__.py +0 -0
- scaler/utility/queues/async_indexed_queue.py +37 -0
- scaler/utility/queues/async_priority_queue.py +70 -0
- scaler/utility/queues/async_sorted_priority_queue.py +45 -0
- scaler/utility/queues/indexed_queue.py +114 -0
- scaler/utility/serialization.py +9 -0
- scaler/version.txt +1 -0
- scaler/worker/__init__.py +0 -0
- scaler/worker/agent/__init__.py +0 -0
- scaler/worker/agent/heartbeat_manager.py +107 -0
- scaler/worker/agent/mixins.py +137 -0
- scaler/worker/agent/processor/__init__.py +0 -0
- scaler/worker/agent/processor/object_cache.py +107 -0
- scaler/worker/agent/processor/processor.py +285 -0
- scaler/worker/agent/processor/streaming_buffer.py +28 -0
- scaler/worker/agent/processor_holder.py +147 -0
- scaler/worker/agent/processor_manager.py +369 -0
- scaler/worker/agent/profiling_manager.py +109 -0
- scaler/worker/agent/task_manager.py +150 -0
- scaler/worker/agent/timeout_manager.py +19 -0
- scaler/worker/preload.py +84 -0
- scaler/worker/worker.py +265 -0
- scaler/worker_adapter/__init__.py +0 -0
- scaler/worker_adapter/common.py +26 -0
- scaler/worker_adapter/ecs.py +269 -0
- scaler/worker_adapter/native.py +155 -0
- scaler/worker_adapter/symphony/__init__.py +0 -0
- scaler/worker_adapter/symphony/callback.py +45 -0
- scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
- scaler/worker_adapter/symphony/message.py +24 -0
- scaler/worker_adapter/symphony/task_manager.py +289 -0
- scaler/worker_adapter/symphony/worker.py +204 -0
- scaler/worker_adapter/symphony/worker_adapter.py +139 -0
- src/scaler/io/ymq/_ymq.so +0 -0
- src/scaler/object_storage/object_storage_server.so +0 -0
scaler/io/ymq/_ymq.pyi
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# NOTE: NOT IMPLEMENTATION, TYPE INFORMATION ONLY
|
|
2
|
+
# This file contains type stubs for the Ymq Python C Extension module
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
from typing import Callable, Optional, SupportsBytes, Union
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from collections.abc import Buffer # type: ignore[attr-defined]
|
|
8
|
+
except ImportError:
|
|
9
|
+
from typing_extensions import Buffer
|
|
10
|
+
|
|
11
|
+
class Bytes(Buffer):
|
|
12
|
+
data: bytes | None
|
|
13
|
+
len: int
|
|
14
|
+
|
|
15
|
+
def __init__(self, data: Buffer | None = None) -> None: ...
|
|
16
|
+
def __repr__(self) -> str: ...
|
|
17
|
+
def __len__(self) -> int: ...
|
|
18
|
+
|
|
19
|
+
# this type signature is not 100% accurate because it's implemented in C
|
|
20
|
+
# but this satisfies the type check and is good enough
|
|
21
|
+
def __buffer__(self, flags: int, /) -> memoryview: ...
|
|
22
|
+
|
|
23
|
+
class Message:
|
|
24
|
+
address: Bytes | None
|
|
25
|
+
payload: Bytes
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, address: Bytes | bytes | SupportsBytes | None, payload: Bytes | bytes | SupportsBytes
|
|
29
|
+
) -> None: ...
|
|
30
|
+
def __repr__(self) -> str: ...
|
|
31
|
+
def __str__(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
class IOSocketType(IntEnum):
|
|
34
|
+
Uninit = 0
|
|
35
|
+
Binder = 1
|
|
36
|
+
Connector = 2
|
|
37
|
+
Unicast = 3
|
|
38
|
+
Multicast = 4
|
|
39
|
+
|
|
40
|
+
class BaseIOContext:
|
|
41
|
+
num_threads: int
|
|
42
|
+
|
|
43
|
+
def __init__(self, num_threads: int = 1) -> None: ...
|
|
44
|
+
def __repr__(self) -> str: ...
|
|
45
|
+
def createIOSocket(
|
|
46
|
+
self, callback: Callable[[Union[BaseIOSocket, Exception]], None], identity: str, socket_type: IOSocketType
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Create an io socket with an identity and socket type"""
|
|
49
|
+
|
|
50
|
+
class BaseIOSocket:
|
|
51
|
+
identity: str
|
|
52
|
+
socket_type: IOSocketType
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str: ...
|
|
55
|
+
def send(self, callback: Callable[[Optional[Exception]], None], message: Message) -> None:
|
|
56
|
+
"""Send a message to one of the socket's peers"""
|
|
57
|
+
|
|
58
|
+
def recv(self, callback: Callable[[Union[Message, Exception]], None]) -> None:
|
|
59
|
+
"""Receive a message from one of the socket's peers"""
|
|
60
|
+
|
|
61
|
+
def bind(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
|
|
62
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
63
|
+
|
|
64
|
+
def connect(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
|
|
65
|
+
"""Connect to a remote socket"""
|
|
66
|
+
|
|
67
|
+
class ErrorCode(IntEnum):
|
|
68
|
+
Uninit = 0
|
|
69
|
+
InvalidPortFormat = 1
|
|
70
|
+
InvalidAddressFormat = 2
|
|
71
|
+
ConfigurationError = 3
|
|
72
|
+
SignalNotSupported = 4
|
|
73
|
+
CoreBug = 5
|
|
74
|
+
RepetetiveIOSocketIdentity = 6
|
|
75
|
+
RedundantIOSocketRefCount = 7
|
|
76
|
+
MultipleConnectToNotSupported = 8
|
|
77
|
+
MultipleBindToNotSupported = 9
|
|
78
|
+
InitialConnectFailedWithInProgress = 10
|
|
79
|
+
SendMessageRequestCouldNotComplete = 11
|
|
80
|
+
SetSockOptNonFatalFailure = 12
|
|
81
|
+
IPv6NotSupported = 13
|
|
82
|
+
RemoteEndDisconnectedOnSocketWithoutGuaranteedDelivery = 14
|
|
83
|
+
ConnectorSocketClosedByRemoteEnd = 15
|
|
84
|
+
IOSocketStopRequested = 16
|
|
85
|
+
BinderSendMessageWithNoAddress = 17
|
|
86
|
+
|
|
87
|
+
def explanation(self) -> str: ...
|
|
88
|
+
|
|
89
|
+
class YMQException(Exception):
|
|
90
|
+
code: ErrorCode
|
|
91
|
+
message: str
|
|
92
|
+
|
|
93
|
+
def __init__(self, /, code: ErrorCode, message: str) -> None: ...
|
|
94
|
+
def __repr__(self) -> str: ...
|
|
95
|
+
def __str__(self) -> str: ...
|
scaler/io/ymq/ymq.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# This file wraps the interface exported by the C implementation of the module
|
|
2
|
+
# and provides a more ergonomic interface supporting both asynchronous and synchronous execution
|
|
3
|
+
|
|
4
|
+
__all__ = ["IOSocket", "IOContext", "Message", "IOSocketType", "YMQException", "Bytes", "ErrorCode"]
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from typing import Concatenate, ParamSpec # type: ignore[attr-defined]
|
|
12
|
+
except ImportError:
|
|
13
|
+
from typing_extensions import ParamSpec, Concatenate # type: ignore[assignment]
|
|
14
|
+
|
|
15
|
+
from scaler.io.ymq._ymq import BaseIOContext, BaseIOSocket, Bytes, ErrorCode, IOSocketType, Message, YMQException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IOSocket:
|
|
19
|
+
_base: BaseIOSocket
|
|
20
|
+
|
|
21
|
+
def __init__(self, base: BaseIOSocket) -> None:
|
|
22
|
+
self._base = base
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def socket_type(self) -> IOSocketType:
|
|
26
|
+
return self._base.socket_type
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def identity(self) -> str:
|
|
30
|
+
return self._base.identity
|
|
31
|
+
|
|
32
|
+
async def bind(self, address: str) -> None:
|
|
33
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
34
|
+
await call_async(self._base.bind, address)
|
|
35
|
+
|
|
36
|
+
def bind_sync(self, address: str, /, timeout: Optional[float] = None) -> None:
|
|
37
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
38
|
+
call_sync(self._base.bind, address, timeout=timeout)
|
|
39
|
+
|
|
40
|
+
async def connect(self, address: str) -> None:
|
|
41
|
+
"""Connect to a remote socket"""
|
|
42
|
+
await call_async(self._base.connect, address)
|
|
43
|
+
|
|
44
|
+
def connect_sync(self, address: str, /, timeout: Optional[float] = None) -> None:
|
|
45
|
+
"""Connect to a remote socket"""
|
|
46
|
+
call_sync(self._base.connect, address, timeout=timeout)
|
|
47
|
+
|
|
48
|
+
async def send(self, message: Message) -> None:
|
|
49
|
+
"""Send a message to one of the socket's peers"""
|
|
50
|
+
await call_async(self._base.send, message)
|
|
51
|
+
|
|
52
|
+
def send_sync(self, message: Message, /, timeout: Optional[float] = None) -> None:
|
|
53
|
+
"""Send a message to one of the socket's peers"""
|
|
54
|
+
call_sync(self._base.send, message, timeout=timeout)
|
|
55
|
+
|
|
56
|
+
async def recv(self) -> Message:
|
|
57
|
+
"""Receive a message from one of the socket's peers"""
|
|
58
|
+
return await call_async(self._base.recv)
|
|
59
|
+
|
|
60
|
+
def recv_sync(self, /, timeout: Optional[float] = None) -> Message:
|
|
61
|
+
"""Receive a message from one of the socket's peers"""
|
|
62
|
+
return call_sync(self._base.recv, timeout=timeout)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class IOContext:
|
|
66
|
+
_base: BaseIOContext
|
|
67
|
+
|
|
68
|
+
def __init__(self, num_threads: int = 1) -> None:
|
|
69
|
+
self._base = BaseIOContext(num_threads)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def num_threads(self) -> int:
|
|
73
|
+
return self._base.num_threads
|
|
74
|
+
|
|
75
|
+
async def createIOSocket(self, identity: str, socket_type: IOSocketType) -> IOSocket:
|
|
76
|
+
"""Create an io socket with an identity and socket type"""
|
|
77
|
+
return IOSocket(await call_async(self._base.createIOSocket, identity, socket_type))
|
|
78
|
+
|
|
79
|
+
def createIOSocket_sync(self, identity: str, socket_type: IOSocketType) -> IOSocket:
|
|
80
|
+
"""Create an io socket with an identity and socket type"""
|
|
81
|
+
return IOSocket(call_sync(self._base.createIOSocket, identity, socket_type))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
P = ParamSpec("P")
|
|
85
|
+
T = TypeVar("T")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def call_async(
|
|
89
|
+
func: Callable[Concatenate[Callable[[Union[T, BaseException]], None], P], None], # type: ignore
|
|
90
|
+
*args: P.args, # type: ignore
|
|
91
|
+
**kwargs: P.kwargs, # type: ignore
|
|
92
|
+
) -> T:
|
|
93
|
+
loop = asyncio.get_event_loop()
|
|
94
|
+
future = loop.create_future()
|
|
95
|
+
|
|
96
|
+
def callback(result: Union[T, BaseException]):
|
|
97
|
+
if isinstance(result, BaseException):
|
|
98
|
+
loop.call_soon_threadsafe(_safe_set_exception, future, result)
|
|
99
|
+
else:
|
|
100
|
+
loop.call_soon_threadsafe(_safe_set_result, future, result)
|
|
101
|
+
|
|
102
|
+
func(callback, *args, **kwargs)
|
|
103
|
+
return await future
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# about the ignore directives: mypy cannot properly handle typing extension's ParamSpec and Concatenate in python <=3.9
|
|
107
|
+
# these type hints are correctly understood in Python 3.10+
|
|
108
|
+
def call_sync( # type: ignore[valid-type]
|
|
109
|
+
func: Callable[Concatenate[Callable[[Union[T, BaseException]], None], P], None], # type: ignore
|
|
110
|
+
*args: P.args, # type: ignore
|
|
111
|
+
timeout: Optional[float] = None,
|
|
112
|
+
**kwargs: P.kwargs, # type: ignore
|
|
113
|
+
) -> T: # type: ignore
|
|
114
|
+
future: concurrent.futures.Future = concurrent.futures.Future()
|
|
115
|
+
|
|
116
|
+
def callback(result: Union[T, BaseException]):
|
|
117
|
+
if future.done():
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
if isinstance(result, BaseException):
|
|
121
|
+
future.set_exception(result)
|
|
122
|
+
else:
|
|
123
|
+
future.set_result(result)
|
|
124
|
+
|
|
125
|
+
func(callback, *args, **kwargs)
|
|
126
|
+
return future.result(timeout)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _safe_set_result(future: asyncio.Future, result: Any) -> None:
|
|
130
|
+
if future.done():
|
|
131
|
+
return
|
|
132
|
+
future.set_result(result)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _safe_set_exception(future: asyncio.Future, exc: BaseException) -> None:
|
|
136
|
+
if future.done():
|
|
137
|
+
return
|
|
138
|
+
future.set_exception(exc)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from scaler.io.mixins import AsyncObjectStorageConnector
|
|
8
|
+
from scaler.io.ymq.ymq import IOContext, IOSocketType, Message, YMQException
|
|
9
|
+
from scaler.protocol.capnp._python import _object_storage # noqa
|
|
10
|
+
from scaler.protocol.python.object_storage import ObjectRequestHeader, ObjectResponseHeader, to_capnp_object_id
|
|
11
|
+
from scaler.utility.exceptions import ObjectStorageException
|
|
12
|
+
from scaler.utility.identifiers import ObjectID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PyYMQAsyncObjectStorageConnector(AsyncObjectStorageConnector):
|
|
16
|
+
"""An asyncio connector that uses YMQ to connect to a Scaler's object storage instance."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._host: Optional[str] = None
|
|
20
|
+
self._port: Optional[int] = None
|
|
21
|
+
|
|
22
|
+
self._connected_event = asyncio.Event()
|
|
23
|
+
|
|
24
|
+
self._next_request_id = 0
|
|
25
|
+
self._pending_get_requests: Dict[ObjectID, asyncio.Future] = {}
|
|
26
|
+
|
|
27
|
+
self._lock = asyncio.Lock()
|
|
28
|
+
self._identity: str = f"{self.__class__.__name__}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}"
|
|
29
|
+
self._io_context: IOContext = IOContext()
|
|
30
|
+
self._io_socket = self._io_context.createIOSocket_sync(self._identity, IOSocketType.Connector)
|
|
31
|
+
|
|
32
|
+
def __del__(self):
|
|
33
|
+
if not self.is_connected():
|
|
34
|
+
return
|
|
35
|
+
self._io_socket = None
|
|
36
|
+
|
|
37
|
+
async def connect(self, host: str, port: int):
|
|
38
|
+
self._host = host
|
|
39
|
+
self._port = port
|
|
40
|
+
|
|
41
|
+
if self.is_connected():
|
|
42
|
+
raise ObjectStorageException("connector is already connected.")
|
|
43
|
+
await self._io_socket.connect(self.address)
|
|
44
|
+
self._connected_event.set()
|
|
45
|
+
|
|
46
|
+
async def wait_until_connected(self):
|
|
47
|
+
await self._connected_event.wait()
|
|
48
|
+
|
|
49
|
+
def is_connected(self) -> bool:
|
|
50
|
+
return self._connected_event.is_set()
|
|
51
|
+
|
|
52
|
+
async def destroy(self):
|
|
53
|
+
if not self.is_connected():
|
|
54
|
+
return
|
|
55
|
+
self._io_socket = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def address(self) -> str:
|
|
59
|
+
return f"tcp://{self._host}:{self._port}"
|
|
60
|
+
|
|
61
|
+
async def routine(self):
|
|
62
|
+
await self.wait_until_connected()
|
|
63
|
+
|
|
64
|
+
response = await self.__receive_response()
|
|
65
|
+
if response is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
header, payload = response
|
|
69
|
+
|
|
70
|
+
if header.response_type != ObjectResponseHeader.ObjectResponseType.GetOK:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
pending_get_future = self._pending_get_requests.pop(header.object_id, None)
|
|
74
|
+
|
|
75
|
+
if pending_get_future is None:
|
|
76
|
+
logging.warning(f"unknown get-ok response for unrequested object_id={repr(header.object_id)}.")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
pending_get_future.set_result(payload)
|
|
80
|
+
|
|
81
|
+
async def set_object(self, object_id: ObjectID, payload: bytes) -> None:
|
|
82
|
+
await self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
|
|
83
|
+
|
|
84
|
+
async def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytes:
|
|
85
|
+
pending_get_future = self._pending_get_requests.get(object_id)
|
|
86
|
+
|
|
87
|
+
if pending_get_future is None:
|
|
88
|
+
pending_get_future = asyncio.Future()
|
|
89
|
+
self._pending_get_requests[object_id] = pending_get_future
|
|
90
|
+
|
|
91
|
+
await self.__send_request(
|
|
92
|
+
object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject, None
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return await pending_get_future
|
|
96
|
+
|
|
97
|
+
async def delete_object(self, object_id: ObjectID) -> None:
|
|
98
|
+
await self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject, None)
|
|
99
|
+
|
|
100
|
+
async def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
|
|
101
|
+
object_id_payload = to_capnp_object_id(object_id).to_bytes()
|
|
102
|
+
|
|
103
|
+
await self.__send_request(
|
|
104
|
+
new_object_id,
|
|
105
|
+
len(object_id_payload),
|
|
106
|
+
ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
|
|
107
|
+
object_id_payload,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def __ensure_is_connected(self):
|
|
111
|
+
if self._io_socket is None:
|
|
112
|
+
raise ObjectStorageException("connector is not connected.")
|
|
113
|
+
|
|
114
|
+
async def __send_request(
|
|
115
|
+
self,
|
|
116
|
+
object_id: ObjectID,
|
|
117
|
+
payload_length: int,
|
|
118
|
+
request_type: ObjectRequestHeader.ObjectRequestType,
|
|
119
|
+
payload: Optional[bytes],
|
|
120
|
+
):
|
|
121
|
+
self.__ensure_is_connected()
|
|
122
|
+
|
|
123
|
+
request_id = self._next_request_id
|
|
124
|
+
self._next_request_id += 1
|
|
125
|
+
self._next_request_id %= 2**64 - 1 # UINT64_MAX
|
|
126
|
+
|
|
127
|
+
header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
async with self._lock:
|
|
131
|
+
await self.__write_request_header(header)
|
|
132
|
+
|
|
133
|
+
if payload is not None:
|
|
134
|
+
await self.__write_request_payload(payload)
|
|
135
|
+
|
|
136
|
+
except YMQException:
|
|
137
|
+
self._io_socket = None
|
|
138
|
+
self.__raise_connection_failure()
|
|
139
|
+
|
|
140
|
+
async def __write_request_header(self, header: ObjectRequestHeader):
|
|
141
|
+
assert self._io_socket is not None
|
|
142
|
+
await self._io_socket.send(Message(address=None, payload=header.get_message().to_bytes()))
|
|
143
|
+
|
|
144
|
+
async def __write_request_payload(self, payload: bytes):
|
|
145
|
+
assert self._io_socket is not None
|
|
146
|
+
await self._io_socket.send(Message(address=None, payload=payload))
|
|
147
|
+
|
|
148
|
+
async def __receive_response(self) -> Optional[Tuple[ObjectResponseHeader, bytes]]:
|
|
149
|
+
if self._io_socket is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
header = await self.__read_response_header()
|
|
154
|
+
payload = await self.__read_response_payload(header)
|
|
155
|
+
except YMQException:
|
|
156
|
+
self._io_socket = None
|
|
157
|
+
self.__raise_connection_failure()
|
|
158
|
+
|
|
159
|
+
return header, payload
|
|
160
|
+
|
|
161
|
+
async def __read_response_header(self) -> ObjectResponseHeader:
|
|
162
|
+
assert self._io_socket is not None
|
|
163
|
+
|
|
164
|
+
msg = await self._io_socket.recv()
|
|
165
|
+
header_data = msg.payload.data
|
|
166
|
+
assert len(header_data) == ObjectResponseHeader.MESSAGE_LENGTH
|
|
167
|
+
|
|
168
|
+
with _object_storage.ObjectResponseHeader.from_bytes(header_data) as header_message:
|
|
169
|
+
return ObjectResponseHeader(header_message)
|
|
170
|
+
|
|
171
|
+
async def __read_response_payload(self, header: ObjectResponseHeader) -> bytes:
|
|
172
|
+
assert self._io_socket is not None
|
|
173
|
+
# assert self._reader is not None
|
|
174
|
+
|
|
175
|
+
if header.payload_length > 0:
|
|
176
|
+
res = await self._io_socket.recv()
|
|
177
|
+
assert len(res.payload) == header.payload_length
|
|
178
|
+
return res.payload.data
|
|
179
|
+
else:
|
|
180
|
+
return b""
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def __raise_connection_failure():
|
|
184
|
+
raise ObjectStorageException("connection failure to object storage server.")
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import uuid
|
|
3
|
+
from threading import Lock
|
|
4
|
+
from typing import Iterable, Optional
|
|
5
|
+
|
|
6
|
+
from scaler.io.mixins import SyncObjectStorageConnector
|
|
7
|
+
from scaler.io.ymq.ymq import IOContext, IOSocket, IOSocketType, Message, YMQException
|
|
8
|
+
from scaler.protocol.capnp._python import _object_storage # noqa
|
|
9
|
+
from scaler.protocol.python.object_storage import ObjectRequestHeader, ObjectResponseHeader, to_capnp_object_id
|
|
10
|
+
from scaler.utility.exceptions import ObjectStorageException
|
|
11
|
+
from scaler.utility.identifiers import ObjectID
|
|
12
|
+
|
|
13
|
+
# Some OSes raise an OSError when sending buffers too large with send() or sendmsg().
|
|
14
|
+
MAX_CHUNK_SIZE = 128 * 1024 * 1024
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PyYMQSyncObjectStorageConnector(SyncObjectStorageConnector):
|
|
18
|
+
"""An synchronous connector that uses an raw TCP socket to connect to a Scaler's object storage instance."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, host: str, port: int):
|
|
21
|
+
self._host = host
|
|
22
|
+
self._port = port
|
|
23
|
+
|
|
24
|
+
self._next_request_id = 0
|
|
25
|
+
|
|
26
|
+
self._socket_lock = Lock()
|
|
27
|
+
|
|
28
|
+
self._identity: str = f"{self.__class__.__name__}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}"
|
|
29
|
+
self._io_context = IOContext()
|
|
30
|
+
self._io_socket: IOSocket = self._io_context.createIOSocket_sync(self._identity, IOSocketType.Connector)
|
|
31
|
+
self._io_socket.connect_sync(self.address)
|
|
32
|
+
|
|
33
|
+
def __del__(self):
|
|
34
|
+
self.destroy()
|
|
35
|
+
|
|
36
|
+
def destroy(self):
|
|
37
|
+
with self._socket_lock:
|
|
38
|
+
if self._io_socket is not None:
|
|
39
|
+
self._io_socket = None
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def address(self) -> str:
|
|
43
|
+
return f"tcp://{self._host}:{self._port}"
|
|
44
|
+
|
|
45
|
+
def set_object(self, object_id: ObjectID, payload: bytes):
|
|
46
|
+
"""
|
|
47
|
+
Sets the object's payload on the object storage server.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
with self._socket_lock:
|
|
51
|
+
self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
|
|
52
|
+
response_header, response_payload = self.__receive_response()
|
|
53
|
+
|
|
54
|
+
self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.SetOK])
|
|
55
|
+
self.__ensure_empty_payload(response_payload)
|
|
56
|
+
|
|
57
|
+
def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytearray:
|
|
58
|
+
"""
|
|
59
|
+
Returns the object's payload from the object storage server.
|
|
60
|
+
|
|
61
|
+
Will block until the object is available.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
with self._socket_lock:
|
|
65
|
+
self.__send_request(object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject)
|
|
66
|
+
response_header, response_payload = self.__receive_response()
|
|
67
|
+
|
|
68
|
+
self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.GetOK])
|
|
69
|
+
|
|
70
|
+
return response_payload
|
|
71
|
+
|
|
72
|
+
def delete_object(self, object_id: ObjectID) -> bool:
|
|
73
|
+
"""
|
|
74
|
+
Removes the object from the object storage server.
|
|
75
|
+
|
|
76
|
+
Returns `False` if the object wasn't found in the server. Otherwise returns `True`.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
with self._socket_lock:
|
|
80
|
+
self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject)
|
|
81
|
+
response_header, response_payload = self.__receive_response()
|
|
82
|
+
|
|
83
|
+
self.__ensure_response_type(
|
|
84
|
+
response_header,
|
|
85
|
+
[ObjectResponseHeader.ObjectResponseType.DelOK, ObjectResponseHeader.ObjectResponseType.DelNotExists],
|
|
86
|
+
)
|
|
87
|
+
self.__ensure_empty_payload(response_payload)
|
|
88
|
+
|
|
89
|
+
return response_header.response_type == ObjectResponseHeader.ObjectResponseType.DelOK
|
|
90
|
+
|
|
91
|
+
def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Link an object's content to a new object ID on the object storage server.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
object_id_payload = to_capnp_object_id(object_id).to_bytes()
|
|
97
|
+
|
|
98
|
+
with self._socket_lock:
|
|
99
|
+
self.__send_request(
|
|
100
|
+
new_object_id,
|
|
101
|
+
len(object_id_payload),
|
|
102
|
+
ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
|
|
103
|
+
object_id_payload,
|
|
104
|
+
)
|
|
105
|
+
response_header, response_payload = self.__receive_response()
|
|
106
|
+
|
|
107
|
+
self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.DuplicateOK])
|
|
108
|
+
self.__ensure_empty_payload(response_payload)
|
|
109
|
+
|
|
110
|
+
def __ensure_is_connected(self):
|
|
111
|
+
if self._io_socket is None:
|
|
112
|
+
raise ObjectStorageException("connector is closed.")
|
|
113
|
+
|
|
114
|
+
def __ensure_response_type(
|
|
115
|
+
self, header: ObjectResponseHeader, valid_response_types: Iterable[ObjectResponseHeader.ObjectResponseType]
|
|
116
|
+
):
|
|
117
|
+
if header.response_type not in valid_response_types:
|
|
118
|
+
raise RuntimeError(f"unexpected object storage response_type={header.response_type}.")
|
|
119
|
+
|
|
120
|
+
def __ensure_empty_payload(self, payload: bytearray):
|
|
121
|
+
if len(payload) != 0:
|
|
122
|
+
raise RuntimeError(f"unexpected response payload_length={len(payload)}, expected 0.")
|
|
123
|
+
|
|
124
|
+
def __send_request(
|
|
125
|
+
self,
|
|
126
|
+
object_id: ObjectID,
|
|
127
|
+
payload_length: int,
|
|
128
|
+
request_type: ObjectRequestHeader.ObjectRequestType,
|
|
129
|
+
payload: Optional[bytes] = None,
|
|
130
|
+
):
|
|
131
|
+
self.__ensure_is_connected()
|
|
132
|
+
assert self._io_socket is not None
|
|
133
|
+
|
|
134
|
+
request_id = self._next_request_id
|
|
135
|
+
self._next_request_id += 1
|
|
136
|
+
self._next_request_id %= 2**64 - 1 # UINT64_MAX
|
|
137
|
+
|
|
138
|
+
header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
|
|
139
|
+
header_bytes = header.get_message().to_bytes()
|
|
140
|
+
|
|
141
|
+
if payload is not None:
|
|
142
|
+
self._io_socket.send_sync(Message(address=None, payload=header_bytes))
|
|
143
|
+
self._io_socket.send_sync(Message(address=None, payload=payload))
|
|
144
|
+
else:
|
|
145
|
+
self._io_socket.send_sync(Message(address=None, payload=header_bytes))
|
|
146
|
+
|
|
147
|
+
def __receive_response(self):
|
|
148
|
+
assert self._io_socket is not None
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
header = self.__read_response_header()
|
|
152
|
+
payload = self.__read_response_payload(header)
|
|
153
|
+
return header, payload
|
|
154
|
+
except YMQException:
|
|
155
|
+
self.__raise_connection_failure()
|
|
156
|
+
|
|
157
|
+
def __read_response_header(self) -> ObjectResponseHeader:
|
|
158
|
+
assert self._io_socket is not None
|
|
159
|
+
|
|
160
|
+
header_bytes = self._io_socket.recv_sync().payload.data
|
|
161
|
+
if header_bytes is None:
|
|
162
|
+
self.__raise_connection_failure()
|
|
163
|
+
|
|
164
|
+
# pycapnp does not like to read from a bytearray object. This look like an not-yet-resolved issue.
|
|
165
|
+
# That's is annoying because it leads to an unnecessary copy of the header's buffer.
|
|
166
|
+
# See https://github.com/capnproto/pycapnp/issues/153
|
|
167
|
+
# header_bytes = bytes(header_bytearray)
|
|
168
|
+
|
|
169
|
+
with _object_storage.ObjectResponseHeader.from_bytes(header_bytes) as header_message:
|
|
170
|
+
return ObjectResponseHeader(header_message)
|
|
171
|
+
|
|
172
|
+
def __read_response_payload(self, header: ObjectResponseHeader) -> bytearray:
|
|
173
|
+
if header.payload_length > 0:
|
|
174
|
+
res = self._io_socket.recv_sync().payload.data
|
|
175
|
+
if res is None:
|
|
176
|
+
self.__raise_connection_failure()
|
|
177
|
+
assert len(res) == header.payload_length
|
|
178
|
+
return bytearray(res)
|
|
179
|
+
else:
|
|
180
|
+
return bytearray()
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def __raise_connection_failure():
|
|
184
|
+
raise ObjectStorageException("connection failure to object storage server.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import capnp # noqa
|
|
2
|
+
|
|
3
|
+
import scaler.protocol.capnp.common_capnp as _common # noqa
|
|
4
|
+
import scaler.protocol.capnp.message_capnp as _message # noqa
|
|
5
|
+
import scaler.protocol.capnp.object_storage_capnp as _object_storage # noqa
|
|
6
|
+
import scaler.protocol.capnp.status_capnp as _status # noqa
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
@0xf57f79ac88fab620;
|
|
2
|
+
|
|
3
|
+
enum TaskResultType {
|
|
4
|
+
success @0; # if submit and task is done and get result
|
|
5
|
+
failed @1; # if submit and task is failed on worker
|
|
6
|
+
failedWorkerDied @2; # if submit and worker died (only happened when scheduler keep_task=False)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
enum TaskCancelConfirmType {
|
|
10
|
+
canceled @0; # if cancel success
|
|
11
|
+
cancelFailed @1; # if cancel failed, this might happened if the task is in process
|
|
12
|
+
cancelNotFound @2; # if cancel cannot find such task
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
enum TaskTransition {
|
|
16
|
+
hasCapacity @0;
|
|
17
|
+
taskResultSuccess @1;
|
|
18
|
+
taskResultFailed @2;
|
|
19
|
+
taskResultWorkerDied @3;
|
|
20
|
+
taskCancel @4;
|
|
21
|
+
taskCancelConfirmCanceled @5;
|
|
22
|
+
taskCancelConfirmFailed @6;
|
|
23
|
+
taskCancelConfirmNotFound @7;
|
|
24
|
+
balanceTaskCancel @8;
|
|
25
|
+
workerDisconnect @9;
|
|
26
|
+
schedulerHasTask @10;
|
|
27
|
+
schedulerHasNoTask @11;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
enum TaskState {
|
|
31
|
+
inactive @0;
|
|
32
|
+
running @1;
|
|
33
|
+
canceling @2;
|
|
34
|
+
balanceCanceling @3;
|
|
35
|
+
success @4;
|
|
36
|
+
failed @5;
|
|
37
|
+
failedWorkerDied @6;
|
|
38
|
+
canceled @7;
|
|
39
|
+
canceledNotFound @8;
|
|
40
|
+
balanceCanceled @9;
|
|
41
|
+
workerDisconnecting @10;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
enum WorkerState {
|
|
45
|
+
connected @0;
|
|
46
|
+
disconnected @1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
struct TaskCapability {
|
|
50
|
+
name @0 :Text; # the name of the capability provided by the worker/required by the task (e.g. "gpu" or "linux")
|
|
51
|
+
value @1 :Int64; # the quantity of the capability provided/required. Use -1 for quantity-less capabilities
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
struct ObjectMetadata {
|
|
55
|
+
objectIds @0 :List(Data);
|
|
56
|
+
objectTypes @1 :List(ObjectContentType);
|
|
57
|
+
objectNames @2 :List(Data);
|
|
58
|
+
|
|
59
|
+
enum ObjectContentType {
|
|
60
|
+
serializer @0;
|
|
61
|
+
object @1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
struct ObjectStorageAddress {
|
|
66
|
+
host @0 :Text;
|
|
67
|
+
port @1 :UInt16;
|
|
68
|
+
}
|