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/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.config.types.zmq import ZMQConfig
11
+ from scaler.io.mixins import SyncConnector
12
+ from scaler.io.utility import deserialize, serialize
13
+ from scaler.protocol.python.mixins import Message
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,247 @@
1
+ import collections
2
+ import socket
3
+ import struct
4
+ import uuid
5
+ from threading import Lock
6
+ from typing import Iterable, List, Optional, Tuple
7
+
8
+ from scaler.io.mixins import SyncObjectStorageConnector
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._identity: bytes = (
26
+ f"{self.__class__.__name__}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}".encode()
27
+ )
28
+
29
+ self._socket: Optional[socket.socket] = socket.create_connection((self._host, self._port))
30
+ self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
31
+
32
+ self._next_request_id = 0
33
+
34
+ self._socket_lock = Lock()
35
+
36
+ self.__send_buffers([struct.pack("<Q", len(self._identity)), self._identity])
37
+ self.__read_framed_message() # receive server identity
38
+
39
+ def __del__(self):
40
+ self.destroy()
41
+
42
+ def destroy(self):
43
+ with self._socket_lock:
44
+ if self._socket is not None:
45
+ self._socket.close()
46
+ self._socket = None
47
+
48
+ @property
49
+ def address(self) -> str:
50
+ return f"tcp://{self._host}:{self._port}"
51
+
52
+ def set_object(self, object_id: ObjectID, payload: bytes):
53
+ """
54
+ Sets the object's payload on the object storage server.
55
+ """
56
+
57
+ with self._socket_lock:
58
+ self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
59
+ response_header, response_payload = self.__receive_response()
60
+
61
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.SetOK])
62
+ self.__ensure_empty_payload(response_payload)
63
+
64
+ def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytearray:
65
+ """
66
+ Returns the object's payload from the object storage server.
67
+
68
+ Will block until the object is available.
69
+ """
70
+
71
+ with self._socket_lock:
72
+ self.__send_request(object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject)
73
+ response_header, response_payload = self.__receive_response()
74
+
75
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.GetOK])
76
+
77
+ return response_payload
78
+
79
+ def delete_object(self, object_id: ObjectID) -> bool:
80
+ """
81
+ Removes the object from the object storage server.
82
+
83
+ Returns `False` if the object wasn't found in the server. Otherwise returns `True`.
84
+ """
85
+
86
+ with self._socket_lock:
87
+ self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject)
88
+ response_header, response_payload = self.__receive_response()
89
+
90
+ self.__ensure_response_type(
91
+ response_header,
92
+ [ObjectResponseHeader.ObjectResponseType.DelOK, ObjectResponseHeader.ObjectResponseType.DelNotExists],
93
+ )
94
+ self.__ensure_empty_payload(response_payload)
95
+
96
+ return response_header.response_type == ObjectResponseHeader.ObjectResponseType.DelOK
97
+
98
+ def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
99
+ """
100
+ Link an object's content to a new object ID on the object storage server.
101
+ """
102
+
103
+ object_id_payload = to_capnp_object_id(object_id).to_bytes()
104
+
105
+ with self._socket_lock:
106
+ self.__send_request(
107
+ new_object_id,
108
+ len(object_id_payload),
109
+ ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
110
+ object_id_payload,
111
+ )
112
+ response_header, response_payload = self.__receive_response()
113
+
114
+ self.__ensure_response_type(response_header, [ObjectResponseHeader.ObjectResponseType.DuplicateOK])
115
+ self.__ensure_empty_payload(response_payload)
116
+
117
+ def __ensure_is_connected(self):
118
+ if self._socket is None:
119
+ raise ObjectStorageException("connector is closed.")
120
+
121
+ def __ensure_response_type(
122
+ self, header: ObjectResponseHeader, valid_response_types: Iterable[ObjectResponseHeader.ObjectResponseType]
123
+ ):
124
+ if header.response_type not in valid_response_types:
125
+ raise RuntimeError(f"unexpected object storage response_type={header.response_type}.")
126
+
127
+ def __ensure_empty_payload(self, payload: bytearray):
128
+ if len(payload) != 0:
129
+ raise RuntimeError(f"unexpected response payload_length={len(payload)}, expected 0.")
130
+
131
+ def __send_request(
132
+ self,
133
+ object_id: ObjectID,
134
+ payload_length: int,
135
+ request_type: ObjectRequestHeader.ObjectRequestType,
136
+ payload: Optional[bytes] = None,
137
+ ):
138
+ self.__ensure_is_connected()
139
+ assert self._socket is not None
140
+
141
+ request_id = self._next_request_id
142
+ self._next_request_id += 1
143
+ self._next_request_id %= 2**64 - 1 # UINT64_MAX
144
+
145
+ header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
146
+ header_bytes = header.get_message().to_bytes()
147
+
148
+ if payload is not None:
149
+ self.__send_buffers(
150
+ [struct.pack("<Q", len(header_bytes)), header_bytes, struct.pack("<Q", len(payload)), payload]
151
+ )
152
+ else:
153
+ self.__send_buffers([struct.pack("<Q", len(header_bytes)), header_bytes])
154
+
155
+ def __send_buffers(self, buffers: List[bytes]) -> None:
156
+ if len(buffers) < 1:
157
+ return
158
+
159
+ total_size = sum(len(buffer) for buffer in buffers)
160
+
161
+ # If the message is small enough, first try to send it at once with sendmsg(). This would ensure the message can
162
+ # be transmitted within a single TCP segment.
163
+ if total_size < MAX_CHUNK_SIZE:
164
+ sent = self._socket.sendmsg(buffers)
165
+
166
+ if sent <= 0:
167
+ self.__raise_connection_failure()
168
+
169
+ remaining_buffers = collections.deque(buffers)
170
+ while sent > len(remaining_buffers[0]):
171
+ removed_buffer = remaining_buffers.popleft()
172
+ sent -= len(removed_buffer)
173
+
174
+ if sent > 0:
175
+ # Truncate the first partially sent buffer
176
+ remaining_buffers[0] = memoryview(remaining_buffers[0])[sent:]
177
+
178
+ buffers = list(remaining_buffers)
179
+
180
+ # Send the remaining buffers sequentially
181
+ for buffer in buffers:
182
+ self.__send_buffer(buffer)
183
+
184
+ def __send_buffer(self, buffer: bytes) -> None:
185
+ buffer_view = memoryview(buffer)
186
+
187
+ total_sent = 0
188
+ while total_sent < len(buffer):
189
+ sent = self._socket.send(buffer_view[total_sent : MAX_CHUNK_SIZE + total_sent])
190
+
191
+ if sent <= 0:
192
+ self.__raise_connection_failure()
193
+
194
+ total_sent += sent
195
+
196
+ def __receive_response(self) -> Tuple[ObjectResponseHeader, bytearray]:
197
+ assert self._socket is not None
198
+
199
+ header = self.__read_response_header()
200
+ payload = self.__read_response_payload(header)
201
+
202
+ return header, payload
203
+
204
+ def __read_response_header(self) -> ObjectResponseHeader:
205
+ assert self._socket is not None
206
+
207
+ header_bytearray = self.__read_framed_message()
208
+
209
+ # pycapnp does not like to read from a bytearray object. This look like an not-yet-resolved issue.
210
+ # That's is annoying because it leads to an unnecessary copy of the header's buffer.
211
+ # See https://github.com/capnproto/pycapnp/issues/153
212
+ header_bytes = bytes(header_bytearray)
213
+
214
+ with _object_storage.ObjectResponseHeader.from_bytes(header_bytes) as header_message:
215
+ return ObjectResponseHeader(header_message)
216
+
217
+ def __read_response_payload(self, header: ObjectResponseHeader) -> bytearray:
218
+ if header.payload_length > 0:
219
+ res = self.__read_framed_message()
220
+ assert len(res) == header.payload_length
221
+ return res
222
+ else:
223
+ return bytearray()
224
+
225
+ def __read_exactly(self, length: int) -> bytearray:
226
+ buffer = bytearray(length)
227
+
228
+ total_received = 0
229
+ while total_received < length:
230
+ chunk_size = min(MAX_CHUNK_SIZE, length - total_received)
231
+ received = self._socket.recv_into(memoryview(buffer)[total_received:], chunk_size)
232
+
233
+ if received <= 0:
234
+ self.__raise_connection_failure()
235
+
236
+ total_received += received
237
+
238
+ return buffer
239
+
240
+ def __read_framed_message(self) -> bytearray:
241
+ length_bytes = self.__read_exactly(8)
242
+ (payload_length,) = struct.unpack("<Q", length_bytes)
243
+ return self.__read_exactly(payload_length) if payload_length > 0 else bytearray()
244
+
245
+ @staticmethod
246
+ def __raise_connection_failure():
247
+ raise ObjectStorageException("connection failure to object storage server.")
@@ -0,0 +1,83 @@
1
+ import logging
2
+ import threading
3
+ from typing import Callable, Optional
4
+
5
+ import zmq
6
+
7
+ from scaler.config.types.zmq import ZMQConfig
8
+ from scaler.io.mixins import SyncSubscriber
9
+ from scaler.io.utility import deserialize
10
+ from scaler.protocol.python.mixins import Message
11
+
12
+
13
+ class ZMQSyncSubscriber(SyncSubscriber, threading.Thread):
14
+ def __init__(
15
+ self,
16
+ address: ZMQConfig,
17
+ callback: Callable[[Message], None],
18
+ topic: bytes,
19
+ exit_callback: Optional[Callable[[], None]] = None,
20
+ stop_event: threading.Event = threading.Event(),
21
+ daemonic: bool = False,
22
+ timeout_seconds: int = -1,
23
+ ):
24
+ threading.Thread.__init__(self)
25
+
26
+ self._stop_event = stop_event
27
+ self._address = address
28
+ self._callback = callback
29
+ self._exit_callback = exit_callback
30
+ self._topic = topic
31
+ self.daemon = bool(daemonic)
32
+ self._timeout_seconds = timeout_seconds
33
+
34
+ self._context: Optional[zmq.Context] = None
35
+ self._socket: Optional[zmq.Socket] = None
36
+
37
+ def __close(self):
38
+ self._socket.close()
39
+
40
+ def __stop_polling(self):
41
+ self._stop_event.set()
42
+
43
+ def destroy(self):
44
+ self.__stop_polling()
45
+
46
+ def run(self) -> None:
47
+ self.__initialize()
48
+
49
+ while not self._stop_event.is_set():
50
+ self.__routine_polling()
51
+
52
+ if self._exit_callback is not None:
53
+ self._exit_callback()
54
+
55
+ self.__close()
56
+
57
+ def __initialize(self):
58
+ self._context = zmq.Context.instance()
59
+ self._socket = self._context.socket(zmq.SUB)
60
+ self._socket.setsockopt(zmq.RCVHWM, 0)
61
+
62
+ if self._timeout_seconds == -1:
63
+ self._socket.setsockopt(zmq.RCVTIMEO, self._timeout_seconds)
64
+ else:
65
+ self._socket.setsockopt(zmq.RCVTIMEO, self._timeout_seconds * 1000)
66
+
67
+ self._socket.subscribe(self._topic)
68
+ self._socket.connect(self._address.to_address())
69
+ self._socket.connect(self._address.to_address())
70
+
71
+ def __routine_polling(self):
72
+ try:
73
+ self.__routine_receive(self._socket.recv(copy=False).bytes)
74
+ except zmq.Again:
75
+ raise TimeoutError(f"Cannot connect to {self._address.to_address()} in {self._timeout_seconds} seconds")
76
+
77
+ def __routine_receive(self, payload: bytes):
78
+ result: Optional[Message] = deserialize(payload)
79
+ if result is None:
80
+ logging.error(f"received unknown message: {payload!r}")
81
+ return None
82
+
83
+ self._callback(result)
scaler/io/utility.py ADDED
@@ -0,0 +1,80 @@
1
+ import logging
2
+ import os
3
+ from typing import List, Optional
4
+
5
+ from scaler.config.defaults import CAPNP_DATA_SIZE_LIMIT, CAPNP_MESSAGE_SIZE_LIMIT
6
+ from scaler.config.types.network_backend import NetworkBackend
7
+ from scaler.io.async_object_storage_connector import PyAsyncObjectStorageConnector
8
+ from scaler.io.mixins import AsyncObjectStorageConnector, SyncObjectStorageConnector
9
+ from scaler.io.sync_object_storage_connector import PySyncObjectStorageConnector
10
+ from scaler.protocol.capnp._python import _message # noqa
11
+ from scaler.protocol.python.message import PROTOCOL
12
+ from scaler.protocol.python.mixins import Message
13
+
14
+ try:
15
+ from collections.abc import Buffer # type: ignore[attr-defined]
16
+ except ImportError:
17
+ from typing_extensions import Buffer
18
+
19
+
20
+ def get_scaler_network_backend_from_env():
21
+ backend_str = os.environ.get("SCALER_NETWORK_BACKEND", "tcp_zmq") # Default to tcp_zmq
22
+ try:
23
+ return NetworkBackend[backend_str]
24
+ except KeyError:
25
+ return None
26
+
27
+
28
+ def create_async_object_storage_connector(*args, **kwargs) -> AsyncObjectStorageConnector:
29
+ connector_type = get_scaler_network_backend_from_env()
30
+ if connector_type == NetworkBackend.ymq:
31
+ from scaler.io.ymq_async_object_storage_connector import PyYMQAsyncObjectStorageConnector
32
+
33
+ return PyYMQAsyncObjectStorageConnector(*args, **kwargs)
34
+
35
+ elif connector_type == NetworkBackend.tcp_zmq:
36
+ return PyAsyncObjectStorageConnector(*args, **kwargs)
37
+
38
+ else:
39
+ raise ValueError(
40
+ f"Invalid SCALER_NETWORK_BACKEND value." f"Expected one of: {[e.name for e in NetworkBackend]}"
41
+ )
42
+
43
+
44
+ def create_sync_object_storage_connector(*args, **kwargs) -> SyncObjectStorageConnector:
45
+ connector_type = get_scaler_network_backend_from_env()
46
+ if connector_type == NetworkBackend.ymq:
47
+ from scaler.io.ymq_sync_object_storage_connector import PyYMQSyncObjectStorageConnector
48
+
49
+ return PyYMQSyncObjectStorageConnector(*args, **kwargs)
50
+
51
+ elif connector_type == NetworkBackend.tcp_zmq:
52
+ return PySyncObjectStorageConnector(*args, **kwargs)
53
+ else:
54
+ raise ValueError(
55
+ f"Invalid SCALER_NETWORK_BACKEND value." f"Expected one of: {[e.name for e in NetworkBackend]}"
56
+ )
57
+
58
+
59
+ def deserialize(data: Buffer) -> Optional[Message]:
60
+ with _message.Message.from_bytes(data, traversal_limit_in_words=CAPNP_MESSAGE_SIZE_LIMIT) as payload:
61
+ if not hasattr(payload, payload.which()):
62
+ logging.error(f"unknown message type: {payload.which()}")
63
+ return None
64
+
65
+ message = getattr(payload, payload.which())
66
+ return PROTOCOL[payload.which()](message)
67
+
68
+
69
+ def serialize(message: Message) -> bytes:
70
+ payload = _message.Message(**{PROTOCOL.inverse[type(message)]: message.get_message()})
71
+ return payload.to_bytes()
72
+
73
+
74
+ def chunk_to_list_of_bytes(data: bytes) -> List[bytes]:
75
+ # TODO: change to list of memoryview when capnp can support memoryview
76
+ return [data[i : i + CAPNP_DATA_SIZE_LIMIT] for i in range(0, len(data), CAPNP_DATA_SIZE_LIMIT)]
77
+
78
+
79
+ def concat_list_of_bytes(data: List[bytes]) -> bytes:
80
+ return bytearray().join(data)
File without changes