opengris-scaler 1.12.7__cp310-cp310-manylinux_2_28_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.7.dist-info/METADATA +729 -0
- opengris_scaler-1.12.7.dist-info/RECORD +232 -0
- opengris_scaler-1.12.7.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.7.dist-info/entry_points.txt +9 -0
- opengris_scaler-1.12.7.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.7.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.7.dist-info/licenses/NOTICE +8 -0
- opengris_scaler.libs/libcapnp-1-b787335c.1.0.so +0 -0
- opengris_scaler.libs/libkj-1-094aa318.1.0.so +0 -0
- scaler/CMakeLists.txt +11 -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 +635 -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 +148 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +83 -0
- scaler/config/__init__.py +0 -0
- scaler/config/defaults.py +87 -0
- scaler/config/loader.py +95 -0
- scaler/config/mixins.py +15 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +56 -0
- scaler/config/section/native_worker_adapter.py +44 -0
- scaler/config/section/object_storage_server.py +7 -0
- scaler/config/section/scheduler.py +53 -0
- scaler/config/section/symphony_worker_adapter.py +47 -0
- scaler/config/section/top.py +13 -0
- scaler/config/section/webui.py +16 -0
- scaler/config/types/__init__.py +0 -0
- scaler/config/types/object_storage_server.py +45 -0
- scaler/config/types/worker.py +57 -0
- scaler/config/types/zmq.py +79 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +133 -0
- scaler/entry_points/object_storage_server.py +41 -0
- scaler/entry_points/scheduler.py +135 -0
- scaler/entry_points/top.py +286 -0
- scaler/entry_points/webui.py +26 -0
- scaler/entry_points/worker_adapter_native.py +137 -0
- scaler/entry_points/worker_adapter_symphony.py +102 -0
- scaler/io/__init__.py +0 -0
- scaler/io/async_binder.py +85 -0
- scaler/io/async_connector.py +95 -0
- scaler/io/async_object_storage_connector.py +185 -0
- scaler/io/mixins.py +154 -0
- scaler/io/sync_connector.py +68 -0
- scaler/io/sync_object_storage_connector.py +185 -0
- scaler/io/sync_subscriber.py +83 -0
- scaler/io/utility.py +31 -0
- scaler/io/ymq/CMakeLists.txt +98 -0
- scaler/io/ymq/__init__.py +0 -0
- scaler/io/ymq/_ymq.pyi +96 -0
- scaler/io/ymq/_ymq.so +0 -0
- scaler/io/ymq/bytes.h +114 -0
- scaler/io/ymq/common.h +29 -0
- scaler/io/ymq/configuration.h +60 -0
- scaler/io/ymq/epoll_context.cpp +185 -0
- scaler/io/ymq/epoll_context.h +85 -0
- scaler/io/ymq/error.h +132 -0
- scaler/io/ymq/event_loop.h +55 -0
- scaler/io/ymq/event_loop_thread.cpp +64 -0
- scaler/io/ymq/event_loop_thread.h +46 -0
- scaler/io/ymq/event_manager.h +81 -0
- scaler/io/ymq/file_descriptor.h +203 -0
- scaler/io/ymq/interruptive_concurrent_queue.h +169 -0
- scaler/io/ymq/io_context.cpp +98 -0
- scaler/io/ymq/io_context.h +44 -0
- scaler/io/ymq/io_socket.cpp +299 -0
- scaler/io/ymq/io_socket.h +121 -0
- scaler/io/ymq/iocp_context.cpp +102 -0
- scaler/io/ymq/iocp_context.h +83 -0
- scaler/io/ymq/logging.h +163 -0
- scaler/io/ymq/message.h +15 -0
- scaler/io/ymq/message_connection.h +16 -0
- scaler/io/ymq/message_connection_tcp.cpp +672 -0
- scaler/io/ymq/message_connection_tcp.h +96 -0
- scaler/io/ymq/network_utils.h +179 -0
- scaler/io/ymq/pymod_ymq/bytes.h +113 -0
- scaler/io/ymq/pymod_ymq/exception.h +124 -0
- scaler/io/ymq/pymod_ymq/gil.h +15 -0
- scaler/io/ymq/pymod_ymq/io_context.h +166 -0
- scaler/io/ymq/pymod_ymq/io_socket.h +285 -0
- scaler/io/ymq/pymod_ymq/message.h +99 -0
- scaler/io/ymq/pymod_ymq/python.h +153 -0
- scaler/io/ymq/pymod_ymq/ymq.cpp +23 -0
- scaler/io/ymq/pymod_ymq/ymq.h +357 -0
- scaler/io/ymq/readme.md +114 -0
- scaler/io/ymq/simple_interface.cpp +80 -0
- scaler/io/ymq/simple_interface.h +24 -0
- scaler/io/ymq/tcp_client.cpp +367 -0
- scaler/io/ymq/tcp_client.h +75 -0
- scaler/io/ymq/tcp_operations.h +41 -0
- scaler/io/ymq/tcp_server.cpp +410 -0
- scaler/io/ymq/tcp_server.h +79 -0
- scaler/io/ymq/third_party/concurrentqueue.h +3747 -0
- scaler/io/ymq/timed_queue.h +272 -0
- scaler/io/ymq/timestamp.h +102 -0
- scaler/io/ymq/typedefs.h +20 -0
- scaler/io/ymq/utils.h +34 -0
- scaler/io/ymq/ymq.py +130 -0
- scaler/object_storage/CMakeLists.txt +50 -0
- scaler/object_storage/__init__.py +0 -0
- scaler/object_storage/constants.h +11 -0
- scaler/object_storage/defs.h +14 -0
- scaler/object_storage/io_helper.cpp +44 -0
- scaler/object_storage/io_helper.h +9 -0
- scaler/object_storage/message.cpp +56 -0
- scaler/object_storage/message.h +130 -0
- scaler/object_storage/object_manager.cpp +126 -0
- scaler/object_storage/object_manager.h +52 -0
- scaler/object_storage/object_storage_server.cpp +359 -0
- scaler/object_storage/object_storage_server.h +126 -0
- scaler/object_storage/object_storage_server.so +0 -0
- scaler/object_storage/pymod_object_storage_server.cpp +104 -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 +63 -0
- scaler/protocol/capnp/message.capnp +216 -0
- scaler/protocol/capnp/object_storage.capnp +52 -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 +135 -0
- scaler/protocol/python/message.py +726 -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 +201 -0
- scaler/scheduler/controllers/object_controller.py +147 -0
- scaler/scheduler/controllers/scaling_controller.py +86 -0
- scaler/scheduler/controllers/task_controller.py +373 -0
- scaler/scheduler/controllers/worker_controller.py +168 -0
- scaler/scheduler/object_usage/__init__.py +0 -0
- scaler/scheduler/object_usage/object_tracker.py +131 -0
- scaler/scheduler/scheduler.py +253 -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 +118 -0
- scaler/ui/memory_window.py +146 -0
- scaler/ui/setting_page.py +47 -0
- scaler/ui/task_graph.py +370 -0
- scaler/ui/task_log.py +83 -0
- scaler/ui/utility.py +35 -0
- scaler/ui/webui.py +125 -0
- scaler/ui/worker_processors.py +85 -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 +105 -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 +279 -0
- scaler/worker/agent/processor/streaming_buffer.py +28 -0
- scaler/worker/agent/processor_holder.py +145 -0
- scaler/worker/agent/processor_manager.py +365 -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 +264 -0
- scaler/worker_adapter/__init__.py +0 -0
- scaler/worker_adapter/native.py +154 -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 +288 -0
- scaler/worker_adapter/symphony/worker.py +205 -0
- 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.")
|