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.

Files changed (187) hide show
  1. opengris_scaler-1.12.28.dist-info/METADATA +728 -0
  2. opengris_scaler-1.12.28.dist-info/RECORD +187 -0
  3. opengris_scaler-1.12.28.dist-info/WHEEL +5 -0
  4. opengris_scaler-1.12.28.dist-info/entry_points.txt +10 -0
  5. opengris_scaler-1.12.28.dist-info/licenses/LICENSE +201 -0
  6. opengris_scaler-1.12.28.dist-info/licenses/LICENSE.spdx +7 -0
  7. opengris_scaler-1.12.28.dist-info/licenses/NOTICE +8 -0
  8. opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
  9. opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
  10. opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
  11. opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
  12. scaler/__init__.py +14 -0
  13. scaler/about.py +5 -0
  14. scaler/client/__init__.py +0 -0
  15. scaler/client/agent/__init__.py +0 -0
  16. scaler/client/agent/client_agent.py +210 -0
  17. scaler/client/agent/disconnect_manager.py +27 -0
  18. scaler/client/agent/future_manager.py +112 -0
  19. scaler/client/agent/heartbeat_manager.py +74 -0
  20. scaler/client/agent/mixins.py +89 -0
  21. scaler/client/agent/object_manager.py +98 -0
  22. scaler/client/agent/task_manager.py +64 -0
  23. scaler/client/client.py +658 -0
  24. scaler/client/future.py +252 -0
  25. scaler/client/object_buffer.py +129 -0
  26. scaler/client/object_reference.py +25 -0
  27. scaler/client/serializer/__init__.py +0 -0
  28. scaler/client/serializer/default.py +16 -0
  29. scaler/client/serializer/mixins.py +38 -0
  30. scaler/cluster/__init__.py +0 -0
  31. scaler/cluster/cluster.py +115 -0
  32. scaler/cluster/combo.py +150 -0
  33. scaler/cluster/object_storage_server.py +45 -0
  34. scaler/cluster/scheduler.py +86 -0
  35. scaler/config/__init__.py +0 -0
  36. scaler/config/defaults.py +94 -0
  37. scaler/config/loader.py +96 -0
  38. scaler/config/mixins.py +20 -0
  39. scaler/config/section/__init__.py +0 -0
  40. scaler/config/section/cluster.py +55 -0
  41. scaler/config/section/ecs_worker_adapter.py +85 -0
  42. scaler/config/section/native_worker_adapter.py +43 -0
  43. scaler/config/section/object_storage_server.py +8 -0
  44. scaler/config/section/scheduler.py +54 -0
  45. scaler/config/section/symphony_worker_adapter.py +47 -0
  46. scaler/config/section/top.py +13 -0
  47. scaler/config/section/webui.py +21 -0
  48. scaler/config/types/__init__.py +0 -0
  49. scaler/config/types/network_backend.py +12 -0
  50. scaler/config/types/object_storage_server.py +45 -0
  51. scaler/config/types/worker.py +62 -0
  52. scaler/config/types/zmq.py +83 -0
  53. scaler/entry_points/__init__.py +0 -0
  54. scaler/entry_points/cluster.py +133 -0
  55. scaler/entry_points/object_storage_server.py +45 -0
  56. scaler/entry_points/scheduler.py +144 -0
  57. scaler/entry_points/top.py +286 -0
  58. scaler/entry_points/webui.py +48 -0
  59. scaler/entry_points/worker_adapter_ecs.py +191 -0
  60. scaler/entry_points/worker_adapter_native.py +137 -0
  61. scaler/entry_points/worker_adapter_symphony.py +98 -0
  62. scaler/io/__init__.py +0 -0
  63. scaler/io/async_binder.py +89 -0
  64. scaler/io/async_connector.py +95 -0
  65. scaler/io/async_object_storage_connector.py +225 -0
  66. scaler/io/mixins.py +154 -0
  67. scaler/io/sync_connector.py +68 -0
  68. scaler/io/sync_object_storage_connector.py +247 -0
  69. scaler/io/sync_subscriber.py +83 -0
  70. scaler/io/utility.py +80 -0
  71. scaler/io/ymq/__init__.py +0 -0
  72. scaler/io/ymq/_ymq.pyi +95 -0
  73. scaler/io/ymq/ymq.py +138 -0
  74. scaler/io/ymq_async_object_storage_connector.py +184 -0
  75. scaler/io/ymq_sync_object_storage_connector.py +184 -0
  76. scaler/object_storage/__init__.py +0 -0
  77. scaler/protocol/__init__.py +0 -0
  78. scaler/protocol/capnp/__init__.py +0 -0
  79. scaler/protocol/capnp/_python.py +6 -0
  80. scaler/protocol/capnp/common.capnp +68 -0
  81. scaler/protocol/capnp/message.capnp +218 -0
  82. scaler/protocol/capnp/object_storage.capnp +57 -0
  83. scaler/protocol/capnp/status.capnp +73 -0
  84. scaler/protocol/introduction.md +105 -0
  85. scaler/protocol/python/__init__.py +0 -0
  86. scaler/protocol/python/common.py +140 -0
  87. scaler/protocol/python/message.py +751 -0
  88. scaler/protocol/python/mixins.py +13 -0
  89. scaler/protocol/python/object_storage.py +118 -0
  90. scaler/protocol/python/status.py +279 -0
  91. scaler/protocol/worker.md +228 -0
  92. scaler/scheduler/__init__.py +0 -0
  93. scaler/scheduler/allocate_policy/__init__.py +0 -0
  94. scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
  95. scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
  96. scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
  97. scaler/scheduler/allocate_policy/mixins.py +55 -0
  98. scaler/scheduler/controllers/__init__.py +0 -0
  99. scaler/scheduler/controllers/balance_controller.py +65 -0
  100. scaler/scheduler/controllers/client_controller.py +131 -0
  101. scaler/scheduler/controllers/config_controller.py +31 -0
  102. scaler/scheduler/controllers/graph_controller.py +424 -0
  103. scaler/scheduler/controllers/information_controller.py +81 -0
  104. scaler/scheduler/controllers/mixins.py +194 -0
  105. scaler/scheduler/controllers/object_controller.py +147 -0
  106. scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
  107. scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
  108. scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
  109. scaler/scheduler/controllers/scaling_policies/null.py +14 -0
  110. scaler/scheduler/controllers/scaling_policies/types.py +9 -0
  111. scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
  112. scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
  113. scaler/scheduler/controllers/task_controller.py +376 -0
  114. scaler/scheduler/controllers/worker_controller.py +169 -0
  115. scaler/scheduler/object_usage/__init__.py +0 -0
  116. scaler/scheduler/object_usage/object_tracker.py +131 -0
  117. scaler/scheduler/scheduler.py +251 -0
  118. scaler/scheduler/task/__init__.py +0 -0
  119. scaler/scheduler/task/task_state_machine.py +92 -0
  120. scaler/scheduler/task/task_state_manager.py +61 -0
  121. scaler/ui/__init__.py +0 -0
  122. scaler/ui/constants.py +9 -0
  123. scaler/ui/live_display.py +147 -0
  124. scaler/ui/memory_window.py +146 -0
  125. scaler/ui/setting_page.py +40 -0
  126. scaler/ui/task_graph.py +832 -0
  127. scaler/ui/task_log.py +107 -0
  128. scaler/ui/utility.py +66 -0
  129. scaler/ui/webui.py +147 -0
  130. scaler/ui/worker_processors.py +104 -0
  131. scaler/utility/__init__.py +0 -0
  132. scaler/utility/debug.py +19 -0
  133. scaler/utility/event_list.py +63 -0
  134. scaler/utility/event_loop.py +58 -0
  135. scaler/utility/exceptions.py +42 -0
  136. scaler/utility/formatter.py +44 -0
  137. scaler/utility/graph/__init__.py +0 -0
  138. scaler/utility/graph/optimization.py +27 -0
  139. scaler/utility/graph/topological_sorter.py +11 -0
  140. scaler/utility/graph/topological_sorter_graphblas.py +174 -0
  141. scaler/utility/identifiers.py +107 -0
  142. scaler/utility/logging/__init__.py +0 -0
  143. scaler/utility/logging/decorators.py +25 -0
  144. scaler/utility/logging/scoped_logger.py +33 -0
  145. scaler/utility/logging/utility.py +183 -0
  146. scaler/utility/many_to_many_dict.py +123 -0
  147. scaler/utility/metadata/__init__.py +0 -0
  148. scaler/utility/metadata/profile_result.py +31 -0
  149. scaler/utility/metadata/task_flags.py +30 -0
  150. scaler/utility/mixins.py +13 -0
  151. scaler/utility/network_util.py +7 -0
  152. scaler/utility/one_to_many_dict.py +72 -0
  153. scaler/utility/queues/__init__.py +0 -0
  154. scaler/utility/queues/async_indexed_queue.py +37 -0
  155. scaler/utility/queues/async_priority_queue.py +70 -0
  156. scaler/utility/queues/async_sorted_priority_queue.py +45 -0
  157. scaler/utility/queues/indexed_queue.py +114 -0
  158. scaler/utility/serialization.py +9 -0
  159. scaler/version.txt +1 -0
  160. scaler/worker/__init__.py +0 -0
  161. scaler/worker/agent/__init__.py +0 -0
  162. scaler/worker/agent/heartbeat_manager.py +107 -0
  163. scaler/worker/agent/mixins.py +137 -0
  164. scaler/worker/agent/processor/__init__.py +0 -0
  165. scaler/worker/agent/processor/object_cache.py +107 -0
  166. scaler/worker/agent/processor/processor.py +285 -0
  167. scaler/worker/agent/processor/streaming_buffer.py +28 -0
  168. scaler/worker/agent/processor_holder.py +147 -0
  169. scaler/worker/agent/processor_manager.py +369 -0
  170. scaler/worker/agent/profiling_manager.py +109 -0
  171. scaler/worker/agent/task_manager.py +150 -0
  172. scaler/worker/agent/timeout_manager.py +19 -0
  173. scaler/worker/preload.py +84 -0
  174. scaler/worker/worker.py +265 -0
  175. scaler/worker_adapter/__init__.py +0 -0
  176. scaler/worker_adapter/common.py +26 -0
  177. scaler/worker_adapter/ecs.py +269 -0
  178. scaler/worker_adapter/native.py +155 -0
  179. scaler/worker_adapter/symphony/__init__.py +0 -0
  180. scaler/worker_adapter/symphony/callback.py +45 -0
  181. scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
  182. scaler/worker_adapter/symphony/message.py +24 -0
  183. scaler/worker_adapter/symphony/task_manager.py +289 -0
  184. scaler/worker_adapter/symphony/worker.py +204 -0
  185. scaler/worker_adapter/symphony/worker_adapter.py +139 -0
  186. src/scaler/io/ymq/_ymq.so +0 -0
  187. 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
+ }