opengris-scaler 1.12.28__cp313-cp313-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opengris-scaler might be problematic. Click here for more details.
- opengris_scaler-1.12.28.dist-info/METADATA +728 -0
- opengris_scaler-1.12.28.dist-info/RECORD +187 -0
- opengris_scaler-1.12.28.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.28.dist-info/entry_points.txt +10 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.28.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.28.dist-info/licenses/NOTICE +8 -0
- opengris_scaler.libs/libcapnp-1-e88d5415.0.1.so +0 -0
- opengris_scaler.libs/libgcc_s-2298274a.so.1 +0 -0
- opengris_scaler.libs/libkj-1-9bebd8ac.0.1.so +0 -0
- opengris_scaler.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
- scaler/__init__.py +14 -0
- scaler/about.py +5 -0
- scaler/client/__init__.py +0 -0
- scaler/client/agent/__init__.py +0 -0
- scaler/client/agent/client_agent.py +210 -0
- scaler/client/agent/disconnect_manager.py +27 -0
- scaler/client/agent/future_manager.py +112 -0
- scaler/client/agent/heartbeat_manager.py +74 -0
- scaler/client/agent/mixins.py +89 -0
- scaler/client/agent/object_manager.py +98 -0
- scaler/client/agent/task_manager.py +64 -0
- scaler/client/client.py +658 -0
- scaler/client/future.py +252 -0
- scaler/client/object_buffer.py +129 -0
- scaler/client/object_reference.py +25 -0
- scaler/client/serializer/__init__.py +0 -0
- scaler/client/serializer/default.py +16 -0
- scaler/client/serializer/mixins.py +38 -0
- scaler/cluster/__init__.py +0 -0
- scaler/cluster/cluster.py +115 -0
- scaler/cluster/combo.py +150 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +86 -0
- scaler/config/__init__.py +0 -0
- scaler/config/defaults.py +94 -0
- scaler/config/loader.py +96 -0
- scaler/config/mixins.py +20 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +55 -0
- scaler/config/section/ecs_worker_adapter.py +85 -0
- scaler/config/section/native_worker_adapter.py +43 -0
- scaler/config/section/object_storage_server.py +8 -0
- scaler/config/section/scheduler.py +54 -0
- scaler/config/section/symphony_worker_adapter.py +47 -0
- scaler/config/section/top.py +13 -0
- scaler/config/section/webui.py +21 -0
- scaler/config/types/__init__.py +0 -0
- scaler/config/types/network_backend.py +12 -0
- scaler/config/types/object_storage_server.py +45 -0
- scaler/config/types/worker.py +62 -0
- scaler/config/types/zmq.py +83 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +133 -0
- scaler/entry_points/object_storage_server.py +45 -0
- scaler/entry_points/scheduler.py +144 -0
- scaler/entry_points/top.py +286 -0
- scaler/entry_points/webui.py +48 -0
- scaler/entry_points/worker_adapter_ecs.py +191 -0
- scaler/entry_points/worker_adapter_native.py +137 -0
- scaler/entry_points/worker_adapter_symphony.py +98 -0
- scaler/io/__init__.py +0 -0
- scaler/io/async_binder.py +89 -0
- scaler/io/async_connector.py +95 -0
- scaler/io/async_object_storage_connector.py +225 -0
- scaler/io/mixins.py +154 -0
- scaler/io/sync_connector.py +68 -0
- scaler/io/sync_object_storage_connector.py +247 -0
- scaler/io/sync_subscriber.py +83 -0
- scaler/io/utility.py +80 -0
- scaler/io/ymq/__init__.py +0 -0
- scaler/io/ymq/_ymq.pyi +95 -0
- scaler/io/ymq/ymq.py +138 -0
- scaler/io/ymq_async_object_storage_connector.py +184 -0
- scaler/io/ymq_sync_object_storage_connector.py +184 -0
- scaler/object_storage/__init__.py +0 -0
- scaler/protocol/__init__.py +0 -0
- scaler/protocol/capnp/__init__.py +0 -0
- scaler/protocol/capnp/_python.py +6 -0
- scaler/protocol/capnp/common.capnp +68 -0
- scaler/protocol/capnp/message.capnp +218 -0
- scaler/protocol/capnp/object_storage.capnp +57 -0
- scaler/protocol/capnp/status.capnp +73 -0
- scaler/protocol/introduction.md +105 -0
- scaler/protocol/python/__init__.py +0 -0
- scaler/protocol/python/common.py +140 -0
- scaler/protocol/python/message.py +751 -0
- scaler/protocol/python/mixins.py +13 -0
- scaler/protocol/python/object_storage.py +118 -0
- scaler/protocol/python/status.py +279 -0
- scaler/protocol/worker.md +228 -0
- scaler/scheduler/__init__.py +0 -0
- scaler/scheduler/allocate_policy/__init__.py +0 -0
- scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
- scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
- scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
- scaler/scheduler/allocate_policy/mixins.py +55 -0
- scaler/scheduler/controllers/__init__.py +0 -0
- scaler/scheduler/controllers/balance_controller.py +65 -0
- scaler/scheduler/controllers/client_controller.py +131 -0
- scaler/scheduler/controllers/config_controller.py +31 -0
- scaler/scheduler/controllers/graph_controller.py +424 -0
- scaler/scheduler/controllers/information_controller.py +81 -0
- scaler/scheduler/controllers/mixins.py +194 -0
- scaler/scheduler/controllers/object_controller.py +147 -0
- scaler/scheduler/controllers/scaling_policies/__init__.py +0 -0
- scaler/scheduler/controllers/scaling_policies/fixed_elastic.py +145 -0
- scaler/scheduler/controllers/scaling_policies/mixins.py +10 -0
- scaler/scheduler/controllers/scaling_policies/null.py +14 -0
- scaler/scheduler/controllers/scaling_policies/types.py +9 -0
- scaler/scheduler/controllers/scaling_policies/utility.py +20 -0
- scaler/scheduler/controllers/scaling_policies/vanilla.py +95 -0
- scaler/scheduler/controllers/task_controller.py +376 -0
- scaler/scheduler/controllers/worker_controller.py +169 -0
- scaler/scheduler/object_usage/__init__.py +0 -0
- scaler/scheduler/object_usage/object_tracker.py +131 -0
- scaler/scheduler/scheduler.py +251 -0
- scaler/scheduler/task/__init__.py +0 -0
- scaler/scheduler/task/task_state_machine.py +92 -0
- scaler/scheduler/task/task_state_manager.py +61 -0
- scaler/ui/__init__.py +0 -0
- scaler/ui/constants.py +9 -0
- scaler/ui/live_display.py +147 -0
- scaler/ui/memory_window.py +146 -0
- scaler/ui/setting_page.py +40 -0
- scaler/ui/task_graph.py +832 -0
- scaler/ui/task_log.py +107 -0
- scaler/ui/utility.py +66 -0
- scaler/ui/webui.py +147 -0
- scaler/ui/worker_processors.py +104 -0
- scaler/utility/__init__.py +0 -0
- scaler/utility/debug.py +19 -0
- scaler/utility/event_list.py +63 -0
- scaler/utility/event_loop.py +58 -0
- scaler/utility/exceptions.py +42 -0
- scaler/utility/formatter.py +44 -0
- scaler/utility/graph/__init__.py +0 -0
- scaler/utility/graph/optimization.py +27 -0
- scaler/utility/graph/topological_sorter.py +11 -0
- scaler/utility/graph/topological_sorter_graphblas.py +174 -0
- scaler/utility/identifiers.py +107 -0
- scaler/utility/logging/__init__.py +0 -0
- scaler/utility/logging/decorators.py +25 -0
- scaler/utility/logging/scoped_logger.py +33 -0
- scaler/utility/logging/utility.py +183 -0
- scaler/utility/many_to_many_dict.py +123 -0
- scaler/utility/metadata/__init__.py +0 -0
- scaler/utility/metadata/profile_result.py +31 -0
- scaler/utility/metadata/task_flags.py +30 -0
- scaler/utility/mixins.py +13 -0
- scaler/utility/network_util.py +7 -0
- scaler/utility/one_to_many_dict.py +72 -0
- scaler/utility/queues/__init__.py +0 -0
- scaler/utility/queues/async_indexed_queue.py +37 -0
- scaler/utility/queues/async_priority_queue.py +70 -0
- scaler/utility/queues/async_sorted_priority_queue.py +45 -0
- scaler/utility/queues/indexed_queue.py +114 -0
- scaler/utility/serialization.py +9 -0
- scaler/version.txt +1 -0
- scaler/worker/__init__.py +0 -0
- scaler/worker/agent/__init__.py +0 -0
- scaler/worker/agent/heartbeat_manager.py +107 -0
- scaler/worker/agent/mixins.py +137 -0
- scaler/worker/agent/processor/__init__.py +0 -0
- scaler/worker/agent/processor/object_cache.py +107 -0
- scaler/worker/agent/processor/processor.py +285 -0
- scaler/worker/agent/processor/streaming_buffer.py +28 -0
- scaler/worker/agent/processor_holder.py +147 -0
- scaler/worker/agent/processor_manager.py +369 -0
- scaler/worker/agent/profiling_manager.py +109 -0
- scaler/worker/agent/task_manager.py +150 -0
- scaler/worker/agent/timeout_manager.py +19 -0
- scaler/worker/preload.py +84 -0
- scaler/worker/worker.py +265 -0
- scaler/worker_adapter/__init__.py +0 -0
- scaler/worker_adapter/common.py +26 -0
- scaler/worker_adapter/ecs.py +269 -0
- scaler/worker_adapter/native.py +155 -0
- scaler/worker_adapter/symphony/__init__.py +0 -0
- scaler/worker_adapter/symphony/callback.py +45 -0
- scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
- scaler/worker_adapter/symphony/message.py +24 -0
- scaler/worker_adapter/symphony/task_manager.py +289 -0
- scaler/worker_adapter/symphony/worker.py +204 -0
- scaler/worker_adapter/symphony/worker_adapter.py +139 -0
- src/scaler/io/ymq/_ymq.so +0 -0
- src/scaler/object_storage/object_storage_server.so +0 -0
scaler/io/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
|