opengris-scaler 1.12.7__cp38-cp38-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 (234) hide show
  1. opengris_scaler-1.12.7.dist-info/METADATA +729 -0
  2. opengris_scaler-1.12.7.dist-info/RECORD +234 -0
  3. opengris_scaler-1.12.7.dist-info/WHEEL +5 -0
  4. opengris_scaler-1.12.7.dist-info/entry_points.txt +9 -0
  5. opengris_scaler-1.12.7.dist-info/licenses/LICENSE +201 -0
  6. opengris_scaler-1.12.7.dist-info/licenses/LICENSE.spdx +7 -0
  7. opengris_scaler-1.12.7.dist-info/licenses/NOTICE +8 -0
  8. opengris_scaler.libs/libcapnp-1-61c06778.1.0.so +0 -0
  9. opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
  10. opengris_scaler.libs/libkj-1-21b63b70.1.0.so +0 -0
  11. opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
  12. scaler/CMakeLists.txt +11 -0
  13. scaler/__init__.py +14 -0
  14. scaler/about.py +5 -0
  15. scaler/client/__init__.py +0 -0
  16. scaler/client/agent/__init__.py +0 -0
  17. scaler/client/agent/client_agent.py +210 -0
  18. scaler/client/agent/disconnect_manager.py +27 -0
  19. scaler/client/agent/future_manager.py +112 -0
  20. scaler/client/agent/heartbeat_manager.py +74 -0
  21. scaler/client/agent/mixins.py +89 -0
  22. scaler/client/agent/object_manager.py +98 -0
  23. scaler/client/agent/task_manager.py +64 -0
  24. scaler/client/client.py +635 -0
  25. scaler/client/future.py +252 -0
  26. scaler/client/object_buffer.py +129 -0
  27. scaler/client/object_reference.py +25 -0
  28. scaler/client/serializer/__init__.py +0 -0
  29. scaler/client/serializer/default.py +16 -0
  30. scaler/client/serializer/mixins.py +38 -0
  31. scaler/cluster/__init__.py +0 -0
  32. scaler/cluster/cluster.py +115 -0
  33. scaler/cluster/combo.py +148 -0
  34. scaler/cluster/object_storage_server.py +45 -0
  35. scaler/cluster/scheduler.py +83 -0
  36. scaler/config/__init__.py +0 -0
  37. scaler/config/defaults.py +87 -0
  38. scaler/config/loader.py +95 -0
  39. scaler/config/mixins.py +15 -0
  40. scaler/config/section/__init__.py +0 -0
  41. scaler/config/section/cluster.py +56 -0
  42. scaler/config/section/native_worker_adapter.py +44 -0
  43. scaler/config/section/object_storage_server.py +7 -0
  44. scaler/config/section/scheduler.py +53 -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 +16 -0
  48. scaler/config/types/__init__.py +0 -0
  49. scaler/config/types/object_storage_server.py +45 -0
  50. scaler/config/types/worker.py +57 -0
  51. scaler/config/types/zmq.py +79 -0
  52. scaler/entry_points/__init__.py +0 -0
  53. scaler/entry_points/cluster.py +133 -0
  54. scaler/entry_points/object_storage_server.py +41 -0
  55. scaler/entry_points/scheduler.py +135 -0
  56. scaler/entry_points/top.py +286 -0
  57. scaler/entry_points/webui.py +26 -0
  58. scaler/entry_points/worker_adapter_native.py +137 -0
  59. scaler/entry_points/worker_adapter_symphony.py +102 -0
  60. scaler/io/__init__.py +0 -0
  61. scaler/io/async_binder.py +85 -0
  62. scaler/io/async_connector.py +95 -0
  63. scaler/io/async_object_storage_connector.py +185 -0
  64. scaler/io/mixins.py +154 -0
  65. scaler/io/sync_connector.py +68 -0
  66. scaler/io/sync_object_storage_connector.py +185 -0
  67. scaler/io/sync_subscriber.py +83 -0
  68. scaler/io/utility.py +31 -0
  69. scaler/io/ymq/CMakeLists.txt +98 -0
  70. scaler/io/ymq/__init__.py +0 -0
  71. scaler/io/ymq/_ymq.pyi +96 -0
  72. scaler/io/ymq/_ymq.so +0 -0
  73. scaler/io/ymq/bytes.h +114 -0
  74. scaler/io/ymq/common.h +29 -0
  75. scaler/io/ymq/configuration.h +60 -0
  76. scaler/io/ymq/epoll_context.cpp +185 -0
  77. scaler/io/ymq/epoll_context.h +85 -0
  78. scaler/io/ymq/error.h +132 -0
  79. scaler/io/ymq/event_loop.h +55 -0
  80. scaler/io/ymq/event_loop_thread.cpp +64 -0
  81. scaler/io/ymq/event_loop_thread.h +46 -0
  82. scaler/io/ymq/event_manager.h +81 -0
  83. scaler/io/ymq/file_descriptor.h +203 -0
  84. scaler/io/ymq/interruptive_concurrent_queue.h +169 -0
  85. scaler/io/ymq/io_context.cpp +98 -0
  86. scaler/io/ymq/io_context.h +44 -0
  87. scaler/io/ymq/io_socket.cpp +299 -0
  88. scaler/io/ymq/io_socket.h +121 -0
  89. scaler/io/ymq/iocp_context.cpp +102 -0
  90. scaler/io/ymq/iocp_context.h +83 -0
  91. scaler/io/ymq/logging.h +163 -0
  92. scaler/io/ymq/message.h +15 -0
  93. scaler/io/ymq/message_connection.h +16 -0
  94. scaler/io/ymq/message_connection_tcp.cpp +672 -0
  95. scaler/io/ymq/message_connection_tcp.h +96 -0
  96. scaler/io/ymq/network_utils.h +179 -0
  97. scaler/io/ymq/pymod_ymq/bytes.h +113 -0
  98. scaler/io/ymq/pymod_ymq/exception.h +124 -0
  99. scaler/io/ymq/pymod_ymq/gil.h +15 -0
  100. scaler/io/ymq/pymod_ymq/io_context.h +166 -0
  101. scaler/io/ymq/pymod_ymq/io_socket.h +285 -0
  102. scaler/io/ymq/pymod_ymq/message.h +99 -0
  103. scaler/io/ymq/pymod_ymq/python.h +153 -0
  104. scaler/io/ymq/pymod_ymq/ymq.cpp +23 -0
  105. scaler/io/ymq/pymod_ymq/ymq.h +357 -0
  106. scaler/io/ymq/readme.md +114 -0
  107. scaler/io/ymq/simple_interface.cpp +80 -0
  108. scaler/io/ymq/simple_interface.h +24 -0
  109. scaler/io/ymq/tcp_client.cpp +367 -0
  110. scaler/io/ymq/tcp_client.h +75 -0
  111. scaler/io/ymq/tcp_operations.h +41 -0
  112. scaler/io/ymq/tcp_server.cpp +410 -0
  113. scaler/io/ymq/tcp_server.h +79 -0
  114. scaler/io/ymq/third_party/concurrentqueue.h +3747 -0
  115. scaler/io/ymq/timed_queue.h +272 -0
  116. scaler/io/ymq/timestamp.h +102 -0
  117. scaler/io/ymq/typedefs.h +20 -0
  118. scaler/io/ymq/utils.h +34 -0
  119. scaler/io/ymq/ymq.py +130 -0
  120. scaler/object_storage/CMakeLists.txt +50 -0
  121. scaler/object_storage/__init__.py +0 -0
  122. scaler/object_storage/constants.h +11 -0
  123. scaler/object_storage/defs.h +14 -0
  124. scaler/object_storage/io_helper.cpp +44 -0
  125. scaler/object_storage/io_helper.h +9 -0
  126. scaler/object_storage/message.cpp +56 -0
  127. scaler/object_storage/message.h +130 -0
  128. scaler/object_storage/object_manager.cpp +126 -0
  129. scaler/object_storage/object_manager.h +52 -0
  130. scaler/object_storage/object_storage_server.cpp +359 -0
  131. scaler/object_storage/object_storage_server.h +126 -0
  132. scaler/object_storage/object_storage_server.so +0 -0
  133. scaler/object_storage/pymod_object_storage_server.cpp +104 -0
  134. scaler/protocol/__init__.py +0 -0
  135. scaler/protocol/capnp/__init__.py +0 -0
  136. scaler/protocol/capnp/_python.py +6 -0
  137. scaler/protocol/capnp/common.capnp +63 -0
  138. scaler/protocol/capnp/message.capnp +216 -0
  139. scaler/protocol/capnp/object_storage.capnp +52 -0
  140. scaler/protocol/capnp/status.capnp +73 -0
  141. scaler/protocol/introduction.md +105 -0
  142. scaler/protocol/python/__init__.py +0 -0
  143. scaler/protocol/python/common.py +135 -0
  144. scaler/protocol/python/message.py +726 -0
  145. scaler/protocol/python/mixins.py +13 -0
  146. scaler/protocol/python/object_storage.py +118 -0
  147. scaler/protocol/python/status.py +279 -0
  148. scaler/protocol/worker.md +228 -0
  149. scaler/scheduler/__init__.py +0 -0
  150. scaler/scheduler/allocate_policy/__init__.py +0 -0
  151. scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
  152. scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
  153. scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
  154. scaler/scheduler/allocate_policy/mixins.py +55 -0
  155. scaler/scheduler/controllers/__init__.py +0 -0
  156. scaler/scheduler/controllers/balance_controller.py +65 -0
  157. scaler/scheduler/controllers/client_controller.py +131 -0
  158. scaler/scheduler/controllers/config_controller.py +31 -0
  159. scaler/scheduler/controllers/graph_controller.py +424 -0
  160. scaler/scheduler/controllers/information_controller.py +81 -0
  161. scaler/scheduler/controllers/mixins.py +201 -0
  162. scaler/scheduler/controllers/object_controller.py +147 -0
  163. scaler/scheduler/controllers/scaling_controller.py +86 -0
  164. scaler/scheduler/controllers/task_controller.py +373 -0
  165. scaler/scheduler/controllers/worker_controller.py +168 -0
  166. scaler/scheduler/object_usage/__init__.py +0 -0
  167. scaler/scheduler/object_usage/object_tracker.py +131 -0
  168. scaler/scheduler/scheduler.py +253 -0
  169. scaler/scheduler/task/__init__.py +0 -0
  170. scaler/scheduler/task/task_state_machine.py +92 -0
  171. scaler/scheduler/task/task_state_manager.py +61 -0
  172. scaler/ui/__init__.py +0 -0
  173. scaler/ui/constants.py +9 -0
  174. scaler/ui/live_display.py +118 -0
  175. scaler/ui/memory_window.py +146 -0
  176. scaler/ui/setting_page.py +47 -0
  177. scaler/ui/task_graph.py +370 -0
  178. scaler/ui/task_log.py +83 -0
  179. scaler/ui/utility.py +35 -0
  180. scaler/ui/webui.py +125 -0
  181. scaler/ui/worker_processors.py +85 -0
  182. scaler/utility/__init__.py +0 -0
  183. scaler/utility/debug.py +19 -0
  184. scaler/utility/event_list.py +63 -0
  185. scaler/utility/event_loop.py +58 -0
  186. scaler/utility/exceptions.py +42 -0
  187. scaler/utility/formatter.py +44 -0
  188. scaler/utility/graph/__init__.py +0 -0
  189. scaler/utility/graph/optimization.py +27 -0
  190. scaler/utility/graph/topological_sorter.py +11 -0
  191. scaler/utility/graph/topological_sorter_graphblas.py +174 -0
  192. scaler/utility/identifiers.py +105 -0
  193. scaler/utility/logging/__init__.py +0 -0
  194. scaler/utility/logging/decorators.py +25 -0
  195. scaler/utility/logging/scoped_logger.py +33 -0
  196. scaler/utility/logging/utility.py +183 -0
  197. scaler/utility/many_to_many_dict.py +123 -0
  198. scaler/utility/metadata/__init__.py +0 -0
  199. scaler/utility/metadata/profile_result.py +31 -0
  200. scaler/utility/metadata/task_flags.py +30 -0
  201. scaler/utility/mixins.py +13 -0
  202. scaler/utility/network_util.py +7 -0
  203. scaler/utility/one_to_many_dict.py +72 -0
  204. scaler/utility/queues/__init__.py +0 -0
  205. scaler/utility/queues/async_indexed_queue.py +37 -0
  206. scaler/utility/queues/async_priority_queue.py +70 -0
  207. scaler/utility/queues/async_sorted_priority_queue.py +45 -0
  208. scaler/utility/queues/indexed_queue.py +114 -0
  209. scaler/utility/serialization.py +9 -0
  210. scaler/version.txt +1 -0
  211. scaler/worker/__init__.py +0 -0
  212. scaler/worker/agent/__init__.py +0 -0
  213. scaler/worker/agent/heartbeat_manager.py +107 -0
  214. scaler/worker/agent/mixins.py +137 -0
  215. scaler/worker/agent/processor/__init__.py +0 -0
  216. scaler/worker/agent/processor/object_cache.py +107 -0
  217. scaler/worker/agent/processor/processor.py +279 -0
  218. scaler/worker/agent/processor/streaming_buffer.py +28 -0
  219. scaler/worker/agent/processor_holder.py +145 -0
  220. scaler/worker/agent/processor_manager.py +365 -0
  221. scaler/worker/agent/profiling_manager.py +109 -0
  222. scaler/worker/agent/task_manager.py +150 -0
  223. scaler/worker/agent/timeout_manager.py +19 -0
  224. scaler/worker/preload.py +84 -0
  225. scaler/worker/worker.py +264 -0
  226. scaler/worker_adapter/__init__.py +0 -0
  227. scaler/worker_adapter/native.py +154 -0
  228. scaler/worker_adapter/symphony/__init__.py +0 -0
  229. scaler/worker_adapter/symphony/callback.py +45 -0
  230. scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
  231. scaler/worker_adapter/symphony/message.py +24 -0
  232. scaler/worker_adapter/symphony/task_manager.py +288 -0
  233. scaler/worker_adapter/symphony/worker.py +205 -0
  234. scaler/worker_adapter/symphony/worker_adapter.py +142 -0
