opengris-scaler 1.12.37__cp38-cp38-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opengris_scaler-1.12.37.dist-info/METADATA +730 -0
- opengris_scaler-1.12.37.dist-info/RECORD +196 -0
- opengris_scaler-1.12.37.dist-info/WHEEL +5 -0
- opengris_scaler-1.12.37.dist-info/entry_points.txt +10 -0
- opengris_scaler-1.12.37.dist-info/licenses/LICENSE +201 -0
- opengris_scaler-1.12.37.dist-info/licenses/LICENSE.spdx +7 -0
- opengris_scaler-1.12.37.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 +218 -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 +672 -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 +95 -0
- scaler/cluster/combo.py +157 -0
- scaler/cluster/object_storage_server.py +45 -0
- scaler/cluster/scheduler.py +86 -0
- scaler/config/__init__.py +0 -0
- scaler/config/common/__init__.py +0 -0
- scaler/config/common/logging.py +41 -0
- scaler/config/common/web.py +18 -0
- scaler/config/common/worker.py +65 -0
- scaler/config/common/worker_adapter.py +28 -0
- scaler/config/config_class.py +317 -0
- scaler/config/defaults.py +94 -0
- scaler/config/mixins.py +20 -0
- scaler/config/section/__init__.py +0 -0
- scaler/config/section/cluster.py +66 -0
- scaler/config/section/ecs_worker_adapter.py +78 -0
- scaler/config/section/native_worker_adapter.py +30 -0
- scaler/config/section/object_storage_server.py +13 -0
- scaler/config/section/scheduler.py +126 -0
- scaler/config/section/symphony_worker_adapter.py +35 -0
- scaler/config/section/top.py +16 -0
- scaler/config/section/webui.py +16 -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 +67 -0
- scaler/config/types/zmq.py +83 -0
- scaler/entry_points/__init__.py +0 -0
- scaler/entry_points/cluster.py +10 -0
- scaler/entry_points/object_storage_server.py +26 -0
- scaler/entry_points/scheduler.py +51 -0
- scaler/entry_points/top.py +272 -0
- scaler/entry_points/webui.py +6 -0
- scaler/entry_points/worker_adapter_ecs.py +22 -0
- scaler/entry_points/worker_adapter_native.py +31 -0
- scaler/entry_points/worker_adapter_symphony.py +26 -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 +249 -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.so +0 -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/object_storage/object_storage_server.so +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/common/__init__.py +0 -0
- scaler/ui/common/constants.py +9 -0
- scaler/ui/common/live_display.py +147 -0
- scaler/ui/common/memory_window.py +146 -0
- scaler/ui/common/setting_page.py +40 -0
- scaler/ui/common/task_graph.py +840 -0
- scaler/ui/common/task_log.py +111 -0
- scaler/ui/common/utility.py +66 -0
- scaler/ui/common/webui.py +80 -0
- scaler/ui/common/worker_processors.py +104 -0
- scaler/ui/v1.py +76 -0
- scaler/ui/v2.py +102 -0
- scaler/ui/webui.py +21 -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 +110 -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 +241 -0
- scaler/worker_adapter/native.py +138 -0
- scaler/worker_adapter/symphony/__init__.py +0 -0
- scaler/worker_adapter/symphony/callback.py +45 -0
- scaler/worker_adapter/symphony/heartbeat_manager.py +82 -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 +123 -0
|
@@ -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
|
scaler/io/ymq/_ymq.pyi
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# NOTE: NOT IMPLEMENTATION, TYPE INFORMATION ONLY
|
|
2
|
+
# This file contains type stubs for the Ymq Python C Extension module
|
|
3
|
+
from enum import IntEnum
|
|
4
|
+
from typing import Callable, Optional, SupportsBytes, Union
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from collections.abc import Buffer # type: ignore[attr-defined]
|
|
8
|
+
except ImportError:
|
|
9
|
+
from typing_extensions import Buffer
|
|
10
|
+
|
|
11
|
+
class Bytes(Buffer):
|
|
12
|
+
data: bytes | None
|
|
13
|
+
len: int
|
|
14
|
+
|
|
15
|
+
def __init__(self, data: Buffer | None = None) -> None: ...
|
|
16
|
+
def __repr__(self) -> str: ...
|
|
17
|
+
def __len__(self) -> int: ...
|
|
18
|
+
|
|
19
|
+
# this type signature is not 100% accurate because it's implemented in C
|
|
20
|
+
# but this satisfies the type check and is good enough
|
|
21
|
+
def __buffer__(self, flags: int, /) -> memoryview: ...
|
|
22
|
+
|
|
23
|
+
class Message:
|
|
24
|
+
address: Bytes | None
|
|
25
|
+
payload: Bytes
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, address: Bytes | bytes | SupportsBytes | None, payload: Bytes | bytes | SupportsBytes
|
|
29
|
+
) -> None: ...
|
|
30
|
+
def __repr__(self) -> str: ...
|
|
31
|
+
def __str__(self) -> str: ...
|
|
32
|
+
|
|
33
|
+
class IOSocketType(IntEnum):
|
|
34
|
+
Uninit = 0
|
|
35
|
+
Binder = 1
|
|
36
|
+
Connector = 2
|
|
37
|
+
Unicast = 3
|
|
38
|
+
Multicast = 4
|
|
39
|
+
|
|
40
|
+
class BaseIOContext:
|
|
41
|
+
num_threads: int
|
|
42
|
+
|
|
43
|
+
def __init__(self, num_threads: int = 1) -> None: ...
|
|
44
|
+
def __repr__(self) -> str: ...
|
|
45
|
+
def createIOSocket(
|
|
46
|
+
self, callback: Callable[[Union[BaseIOSocket, Exception]], None], identity: str, socket_type: IOSocketType
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Create an io socket with an identity and socket type"""
|
|
49
|
+
|
|
50
|
+
class BaseIOSocket:
|
|
51
|
+
identity: str
|
|
52
|
+
socket_type: IOSocketType
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str: ...
|
|
55
|
+
def send(self, callback: Callable[[Optional[Exception]], None], message: Message) -> None:
|
|
56
|
+
"""Send a message to one of the socket's peers"""
|
|
57
|
+
|
|
58
|
+
def recv(self, callback: Callable[[Union[Message, Exception]], None]) -> None:
|
|
59
|
+
"""Receive a message from one of the socket's peers"""
|
|
60
|
+
|
|
61
|
+
def bind(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
|
|
62
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
63
|
+
|
|
64
|
+
def connect(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
|
|
65
|
+
"""Connect to a remote socket"""
|
|
66
|
+
|
|
67
|
+
class ErrorCode(IntEnum):
|
|
68
|
+
Uninit = 0
|
|
69
|
+
InvalidPortFormat = 1
|
|
70
|
+
InvalidAddressFormat = 2
|
|
71
|
+
ConfigurationError = 3
|
|
72
|
+
SignalNotSupported = 4
|
|
73
|
+
CoreBug = 5
|
|
74
|
+
RepetetiveIOSocketIdentity = 6
|
|
75
|
+
RedundantIOSocketRefCount = 7
|
|
76
|
+
MultipleConnectToNotSupported = 8
|
|
77
|
+
MultipleBindToNotSupported = 9
|
|
78
|
+
InitialConnectFailedWithInProgress = 10
|
|
79
|
+
SendMessageRequestCouldNotComplete = 11
|
|
80
|
+
SetSockOptNonFatalFailure = 12
|
|
81
|
+
IPv6NotSupported = 13
|
|
82
|
+
RemoteEndDisconnectedOnSocketWithoutGuaranteedDelivery = 14
|
|
83
|
+
ConnectorSocketClosedByRemoteEnd = 15
|
|
84
|
+
IOSocketStopRequested = 16
|
|
85
|
+
BinderSendMessageWithNoAddress = 17
|
|
86
|
+
|
|
87
|
+
def explanation(self) -> str: ...
|
|
88
|
+
|
|
89
|
+
class YMQException(Exception):
|
|
90
|
+
code: ErrorCode
|
|
91
|
+
message: str
|
|
92
|
+
|
|
93
|
+
def __init__(self, /, code: ErrorCode, message: str) -> None: ...
|
|
94
|
+
def __repr__(self) -> str: ...
|
|
95
|
+
def __str__(self) -> str: ...
|
scaler/io/ymq/_ymq.so
ADDED
|
Binary file
|
scaler/io/ymq/ymq.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# This file wraps the interface exported by the C implementation of the module
|
|
2
|
+
# and provides a more ergonomic interface supporting both asynchronous and synchronous execution
|
|
3
|
+
|
|
4
|
+
__all__ = ["IOSocket", "IOContext", "Message", "IOSocketType", "YMQException", "Bytes", "ErrorCode"]
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from typing import Concatenate, ParamSpec # type: ignore[attr-defined]
|
|
12
|
+
except ImportError:
|
|
13
|
+
from typing_extensions import ParamSpec, Concatenate # type: ignore[assignment]
|
|
14
|
+
|
|
15
|
+
from scaler.io.ymq._ymq import BaseIOContext, BaseIOSocket, Bytes, ErrorCode, IOSocketType, Message, YMQException
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IOSocket:
|
|
19
|
+
_base: BaseIOSocket
|
|
20
|
+
|
|
21
|
+
def __init__(self, base: BaseIOSocket) -> None:
|
|
22
|
+
self._base = base
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def socket_type(self) -> IOSocketType:
|
|
26
|
+
return self._base.socket_type
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def identity(self) -> str:
|
|
30
|
+
return self._base.identity
|
|
31
|
+
|
|
32
|
+
async def bind(self, address: str) -> None:
|
|
33
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
34
|
+
await call_async(self._base.bind, address)
|
|
35
|
+
|
|
36
|
+
def bind_sync(self, address: str, /, timeout: Optional[float] = None) -> None:
|
|
37
|
+
"""Bind the socket to an address and listen for incoming connections"""
|
|
38
|
+
call_sync(self._base.bind, address, timeout=timeout)
|
|
39
|
+
|
|
40
|
+
async def connect(self, address: str) -> None:
|
|
41
|
+
"""Connect to a remote socket"""
|
|
42
|
+
await call_async(self._base.connect, address)
|
|
43
|
+
|
|
44
|
+
def connect_sync(self, address: str, /, timeout: Optional[float] = None) -> None:
|
|
45
|
+
"""Connect to a remote socket"""
|
|
46
|
+
call_sync(self._base.connect, address, timeout=timeout)
|
|
47
|
+
|
|
48
|
+
async def send(self, message: Message) -> None:
|
|
49
|
+
"""Send a message to one of the socket's peers"""
|
|
50
|
+
await call_async(self._base.send, message)
|
|
51
|
+
|
|
52
|
+
def send_sync(self, message: Message, /, timeout: Optional[float] = None) -> None:
|
|
53
|
+
"""Send a message to one of the socket's peers"""
|
|
54
|
+
call_sync(self._base.send, message, timeout=timeout)
|
|
55
|
+
|
|
56
|
+
async def recv(self) -> Message:
|
|
57
|
+
"""Receive a message from one of the socket's peers"""
|
|
58
|
+
return await call_async(self._base.recv)
|
|
59
|
+
|
|
60
|
+
def recv_sync(self, /, timeout: Optional[float] = None) -> Message:
|
|
61
|
+
"""Receive a message from one of the socket's peers"""
|
|
62
|
+
return call_sync(self._base.recv, timeout=timeout)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class IOContext:
|
|
66
|
+
_base: BaseIOContext
|
|
67
|
+
|
|
68
|
+
def __init__(self, num_threads: int = 1) -> None:
|
|
69
|
+
self._base = BaseIOContext(num_threads)
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def num_threads(self) -> int:
|
|
73
|
+
return self._base.num_threads
|
|
74
|
+
|
|
75
|
+
async def createIOSocket(self, identity: str, socket_type: IOSocketType) -> IOSocket:
|
|
76
|
+
"""Create an io socket with an identity and socket type"""
|
|
77
|
+
return IOSocket(await call_async(self._base.createIOSocket, identity, socket_type))
|
|
78
|
+
|
|
79
|
+
def createIOSocket_sync(self, identity: str, socket_type: IOSocketType) -> IOSocket:
|
|
80
|
+
"""Create an io socket with an identity and socket type"""
|
|
81
|
+
return IOSocket(call_sync(self._base.createIOSocket, identity, socket_type))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
P = ParamSpec("P")
|
|
85
|
+
T = TypeVar("T")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def call_async(
|
|
89
|
+
func: Callable[Concatenate[Callable[[Union[T, BaseException]], None], P], None], # type: ignore
|
|
90
|
+
*args: P.args, # type: ignore
|
|
91
|
+
**kwargs: P.kwargs, # type: ignore
|
|
92
|
+
) -> T:
|
|
93
|
+
loop = asyncio.get_event_loop()
|
|
94
|
+
future = loop.create_future()
|
|
95
|
+
|
|
96
|
+
def callback(result: Union[T, BaseException]):
|
|
97
|
+
if isinstance(result, BaseException):
|
|
98
|
+
loop.call_soon_threadsafe(_safe_set_exception, future, result)
|
|
99
|
+
else:
|
|
100
|
+
loop.call_soon_threadsafe(_safe_set_result, future, result)
|
|
101
|
+
|
|
102
|
+
func(callback, *args, **kwargs)
|
|
103
|
+
return await future
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# about the ignore directives: mypy cannot properly handle typing extension's ParamSpec and Concatenate in python <=3.9
|
|
107
|
+
# these type hints are correctly understood in Python 3.10+
|
|
108
|
+
def call_sync( # type: ignore[valid-type]
|
|
109
|
+
func: Callable[Concatenate[Callable[[Union[T, BaseException]], None], P], None], # type: ignore
|
|
110
|
+
*args: P.args, # type: ignore
|
|
111
|
+
timeout: Optional[float] = None,
|
|
112
|
+
**kwargs: P.kwargs, # type: ignore
|
|
113
|
+
) -> T: # type: ignore
|
|
114
|
+
future: concurrent.futures.Future = concurrent.futures.Future()
|
|
115
|
+
|
|
116
|
+
def callback(result: Union[T, BaseException]):
|
|
117
|
+
if future.done():
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
if isinstance(result, BaseException):
|
|
121
|
+
future.set_exception(result)
|
|
122
|
+
else:
|
|
123
|
+
future.set_result(result)
|
|
124
|
+
|
|
125
|
+
func(callback, *args, **kwargs)
|
|
126
|
+
return future.result(timeout)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _safe_set_result(future: asyncio.Future, result: Any) -> None:
|
|
130
|
+
if future.done():
|
|
131
|
+
return
|
|
132
|
+
future.set_result(result)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _safe_set_exception(future: asyncio.Future, exc: BaseException) -> None:
|
|
136
|
+
if future.done():
|
|
137
|
+
return
|
|
138
|
+
future.set_exception(exc)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from scaler.io.mixins import AsyncObjectStorageConnector
|
|
8
|
+
from scaler.io.ymq.ymq import IOContext, IOSocketType, Message, YMQException
|
|
9
|
+
from scaler.protocol.capnp._python import _object_storage # noqa
|
|
10
|
+
from scaler.protocol.python.object_storage import ObjectRequestHeader, ObjectResponseHeader, to_capnp_object_id
|
|
11
|
+
from scaler.utility.exceptions import ObjectStorageException
|
|
12
|
+
from scaler.utility.identifiers import ObjectID
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PyYMQAsyncObjectStorageConnector(AsyncObjectStorageConnector):
|
|
16
|
+
"""An asyncio connector that uses YMQ to connect to a Scaler's object storage instance."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._host: Optional[str] = None
|
|
20
|
+
self._port: Optional[int] = None
|
|
21
|
+
|
|
22
|
+
self._connected_event = asyncio.Event()
|
|
23
|
+
|
|
24
|
+
self._next_request_id = 0
|
|
25
|
+
self._pending_get_requests: Dict[ObjectID, asyncio.Future] = {}
|
|
26
|
+
|
|
27
|
+
self._lock = asyncio.Lock()
|
|
28
|
+
self._identity: str = f"{self.__class__.__name__}|{socket.gethostname().split('.')[0]}|{uuid.uuid4()}"
|
|
29
|
+
self._io_context: IOContext = IOContext()
|
|
30
|
+
self._io_socket = self._io_context.createIOSocket_sync(self._identity, IOSocketType.Connector)
|
|
31
|
+
|
|
32
|
+
def __del__(self):
|
|
33
|
+
if not self.is_connected():
|
|
34
|
+
return
|
|
35
|
+
self._io_socket = None
|
|
36
|
+
|
|
37
|
+
async def connect(self, host: str, port: int):
|
|
38
|
+
self._host = host
|
|
39
|
+
self._port = port
|
|
40
|
+
|
|
41
|
+
if self.is_connected():
|
|
42
|
+
raise ObjectStorageException("connector is already connected.")
|
|
43
|
+
await self._io_socket.connect(self.address)
|
|
44
|
+
self._connected_event.set()
|
|
45
|
+
|
|
46
|
+
async def wait_until_connected(self):
|
|
47
|
+
await self._connected_event.wait()
|
|
48
|
+
|
|
49
|
+
def is_connected(self) -> bool:
|
|
50
|
+
return self._connected_event.is_set()
|
|
51
|
+
|
|
52
|
+
async def destroy(self):
|
|
53
|
+
if not self.is_connected():
|
|
54
|
+
return
|
|
55
|
+
self._io_socket = None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def address(self) -> str:
|
|
59
|
+
return f"tcp://{self._host}:{self._port}"
|
|
60
|
+
|
|
61
|
+
async def routine(self):
|
|
62
|
+
await self.wait_until_connected()
|
|
63
|
+
|
|
64
|
+
response = await self.__receive_response()
|
|
65
|
+
if response is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
header, payload = response
|
|
69
|
+
|
|
70
|
+
if header.response_type != ObjectResponseHeader.ObjectResponseType.GetOK:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
pending_get_future = self._pending_get_requests.pop(header.object_id, None)
|
|
74
|
+
|
|
75
|
+
if pending_get_future is None:
|
|
76
|
+
logging.warning(f"unknown get-ok response for unrequested object_id={repr(header.object_id)}.")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
pending_get_future.set_result(payload)
|
|
80
|
+
|
|
81
|
+
async def set_object(self, object_id: ObjectID, payload: bytes) -> None:
|
|
82
|
+
await self.__send_request(object_id, len(payload), ObjectRequestHeader.ObjectRequestType.SetObject, payload)
|
|
83
|
+
|
|
84
|
+
async def get_object(self, object_id: ObjectID, max_payload_length: int = 2**64 - 1) -> bytes:
|
|
85
|
+
pending_get_future = self._pending_get_requests.get(object_id)
|
|
86
|
+
|
|
87
|
+
if pending_get_future is None:
|
|
88
|
+
pending_get_future = asyncio.Future()
|
|
89
|
+
self._pending_get_requests[object_id] = pending_get_future
|
|
90
|
+
|
|
91
|
+
await self.__send_request(
|
|
92
|
+
object_id, max_payload_length, ObjectRequestHeader.ObjectRequestType.GetObject, None
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return await pending_get_future
|
|
96
|
+
|
|
97
|
+
async def delete_object(self, object_id: ObjectID) -> None:
|
|
98
|
+
await self.__send_request(object_id, 0, ObjectRequestHeader.ObjectRequestType.DeleteObject, None)
|
|
99
|
+
|
|
100
|
+
async def duplicate_object_id(self, object_id: ObjectID, new_object_id: ObjectID) -> None:
|
|
101
|
+
object_id_payload = to_capnp_object_id(object_id).to_bytes()
|
|
102
|
+
|
|
103
|
+
await self.__send_request(
|
|
104
|
+
new_object_id,
|
|
105
|
+
len(object_id_payload),
|
|
106
|
+
ObjectRequestHeader.ObjectRequestType.DuplicateObjectID,
|
|
107
|
+
object_id_payload,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def __ensure_is_connected(self):
|
|
111
|
+
if self._io_socket is None:
|
|
112
|
+
raise ObjectStorageException("connector is not connected.")
|
|
113
|
+
|
|
114
|
+
async def __send_request(
|
|
115
|
+
self,
|
|
116
|
+
object_id: ObjectID,
|
|
117
|
+
payload_length: int,
|
|
118
|
+
request_type: ObjectRequestHeader.ObjectRequestType,
|
|
119
|
+
payload: Optional[bytes],
|
|
120
|
+
):
|
|
121
|
+
self.__ensure_is_connected()
|
|
122
|
+
|
|
123
|
+
request_id = self._next_request_id
|
|
124
|
+
self._next_request_id += 1
|
|
125
|
+
self._next_request_id %= 2**64 - 1 # UINT64_MAX
|
|
126
|
+
|
|
127
|
+
header = ObjectRequestHeader.new_msg(object_id, payload_length, request_id, request_type)
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
async with self._lock:
|
|
131
|
+
await self.__write_request_header(header)
|
|
132
|
+
|
|
133
|
+
if payload is not None:
|
|
134
|
+
await self.__write_request_payload(payload)
|
|
135
|
+
|
|
136
|
+
except YMQException:
|
|
137
|
+
self._io_socket = None
|
|
138
|
+
self.__raise_connection_failure()
|
|
139
|
+
|
|
140
|
+
async def __write_request_header(self, header: ObjectRequestHeader):
|
|
141
|
+
assert self._io_socket is not None
|
|
142
|
+
await self._io_socket.send(Message(address=None, payload=header.get_message().to_bytes()))
|
|
143
|
+
|
|
144
|
+
async def __write_request_payload(self, payload: bytes):
|
|
145
|
+
assert self._io_socket is not None
|
|
146
|
+
await self._io_socket.send(Message(address=None, payload=payload))
|
|
147
|
+
|
|
148
|
+
async def __receive_response(self) -> Optional[Tuple[ObjectResponseHeader, bytes]]:
|
|
149
|
+
if self._io_socket is None:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
header = await self.__read_response_header()
|
|
154
|
+
payload = await self.__read_response_payload(header)
|
|
155
|
+
except YMQException:
|
|
156
|
+
self._io_socket = None
|
|
157
|
+
self.__raise_connection_failure()
|
|
158
|
+
|
|
159
|
+
return header, payload
|
|
160
|
+
|
|
161
|
+
async def __read_response_header(self) -> ObjectResponseHeader:
|
|
162
|
+
assert self._io_socket is not None
|
|
163
|
+
|
|
164
|
+
msg = await self._io_socket.recv()
|
|
165
|
+
header_data = msg.payload.data
|
|
166
|
+
assert len(header_data) == ObjectResponseHeader.MESSAGE_LENGTH
|
|
167
|
+
|
|
168
|
+
with _object_storage.ObjectResponseHeader.from_bytes(header_data) as header_message:
|
|
169
|
+
return ObjectResponseHeader(header_message)
|
|
170
|
+
|
|
171
|
+
async def __read_response_payload(self, header: ObjectResponseHeader) -> bytes:
|
|
172
|
+
assert self._io_socket is not None
|
|
173
|
+
# assert self._reader is not None
|
|
174
|
+
|
|
175
|
+
if header.payload_length > 0:
|
|
176
|
+
res = await self._io_socket.recv()
|
|
177
|
+
assert len(res.payload) == header.payload_length
|
|
178
|
+
return res.payload.data
|
|
179
|
+
else:
|
|
180
|
+
return b""
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def __raise_connection_failure():
|
|
184
|
+
raise ObjectStorageException("connection failure to object storage server.")
|