@@ -0,0 +1,95 @@
1
+ import logging
2
+ import os
3
+ import uuid
4
+ from typing import Awaitable, Callable, Literal, Optional
5
+
6
+ import zmq.asyncio
7
+
8
+ from scaler.io.mixins import AsyncConnector
9
+ from scaler.io.utility import deserialize, serialize
10
+ from scaler.protocol.python.mixins import Message
11
+ from scaler.config.types.zmq import ZMQConfig
12
+
13
+
14
+ class ZMQAsyncConnector(AsyncConnector):
15
+ def __init__(
16
+ self,
17
+ context: zmq.asyncio.Context,
18
+ name: str,
19
+ socket_type: int,
20
+ address: ZMQConfig,
21
+ bind_or_connect: Literal["bind", "connect"],
22
+ callback: Optional[Callable[[Message], Awaitable[None]]],
23
+ identity: Optional[bytes],
24
+ ):
25
+ self._address = address
26
+
27
+ self._context = context
28
+ self._socket = self._context.socket(socket_type)
29
+
30
+ if identity is None:
31
+ identity = f"{os.getpid()}|{name}|{uuid.uuid4().bytes.hex()}".encode()
32
+ self._identity = identity
33
+
34
+ # set socket option
35
+ self._socket.setsockopt(zmq.IDENTITY, self._identity)
36
+ self._socket.setsockopt(zmq.SNDHWM, 0)
37
+ self._socket.setsockopt(zmq.RCVHWM, 0)
38
+
39
+ if bind_or_connect == "bind":
40
+ self._socket.bind(self.address)
41
+ elif bind_or_connect == "connect":
42
+ self._socket.connect(self.address)
43
+ else:
44
+ raise TypeError("bind_or_connect has to be 'bind' or 'connect'")
45
+
46
+ self._callback: Optional[Callable[[Message], Awaitable[None]]] = callback
47
+
48
+ def __del__(self):
49
+ self.destroy()
50
+
51
+ def destroy(self):
52
+ if self._socket.closed:
53
+ return
54
+
55
+ self._socket.close(linger=1)
56
+
57
+ @property
58
+ def identity(self) -> bytes:
59
+ return self._identity
60
+
61
+ @property
62
+ def socket(self) -> zmq.asyncio.Socket:
63
+ return self._socket
64
+
65
+ @property
66
+ def address(self) -> str:
67
+ return self._address.to_address()
68
+
69
+ async def routine(self):
70
+ if self._callback is None:
71
+ return
72
+
73
+ message: Optional[Message] = await self.receive()
74
+ if message is None:
75
+ return
76
+
77
+ await self._callback(message)
78
+
79
+ async def receive(self) -> Optional[Message]:
80
+ if self._context.closed:
81
+ return None
82
+
83
+ if self._socket.closed:
84
+ return None
85
+
86
+ payload = await self._socket.recv(copy=False)
87
+ result: Optional[Message] = deserialize(payload.bytes)
88
+ if result is None:
89
+ logging.error(f"received unknown message: {payload.bytes!r}")
90
+ return None
91
+
92
+ return result
93
+
94
+ async def send(self, message: Message):
95
+ await self._socket.send(serialize(message), copy=False)
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import socket
5
+ import uuid
6
+ from typing import Dict, Optional, Tuple
7
+
8
+ from scaler.io.mixins import AsyncObjectStorageConnector
9
+ from scaler.io.ymq.ymq import IOSocketType, IOContext, Message, YMQException
10
+ from scaler.protocol.capnp._python import _object_storage # noqa
11
+ from scaler.protocol.python.object_storage import ObjectRequestHeader, ObjectResponseHeader, to_capnp_object_id
12
+ from scaler.utility.exceptions import ObjectStorageException
13
+ from scaler.utility.identifiers import ObjectID
14
+
15
+
16
+ class PyAsyncObjectStorageConnector(AsyncObjectStorageConnector):
17
+ """An asyncio connector that uses an raw TCP socket to connect to a Scaler's object storage instance."""
18
+
19
+ def __init__(self):
20
+ self._host: Optional[str] = None
21
+ self._port: Optional[int] = None
22
+
23
+ self._connected_event = asyncio.Event()
24
+
25
+ self._next_request_id = 0
26
+ self._pending_get_requests: Dict[ObjectID, asyncio.Future] = {}
27
+
28
+ self._lock = asyncio.Lock()
29
+ self._identity: str = f"{os.getpid()}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}"
30
+ self._io_context: IOContext = IOContext()
31
+ self._io_socket = self._io_context.createIOSocket_sync(self._identity, IOSocketType.Connector)
32
+
33
+ def __del__(self):
34
+ if not self.is_connected():
35
+ return
36
+ self._io_socket = None
37
+
38
+ async def connect(self, host: str, port: int):
39
+ self._host = host
40
+ self._port = port
41
+
42
+ if self.is_connected():
43
+ raise ObjectStorageException("connector is already connected.")
44
+ await self._io_socket.connect(self.address)
45
+ self._connected_event.set()
46
+
47
+ async def wait_until_connected(self):
48
+ await self._connected_event.wait()
49
+
50
+ def is_connected(self) -> bool:
51
+ return self._connected_event.is_set()
52
+
53
+ async def destroy(self):
54
+ if not self.is_connected():
55
+ return
56
+ self._io_socket = None
57
+
58
+ @property
59
+ def address(self) -> str:
60
+ return f"tcp://{self._host}:{self._port}"
61
+
62
+ async def routine(self):
63
+ await self.wait_until_connected()
64
+
65
+ response = await self.__receive_response()
66
+ if response is None:
67
+ return
68
+
69
+ header, payload = response
70
+
71
+ if header.response_type != ObjectResponseHeader.ObjectResponseType.GetOK:
72
+ return
73
+
74
+ pending_get_future = self._pending_get_requests.pop(header.object_id, None)
75
+
76
+ if pending_get_future is None:
77
+ logging.warning(f"unknown get-ok response for unrequested object_id={repr(header.object_id)}.")
78
+ return
79
+
80
+ pending_get_future.set_result(payload)
81
+
82
+ async def set_object(self, object_id: ObjectID, payload: bytes) -> None:
83
+ await self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
84
+
85
+ async def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytes:
86
+ pending_get_future = self._pending_get_requests.get(object_id)
87
+
88
+ if pending_get_future is None:
89
+ pending_get_future = asyncio.Future()
90
+ self._pending_get_requests[object_id] = pending_get_future
91
+
92
+ await self.__send_request(
93
+ object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject, None
94
+ )
95
+
96
+ return await pending_get_future
97
+
98
+ async def delete_object(self, object_id: ObjectID) -> None:
99
+ await self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject, None)
100
+
101
+ async def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
102
+ object_id_payload = to_capnp_object_id(object_id).to_bytes()
103
+
104
+ await self.__send_request(
105
+ new_object_id,
106
+ len(object_id_payload),
107
+ ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
108
+ object_id_payload,
109
+ )
110
+
111
+ def __ensure_is_connected(self):
112
+ if self._io_socket is None:
113
+ raise ObjectStorageException("connector is not connected.")
114
+
115
+ async def __send_request(
116
+ self,
117
+ object_id: ObjectID,
118
+ payload_length: int,
119
+ request_type: ObjectRequestHeader.ObjectRequestType,
120
+ payload: Optional[bytes],
121
+ ):
122
+ self.__ensure_is_connected()
123
+
124
+ request_id = self._next_request_id
125
+ self._next_request_id += 1
126
+ self._next_request_id %= 2**64 - 1 # UINT64_MAX
127
+
128
+ header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
129
+
130
+ try:
131
+ async with self._lock:
132
+ await self.__write_request_header(header)
133
+
134
+ if payload is not None:
135
+ await self.__write_request_payload(payload)
136
+
137
+ except YMQException:
138
+ self._io_socket = None
139
+ self.__raise_connection_failure()
140
+
141
+ async def __write_request_header(self, header: ObjectRequestHeader):
142
+ assert self._io_socket is not None
143
+ await self._io_socket.send(Message(address=None, payload=header.get_message().to_bytes()))
144
+
145
+ async def __write_request_payload(self, payload: bytes):
146
+ assert self._io_socket is not None
147
+ await self._io_socket.send(Message(address=None, payload=payload))
148
+
149
+ async def __receive_response(self) -> Optional[Tuple[ObjectResponseHeader, bytes]]:
150
+ if self._io_socket is None:
151
+ return None
152
+
153
+ try:
154
+ header = await self.__read_response_header()
155
+ payload = await self.__read_response_payload(header)
156
+ except YMQException:
157
+ self._io_socket = None
158
+ self.__raise_connection_failure()
159
+
160
+ return header, payload
161
+
162
+ async def __read_response_header(self) -> ObjectResponseHeader:
163
+ assert self._io_socket is not None
164
+
165
+ msg = await self._io_socket.recv()
166
+ header_data = msg.payload.data
167
+ assert len(header_data) == ObjectResponseHeader.MESSAGE_LENGTH
168
+
169
+ with _object_storage.ObjectResponseHeader.from_bytes(header_data) as header_message:
170
+ return ObjectResponseHeader(header_message)
171
+
172
+ async def __read_response_payload(self, header: ObjectResponseHeader) -> bytes:
173
+ assert self._io_socket is not None
174
+ # assert self._reader is not None
175
+
176
+ if header.payload_length > 0:
177
+ res = await self._io_socket.recv()
178
+ assert len(res.payload) == header.payload_length
179
+ return res.payload.data
180
+ else:
181
+ return b""
182
+
183
+ @staticmethod
184
+ def __raise_connection_failure():
185
+ raise ObjectStorageException("connection failure to object storage server.")
scaler/io/mixins.py ADDED
@@ -0,0 +1,154 @@
1
+ import abc
2
+ from typing import Awaitable, Callable, Optional
3
+
4
+ from scaler.protocol.python.mixins import Message
5
+ from scaler.protocol.python.status import BinderStatus
6
+ from scaler.utility.identifiers import ObjectID
7
+ from scaler.utility.mixins import Looper, Reporter
8
+
9
+
10
+ class AsyncBinder(Looper, Reporter, metaclass=abc.ABCMeta):
11
+ @property
12
+ @abc.abstractmethod
13
+ def identity(self):
14
+ raise NotImplementedError()
15
+
16
+ @abc.abstractmethod
17
+ def destroy(self):
18
+ raise NotImplementedError()
19
+
20
+ @abc.abstractmethod
21
+ def register(self, callback: Callable[[bytes, Message], Awaitable[None]]):
22
+ raise NotImplementedError()
23
+
24
+ @abc.abstractmethod
25
+ async def send(self, to: bytes, message: Message):
26
+ raise NotImplementedError()
27
+
28
+ @abc.abstractmethod
29
+ def get_status(self) -> BinderStatus:
30
+ raise NotImplementedError()
31
+
32
+
33
+ class AsyncConnector(Looper, metaclass=abc.ABCMeta):
34
+ @abc.abstractmethod
35
+ def destroy(self):
36
+ raise NotImplementedError()
37
+
38
+ @property
39
+ @abc.abstractmethod
40
+ def identity(self) -> bytes:
41
+ raise NotImplementedError()
42
+
43
+ @property
44
+ @abc.abstractmethod
45
+ def address(self) -> str:
46
+ raise NotImplementedError()
47
+
48
+ @abc.abstractmethod
49
+ async def send(self, message: Message):
50
+ raise NotImplementedError()
51
+
52
+ @abc.abstractmethod
53
+ async def receive(self) -> Optional[Message]:
54
+ raise NotImplementedError()
55
+
56
+
57
+ class SyncConnector(metaclass=abc.ABCMeta):
58
+ @abc.abstractmethod
59
+ def destroy(self):
60
+ raise NotImplementedError()
61
+
62
+ @property
63
+ @abc.abstractmethod
64
+ def identity(self) -> bytes:
65
+ raise NotImplementedError()
66
+
67
+ @property
68
+ @abc.abstractmethod
69
+ def address(self) -> str:
70
+ raise NotImplementedError()
71
+
72
+ @abc.abstractmethod
73
+ def send(self, message: Message):
74
+ raise NotImplementedError()
75
+
76
+ @abc.abstractmethod
77
+ def receive(self) -> Optional[Message]:
78
+ raise NotImplementedError()
79
+
80
+
81
+ class AsyncObjectStorageConnector(Looper, metaclass=abc.ABCMeta):
82
+ @abc.abstractmethod
83
+ async def connect(self, host: str, port: int):
84
+ raise NotImplementedError()
85
+
86
+ @abc.abstractmethod
87
+ async def wait_until_connected(self):
88
+ raise NotImplementedError()
89
+
90
+ @abc.abstractmethod
91
+ def is_connected(self) -> bool:
92
+ raise NotImplementedError()
93
+
94
+ @abc.abstractmethod
95
+ async def destroy(self):
96
+ raise NotImplementedError()
97
+
98
+ @property
99
+ @abc.abstractmethod
100
+ def address(self) -> str:
101
+ raise NotImplementedError()
102
+
103
+ @abc.abstractmethod
104
+ async def set_object(self, object_id: ObjectID, payload: bytes) -> None:
105
+ raise NotImplementedError()
106
+
107
+ @abc.abstractmethod
108
+ async def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytes:
109
+ raise NotImplementedError()
110
+
111
+ @abc.abstractmethod
112
+ async def delete_object(self, object_id: ObjectID) -> None:
113
+ raise NotImplementedError()
114
+
115
+ @abc.abstractmethod
116
+ async def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
117
+ raise NotImplementedError()
118
+
119
+
120
+ class SyncObjectStorageConnector(metaclass=abc.ABCMeta):
121
+ @abc.abstractmethod
122
+ def destroy(self):
123
+ raise NotImplementedError()
124
+
125
+ @property
126
+ @abc.abstractmethod
127
+ def address(self) -> str:
128
+ raise NotImplementedError()
129
+
130
+ @abc.abstractmethod
131
+ def set_object(self, object_id: ObjectID, payload: bytes):
132
+ raise NotImplementedError()
133
+
134
+ @abc.abstractmethod
135
+ def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytearray:
136
+ raise NotImplementedError()
137
+
138
+ @abc.abstractmethod
139
+ def delete_object(self, object_id: ObjectID) -> bool:
140
+ raise NotImplementedError()
141
+
142
+ @abc.abstractmethod
143
+ def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
144
+ raise NotImplementedError()
145
+
146
+
147
+ class SyncSubscriber(metaclass=abc.ABCMeta):
148
+ @abc.abstractmethod
149
+ def destroy(self):
150
+ raise NotImplementedError()
151
+
152
+ @abc.abstractmethod
153
+ def run(self) -> None:
154
+ raise NotImplementedError()
@@ -0,0 +1,68 @@
1
+ import logging
2
+ import os
3
+ import socket
4
+ import threading
5
+ import uuid
6
+ from typing import Optional
7
+
8
+ import zmq
9
+
10
+ from scaler.io.mixins import SyncConnector
11
+ from scaler.io.utility import deserialize, serialize
12
+ from scaler.protocol.python.mixins import Message
13
+ from scaler.config.types.zmq import ZMQConfig
14
+
15
+
16
+ class ZMQSyncConnector(SyncConnector):
17
+ def __init__(self, context: zmq.Context, socket_type: int, address: ZMQConfig, identity: Optional[bytes]):
18
+ self._address = address
19
+
20
+ self._context = context
21
+ self._socket = self._context.socket(socket_type)
22
+
23
+ self._identity: bytes = (
24
+ f"{os.getpid()}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}".encode()
25
+ if identity is None
26
+ else identity
27
+ )
28
+
29
+ # set socket option
30
+ self._socket.setsockopt(zmq.IDENTITY, self._identity)
31
+ self._socket.setsockopt(zmq.SNDHWM, 0)
32
+ self._socket.setsockopt(zmq.RCVHWM, 0)
33
+
34
+ self._socket.connect(self._address.to_address())
35
+
36
+ self._lock = threading.Lock()
37
+
38
+ def destroy(self):
39
+ self._socket.close()
40
+
41
+ @property
42
+ def address(self) -> str:
43
+ return self._address.to_address()
44
+
45
+ @property
46
+ def identity(self) -> bytes:
47
+ return self._identity
48
+
49
+ def send(self, message: Message):
50
+ with self._lock:
51
+ self._socket.send(serialize(message), copy=False)
52
+
53
+ def receive(self) -> Optional[Message]:
54
+ with self._lock:
55
+ payload = self._socket.recv(copy=False)
56
+
57
+ return self.__compose_message(payload.bytes)
58
+
59
+ def __compose_message(self, payload: bytes) -> Optional[Message]:
60
+ result: Optional[Message] = deserialize(payload)
61
+ if result is None:
62
+ logging.error(f"{self.__get_prefix()}: received unknown message: {payload!r}")
63
+ return None
64
+
65
+ return result
66
+
67
+ def __get_prefix(self):
68
+ return f"{self.__class__.__name__}[{self._identity.decode()}]:"
@@ -0,0 +1,185 @@
1
+ import os
2
+ import socket
3
+ import uuid
4
+ from threading import Lock
5
+ from typing import Iterable, Optional
6
+
7
+ from scaler.io.mixins import SyncObjectStorageConnector
8
+ from scaler.io.ymq.ymq import IOContext, IOSocket, 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
+ # Some OSes raise an OSError when sending buffers too large with send() or sendmsg().
15
+ MAX_CHUNK_SIZE = 128 * 1024 * 1024
16
+
17
+
18
+ class PySyncObjectStorageConnector(SyncObjectStorageConnector):
19
+ """An synchronous connector that uses an raw TCP socket to connect to a Scaler's object storage instance."""
20
+
21
+ def __init__(self, host: str, port: int):
22
+ self._host = host
23
+ self._port = port
24
+
25
+ self._next_request_id = 0
26
+
27
+ self._socket_lock = Lock()
28
+
29
+ self._identity: str = f"{os.getpid()}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}"
30
+ self._io_context = IOContext()
31
+ self._io_socket: IOSocket = self._io_context.createIOSocket_sync(self._identity, IOSocketType.Connector)
32
+ self._io_socket.connect_sync(self.address)
33
+
34
+ def __del__(self):
35
+ self.destroy()
36
+
37
+ def destroy(self):
38
+ with self._socket_lock:
39
+ if self._io_socket is not None:
40
+ self._io_socket = None
41
+
42
+ @property
43
+ def address(self) -> str:
44
+ return f"tcp://{self._host}:{self._port}"
45
+
46
+ def set_object(self, object_id: ObjectID, payload: bytes):
47
+ """
48
+ Sets the object's payload on the object storage server.
49
+ """
50
+
51
+ with self._socket_lock:
52
+ self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
53
+ response_header, response_payload = self.__receive_response()
54
+
55
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.SetOK])
56
+ self.__ensure_empty_payload(response_payload)
57
+
58
+ def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytearray:
59
+ """
60
+ Returns the object's payload from the object storage server.
61
+
62
+ Will block until the object is available.
63
+ """
64
+
65
+ with self._socket_lock:
66
+ self.__send_request(object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject)
67
+ response_header, response_payload = self.__receive_response()
68
+
69
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.GetOK])
70
+
71
+ return response_payload
72
+
73
+ def delete_object(self, object_id: ObjectID) -> bool:
74
+ """
75
+ Removes the object from the object storage server.
76
+
77
+ Returns `False` if the object wasn't found in the server. Otherwise returns `True`.
78
+ """
79
+
80
+ with self._socket_lock:
81
+ self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject)
82
+ response_header, response_payload = self.__receive_response()
83
+
84
+ self.__ensure_response_type(
85
+ response_header,
86
+ [ObjectResponseHeader.ObjectResponseType.DelOK, ObjectResponseHeader.ObjectResponseType.DelNotExists],
87
+ )
88
+ self.__ensure_empty_payload(response_payload)
89
+
90
+ return response_header.response_type == ObjectResponseHeader.ObjectResponseType.DelOK
91
+
92
+ def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
93
+ """
94
+ Link an object's content to a new object ID on the object storage server.
95
+ """
96
+
97
+ object_id_payload = to_capnp_object_id(object_id).to_bytes()
98
+
99
+ with self._socket_lock:
100
+ self.__send_request(
101
+ new_object_id,
102
+ len(object_id_payload),
103
+ ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
104
+ object_id_payload,
105
+ )
106
+ response_header, response_payload = self.__receive_response()
107
+
108
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.DuplicateOK])
109
+ self.__ensure_empty_payload(response_payload)
110
+
111
+ def __ensure_is_connected(self):
112
+ if self._io_socket is None:
113
+ raise ObjectStorageException("connector is closed.")
114
+
115
+ def __ensure_response_type(
116
+ self, header: ObjectResponseHeader, valid_response_types: Iterable[ObjectResponseHeader.ObjectResponseType]
117
+ ):
118
+ if header.response_type not in valid_response_types:
119
+ raise RuntimeError(f"unexpected object storage response_type={header.response_type}.")
120
+
121
+ def __ensure_empty_payload(self, payload: bytearray):
122
+ if len(payload) != 0:
123
+ raise RuntimeError(f"unexpected response payload_length={len(payload)}, expected 0.")
124
+
125
+ def __send_request(
126
+ self,
127
+ object_id: ObjectID,
128
+ payload_length: int,
129
+ request_type: ObjectRequestHeader.ObjectRequestType,
130
+ payload: Optional[bytes] = None,
131
+ ):
132
+ self.__ensure_is_connected()
133
+ assert self._io_socket is not None
134
+
135
+ request_id = self._next_request_id
136
+ self._next_request_id += 1
137
+ self._next_request_id %= 2**64 - 1 # UINT64_MAX
138
+
139
+ header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
140
+ header_bytes = header.get_message().to_bytes()
141
+
142
+ if payload is not None:
143
+ self._io_socket.send_sync(Message(address=None, payload=header_bytes))
144
+ self._io_socket.send_sync(Message(address=None, payload=payload))
145
+ else:
146
+ self._io_socket.send_sync(Message(address=None, payload=header_bytes))
147
+
148
+ def __receive_response(self):
149
+ assert self._io_socket is not None
150
+
151
+ try:
152
+ header = self.__read_response_header()
153
+ payload = self.__read_response_payload(header)
154
+ return header, payload
155
+ except YMQException:
156
+ self.__raise_connection_failure()
157
+
158
+ def __read_response_header(self) -> ObjectResponseHeader:
159
+ assert self._io_socket is not None
160
+
161
+ header_bytes = self._io_socket.recv_sync().payload.data
162
+ if header_bytes is None:
163
+ self.__raise_connection_failure()
164
+
165
+ # pycapnp does not like to read from a bytearray object. This look like an not-yet-resolved issue.
166
+ # That's is annoying because it leads to an unnecessary copy of the header's buffer.
167
+ # See https://github.com/capnproto/pycapnp/issues/153
168
+ # header_bytes = bytes(header_bytearray)
169
+
170
+ with _object_storage.ObjectResponseHeader.from_bytes(header_bytes) as header_message:
171
+ return ObjectResponseHeader(header_message)
172
+
173
+ def __read_response_payload(self, header: ObjectResponseHeader) -> bytearray:
174
+ if header.payload_length > 0:
175
+ res = self._io_socket.recv_sync().payload.data
176
+ if res is None:
177
+ self.__raise_connection_failure()
178
+ assert len(res) == header.payload_length
179
+ return bytearray(res)
180
+ else:
181
+ return bytearray()
182
+
183
+ @staticmethod
184
+ def __raise_connection_failure():
185
+ raise ObjectStorageException("connection failure to object storage server.")