opengris-scaler 1.12.7__cp311-cp311-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.

Files changed (232) hide show
  1. opengris_scaler-1.12.7.dist-info/METADATA +729 -0
  2. opengris_scaler-1.12.7.dist-info/RECORD +232 -0
  3. opengris_scaler-1.12.7.dist-info/WHEEL +5 -0
  4. opengris_scaler-1.12.7.dist-info/entry_points.txt +9 -0
  5. opengris_scaler-1.12.7.dist-info/licenses/LICENSE +201 -0
  6. opengris_scaler-1.12.7.dist-info/licenses/LICENSE.spdx +7 -0
  7. opengris_scaler-1.12.7.dist-info/licenses/NOTICE +8 -0
  8. opengris_scaler.libs/libcapnp-1-b787335c.1.0.so +0 -0
  9. opengris_scaler.libs/libkj-1-094aa318.1.0.so +0 -0
  10. scaler/CMakeLists.txt +11 -0
  11. scaler/__init__.py +14 -0
  12. scaler/about.py +5 -0
  13. scaler/client/__init__.py +0 -0
  14. scaler/client/agent/__init__.py +0 -0
  15. scaler/client/agent/client_agent.py +210 -0
  16. scaler/client/agent/disconnect_manager.py +27 -0
  17. scaler/client/agent/future_manager.py +112 -0
  18. scaler/client/agent/heartbeat_manager.py +74 -0
  19. scaler/client/agent/mixins.py +89 -0
  20. scaler/client/agent/object_manager.py +98 -0
  21. scaler/client/agent/task_manager.py +64 -0
  22. scaler/client/client.py +635 -0
  23. scaler/client/future.py +252 -0
  24. scaler/client/object_buffer.py +129 -0
  25. scaler/client/object_reference.py +25 -0
  26. scaler/client/serializer/__init__.py +0 -0
  27. scaler/client/serializer/default.py +16 -0
  28. scaler/client/serializer/mixins.py +38 -0
  29. scaler/cluster/__init__.py +0 -0
  30. scaler/cluster/cluster.py +115 -0
  31. scaler/cluster/combo.py +148 -0
  32. scaler/cluster/object_storage_server.py +45 -0
  33. scaler/cluster/scheduler.py +83 -0
  34. scaler/config/__init__.py +0 -0
  35. scaler/config/defaults.py +87 -0
  36. scaler/config/loader.py +95 -0
  37. scaler/config/mixins.py +15 -0
  38. scaler/config/section/__init__.py +0 -0
  39. scaler/config/section/cluster.py +56 -0
  40. scaler/config/section/native_worker_adapter.py +44 -0
  41. scaler/config/section/object_storage_server.py +7 -0
  42. scaler/config/section/scheduler.py +53 -0
  43. scaler/config/section/symphony_worker_adapter.py +47 -0
  44. scaler/config/section/top.py +13 -0
  45. scaler/config/section/webui.py +16 -0
  46. scaler/config/types/__init__.py +0 -0
  47. scaler/config/types/object_storage_server.py +45 -0
  48. scaler/config/types/worker.py +57 -0
  49. scaler/config/types/zmq.py +79 -0
  50. scaler/entry_points/__init__.py +0 -0
  51. scaler/entry_points/cluster.py +133 -0
  52. scaler/entry_points/object_storage_server.py +41 -0
  53. scaler/entry_points/scheduler.py +135 -0
  54. scaler/entry_points/top.py +286 -0
  55. scaler/entry_points/webui.py +26 -0
  56. scaler/entry_points/worker_adapter_native.py +137 -0
  57. scaler/entry_points/worker_adapter_symphony.py +102 -0
  58. scaler/io/__init__.py +0 -0
  59. scaler/io/async_binder.py +85 -0
  60. scaler/io/async_connector.py +95 -0
  61. scaler/io/async_object_storage_connector.py +185 -0
  62. scaler/io/mixins.py +154 -0
  63. scaler/io/sync_connector.py +68 -0
  64. scaler/io/sync_object_storage_connector.py +185 -0
  65. scaler/io/sync_subscriber.py +83 -0
  66. scaler/io/utility.py +31 -0
  67. scaler/io/ymq/CMakeLists.txt +98 -0
  68. scaler/io/ymq/__init__.py +0 -0
  69. scaler/io/ymq/_ymq.pyi +96 -0
  70. scaler/io/ymq/_ymq.so +0 -0
  71. scaler/io/ymq/bytes.h +114 -0
  72. scaler/io/ymq/common.h +29 -0
  73. scaler/io/ymq/configuration.h +60 -0
  74. scaler/io/ymq/epoll_context.cpp +185 -0
  75. scaler/io/ymq/epoll_context.h +85 -0
  76. scaler/io/ymq/error.h +132 -0
  77. scaler/io/ymq/event_loop.h +55 -0
  78. scaler/io/ymq/event_loop_thread.cpp +64 -0
  79. scaler/io/ymq/event_loop_thread.h +46 -0
  80. scaler/io/ymq/event_manager.h +81 -0
  81. scaler/io/ymq/file_descriptor.h +203 -0
  82. scaler/io/ymq/interruptive_concurrent_queue.h +169 -0
  83. scaler/io/ymq/io_context.cpp +98 -0
  84. scaler/io/ymq/io_context.h +44 -0
  85. scaler/io/ymq/io_socket.cpp +299 -0
  86. scaler/io/ymq/io_socket.h +121 -0
  87. scaler/io/ymq/iocp_context.cpp +102 -0
  88. scaler/io/ymq/iocp_context.h +83 -0
  89. scaler/io/ymq/logging.h +163 -0
  90. scaler/io/ymq/message.h +15 -0
  91. scaler/io/ymq/message_connection.h +16 -0
  92. scaler/io/ymq/message_connection_tcp.cpp +672 -0
  93. scaler/io/ymq/message_connection_tcp.h +96 -0
  94. scaler/io/ymq/network_utils.h +179 -0
  95. scaler/io/ymq/pymod_ymq/bytes.h +113 -0
  96. scaler/io/ymq/pymod_ymq/exception.h +124 -0
  97. scaler/io/ymq/pymod_ymq/gil.h +15 -0
  98. scaler/io/ymq/pymod_ymq/io_context.h +166 -0
  99. scaler/io/ymq/pymod_ymq/io_socket.h +285 -0
  100. scaler/io/ymq/pymod_ymq/message.h +99 -0
  101. scaler/io/ymq/pymod_ymq/python.h +153 -0
  102. scaler/io/ymq/pymod_ymq/ymq.cpp +23 -0
  103. scaler/io/ymq/pymod_ymq/ymq.h +357 -0
  104. scaler/io/ymq/readme.md +114 -0
  105. scaler/io/ymq/simple_interface.cpp +80 -0
  106. scaler/io/ymq/simple_interface.h +24 -0
  107. scaler/io/ymq/tcp_client.cpp +367 -0
  108. scaler/io/ymq/tcp_client.h +75 -0
  109. scaler/io/ymq/tcp_operations.h +41 -0
  110. scaler/io/ymq/tcp_server.cpp +410 -0
  111. scaler/io/ymq/tcp_server.h +79 -0
  112. scaler/io/ymq/third_party/concurrentqueue.h +3747 -0
  113. scaler/io/ymq/timed_queue.h +272 -0
  114. scaler/io/ymq/timestamp.h +102 -0
  115. scaler/io/ymq/typedefs.h +20 -0
  116. scaler/io/ymq/utils.h +34 -0
  117. scaler/io/ymq/ymq.py +130 -0
  118. scaler/object_storage/CMakeLists.txt +50 -0
  119. scaler/object_storage/__init__.py +0 -0
  120. scaler/object_storage/constants.h +11 -0
  121. scaler/object_storage/defs.h +14 -0
  122. scaler/object_storage/io_helper.cpp +44 -0
  123. scaler/object_storage/io_helper.h +9 -0
  124. scaler/object_storage/message.cpp +56 -0
  125. scaler/object_storage/message.h +130 -0
  126. scaler/object_storage/object_manager.cpp +126 -0
  127. scaler/object_storage/object_manager.h +52 -0
  128. scaler/object_storage/object_storage_server.cpp +359 -0
  129. scaler/object_storage/object_storage_server.h +126 -0
  130. scaler/object_storage/object_storage_server.so +0 -0
  131. scaler/object_storage/pymod_object_storage_server.cpp +104 -0
  132. scaler/protocol/__init__.py +0 -0
  133. scaler/protocol/capnp/__init__.py +0 -0
  134. scaler/protocol/capnp/_python.py +6 -0
  135. scaler/protocol/capnp/common.capnp +63 -0
  136. scaler/protocol/capnp/message.capnp +216 -0
  137. scaler/protocol/capnp/object_storage.capnp +52 -0
  138. scaler/protocol/capnp/status.capnp +73 -0
  139. scaler/protocol/introduction.md +105 -0
  140. scaler/protocol/python/__init__.py +0 -0
  141. scaler/protocol/python/common.py +135 -0
  142. scaler/protocol/python/message.py +726 -0
  143. scaler/protocol/python/mixins.py +13 -0
  144. scaler/protocol/python/object_storage.py +118 -0
  145. scaler/protocol/python/status.py +279 -0
  146. scaler/protocol/worker.md +228 -0
  147. scaler/scheduler/__init__.py +0 -0
  148. scaler/scheduler/allocate_policy/__init__.py +0 -0
  149. scaler/scheduler/allocate_policy/allocate_policy.py +9 -0
  150. scaler/scheduler/allocate_policy/capability_allocate_policy.py +280 -0
  151. scaler/scheduler/allocate_policy/even_load_allocate_policy.py +159 -0
  152. scaler/scheduler/allocate_policy/mixins.py +55 -0
  153. scaler/scheduler/controllers/__init__.py +0 -0
  154. scaler/scheduler/controllers/balance_controller.py +65 -0
  155. scaler/scheduler/controllers/client_controller.py +131 -0
  156. scaler/scheduler/controllers/config_controller.py +31 -0
  157. scaler/scheduler/controllers/graph_controller.py +424 -0
  158. scaler/scheduler/controllers/information_controller.py +81 -0
  159. scaler/scheduler/controllers/mixins.py +201 -0
  160. scaler/scheduler/controllers/object_controller.py +147 -0
  161. scaler/scheduler/controllers/scaling_controller.py +86 -0
  162. scaler/scheduler/controllers/task_controller.py +373 -0
  163. scaler/scheduler/controllers/worker_controller.py +168 -0
  164. scaler/scheduler/object_usage/__init__.py +0 -0
  165. scaler/scheduler/object_usage/object_tracker.py +131 -0
  166. scaler/scheduler/scheduler.py +253 -0
  167. scaler/scheduler/task/__init__.py +0 -0
  168. scaler/scheduler/task/task_state_machine.py +92 -0
  169. scaler/scheduler/task/task_state_manager.py +61 -0
  170. scaler/ui/__init__.py +0 -0
  171. scaler/ui/constants.py +9 -0
  172. scaler/ui/live_display.py +118 -0
  173. scaler/ui/memory_window.py +146 -0
  174. scaler/ui/setting_page.py +47 -0
  175. scaler/ui/task_graph.py +370 -0
  176. scaler/ui/task_log.py +83 -0
  177. scaler/ui/utility.py +35 -0
  178. scaler/ui/webui.py +125 -0
  179. scaler/ui/worker_processors.py +85 -0
  180. scaler/utility/__init__.py +0 -0
  181. scaler/utility/debug.py +19 -0
  182. scaler/utility/event_list.py +63 -0
  183. scaler/utility/event_loop.py +58 -0
  184. scaler/utility/exceptions.py +42 -0
  185. scaler/utility/formatter.py +44 -0
  186. scaler/utility/graph/__init__.py +0 -0
  187. scaler/utility/graph/optimization.py +27 -0
  188. scaler/utility/graph/topological_sorter.py +11 -0
  189. scaler/utility/graph/topological_sorter_graphblas.py +174 -0
  190. scaler/utility/identifiers.py +105 -0
  191. scaler/utility/logging/__init__.py +0 -0
  192. scaler/utility/logging/decorators.py +25 -0
  193. scaler/utility/logging/scoped_logger.py +33 -0
  194. scaler/utility/logging/utility.py +183 -0
  195. scaler/utility/many_to_many_dict.py +123 -0
  196. scaler/utility/metadata/__init__.py +0 -0
  197. scaler/utility/metadata/profile_result.py +31 -0
  198. scaler/utility/metadata/task_flags.py +30 -0
  199. scaler/utility/mixins.py +13 -0
  200. scaler/utility/network_util.py +7 -0
  201. scaler/utility/one_to_many_dict.py +72 -0
  202. scaler/utility/queues/__init__.py +0 -0
  203. scaler/utility/queues/async_indexed_queue.py +37 -0
  204. scaler/utility/queues/async_priority_queue.py +70 -0
  205. scaler/utility/queues/async_sorted_priority_queue.py +45 -0
  206. scaler/utility/queues/indexed_queue.py +114 -0
  207. scaler/utility/serialization.py +9 -0
  208. scaler/version.txt +1 -0
  209. scaler/worker/__init__.py +0 -0
  210. scaler/worker/agent/__init__.py +0 -0
  211. scaler/worker/agent/heartbeat_manager.py +107 -0
  212. scaler/worker/agent/mixins.py +137 -0
  213. scaler/worker/agent/processor/__init__.py +0 -0
  214. scaler/worker/agent/processor/object_cache.py +107 -0
  215. scaler/worker/agent/processor/processor.py +279 -0
  216. scaler/worker/agent/processor/streaming_buffer.py +28 -0
  217. scaler/worker/agent/processor_holder.py +145 -0
  218. scaler/worker/agent/processor_manager.py +365 -0
  219. scaler/worker/agent/profiling_manager.py +109 -0
  220. scaler/worker/agent/task_manager.py +150 -0
  221. scaler/worker/agent/timeout_manager.py +19 -0
  222. scaler/worker/preload.py +84 -0
  223. scaler/worker/worker.py +264 -0
  224. scaler/worker_adapter/__init__.py +0 -0
  225. scaler/worker_adapter/native.py +154 -0
  226. scaler/worker_adapter/symphony/__init__.py +0 -0
  227. scaler/worker_adapter/symphony/callback.py +45 -0
  228. scaler/worker_adapter/symphony/heartbeat_manager.py +79 -0
  229. scaler/worker_adapter/symphony/message.py +24 -0
  230. scaler/worker_adapter/symphony/task_manager.py +288 -0
  231. scaler/worker_adapter/symphony/worker.py +205 -0
  232. scaler/worker_adapter/symphony/worker_adapter.py +142 -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.io.mixins import SyncSubscriber
8
+ from scaler.io.utility import deserialize
9
+ from scaler.protocol.python.mixins import Message
10
+ from scaler.config.types.zmq import ZMQConfig
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,31 @@
1
+ import logging
2
+ from typing import List, Optional
3
+
4
+ from scaler.config.defaults import CAPNP_DATA_SIZE_LIMIT, CAPNP_MESSAGE_SIZE_LIMIT
5
+ from scaler.protocol.capnp._python import _message # noqa
6
+ from scaler.protocol.python.message import PROTOCOL
7
+ from scaler.protocol.python.mixins import Message
8
+
9
+
10
+ def deserialize(data: bytes) -> Optional[Message]:
11
+ with _message.Message.from_bytes(data, traversal_limit_in_words=CAPNP_MESSAGE_SIZE_LIMIT) as payload:
12
+ if not hasattr(payload, payload.which()):
13
+ logging.error(f"unknown message type: {payload.which()}")
14
+ return None
15
+
16
+ message = getattr(payload, payload.which())
17
+ return PROTOCOL[payload.which()](message)
18
+
19
+
20
+ def serialize(message: Message) -> bytes:
21
+ payload = _message.Message(**{PROTOCOL.inverse[type(message)]: message.get_message()})
22
+ return payload.to_bytes()
23
+
24
+
25
+ def chunk_to_list_of_bytes(data: bytes) -> List[bytes]:
26
+ # TODO: change to list of memoryview when capnp can support memoryview
27
+ return [data[i : i + CAPNP_DATA_SIZE_LIMIT] for i in range(0, len(data), CAPNP_DATA_SIZE_LIMIT)]
28
+
29
+
30
+ def concat_list_of_bytes(data: List[bytes]) -> bytes:
31
+ return bytearray().join(data)
@@ -0,0 +1,98 @@
1
+ add_library(ymq_objs OBJECT
2
+ bytes.h
3
+ common.h
4
+ configuration.h
5
+
6
+ epoll_context.h
7
+ epoll_context.cpp
8
+
9
+ iocp_context.h
10
+ iocp_context.cpp
11
+
12
+ event_loop.h
13
+
14
+ event_loop_thread.h
15
+ event_loop_thread.cpp
16
+
17
+ event_manager.h
18
+ # file_descriptor.h
19
+
20
+ message_connection.h
21
+ message_connection_tcp.h
22
+ message_connection_tcp.cpp
23
+
24
+ third_party/concurrentqueue.h
25
+ interruptive_concurrent_queue.h
26
+
27
+ typedefs.h
28
+
29
+ io_context.h
30
+ io_context.cpp
31
+
32
+ io_socket.h
33
+ io_socket.cpp
34
+
35
+ tcp_server.h
36
+ tcp_server.cpp
37
+
38
+ tcp_client.h
39
+ tcp_client.cpp
40
+
41
+ tcp_operations.h
42
+
43
+ timestamp.h
44
+
45
+ timed_queue.h
46
+
47
+ network_utils.h
48
+
49
+ error.h
50
+
51
+ logging.h
52
+
53
+ simple_interface.h
54
+ simple_interface.cpp
55
+ )
56
+
57
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/scaler/io/ymq)
58
+
59
+ if(LINUX)
60
+ # ymq python =======================================================================================================
61
+ find_package(Python3 COMPONENTS Development.Module REQUIRED)
62
+
63
+ add_library(py_ymq SHARED
64
+ pymod_ymq/bytes.h
65
+ pymod_ymq/exception.h
66
+ pymod_ymq/gil.h
67
+ pymod_ymq/message.h
68
+ pymod_ymq/io_context.h
69
+ pymod_ymq/io_socket.h
70
+ pymod_ymq/ymq.h
71
+ pymod_ymq/ymq.cpp
72
+ )
73
+
74
+ # target_include_directories(py_ymq PRIVATE ${Python3_INCLUDE_DIRS})
75
+ target_link_libraries(py_ymq PRIVATE
76
+ $<$<CXX_COMPILER_ID:GNU,Clang>:-Wl,-Bstatic> ymq_objs
77
+ $<$<CXX_COMPILER_ID:GNU,Clang>:-Wl,-Bdynamic>
78
+ Python3::Module
79
+ )
80
+
81
+ set_target_properties(py_ymq PROPERTIES
82
+ PREFIX ""
83
+ OUTPUT_NAME "_ymq"
84
+ LINKER_LANGUAGE CXX
85
+ )
86
+
87
+ install(
88
+ TARGETS py_ymq
89
+ LIBRARY DESTINATION scaler/io/ymq
90
+ )
91
+ endif()
92
+
93
+ if(WIN32)
94
+ # ymq python =======================================================================================================
95
+
96
+ target_link_libraries(ymq_objs PRIVATE "ws2_32")
97
+ target_compile_definitions(ymq_objs PRIVATE _WINSOCKAPI_=) # Yes, trailing equal to guarantee empty def
98
+ endif()
File without changes
scaler/io/ymq/_ymq.pyi ADDED
@@ -0,0 +1,96 @@
1
+ # NOTE: NOT IMPLEMENTATION, TYPE INFORMATION ONLY
2
+ # This file contains type stubs for the Ymq Python C Extension module
3
+ import sys
4
+ from enum import IntEnum
5
+ from typing import Callable, Optional, SupportsBytes, Union
6
+
7
+ if sys.version_info >= (3, 12):
8
+ from collections.abc import Buffer
9
+ else:
10
+ Buffer = object
11
+
12
+ class Bytes(Buffer):
13
+ data: bytes | None
14
+ len: int
15
+
16
+ def __init__(self, data: Buffer | None = None) -> None: ...
17
+ def __repr__(self) -> str: ...
18
+ def __len__(self) -> int: ...
19
+
20
+ # this type signature is not 100% accurate because it's implemented in C
21
+ # but this satisfies the type check and is good enough
22
+ def __buffer__(self, flags: int, /) -> memoryview: ...
23
+
24
+ class Message:
25
+ address: Bytes | None
26
+ payload: Bytes
27
+
28
+ def __init__(
29
+ self, address: Bytes | bytes | SupportsBytes | None, payload: Bytes | bytes | SupportsBytes
30
+ ) -> None: ...
31
+ def __repr__(self) -> str: ...
32
+ def __str__(self) -> str: ...
33
+
34
+ class IOSocketType(IntEnum):
35
+ Uninit = 0
36
+ Binder = 1
37
+ Connector = 2
38
+ Unicast = 3
39
+ Multicast = 4
40
+
41
+ class BaseIOContext:
42
+ num_threads: int
43
+
44
+ def __init__(self, num_threads: int = 1) -> None: ...
45
+ def __repr__(self) -> str: ...
46
+ def createIOSocket(
47
+ self, callback: Callable[[Union[BaseIOSocket, Exception]], None], identity: str, socket_type: IOSocketType
48
+ ) -> None:
49
+ """Create an io socket with an identity and socket type"""
50
+
51
+ class BaseIOSocket:
52
+ identity: str
53
+ socket_type: IOSocketType
54
+
55
+ def __repr__(self) -> str: ...
56
+ def send(self, callback: Callable[[Optional[Exception]], None], message: Message) -> None:
57
+ """Send a message to one of the socket's peers"""
58
+
59
+ def recv(self, callback: Callable[[Union[Message, Exception]], None]) -> None:
60
+ """Receive a message from one of the socket's peers"""
61
+
62
+ def bind(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
63
+ """Bind the socket to an address and listen for incoming connections"""
64
+
65
+ def connect(self, callback: Callable[[Optional[Exception]], None], address: str) -> None:
66
+ """Connect to a remote socket"""
67
+
68
+ class ErrorCode(IntEnum):
69
+ Uninit = 0
70
+ InvalidPortFormat = 1
71
+ InvalidAddressFormat = 2
72
+ ConfigurationError = 3
73
+ SignalNotSupported = 4
74
+ CoreBug = 5
75
+ RepetetiveIOSocketIdentity = 6
76
+ RedundantIOSocketRefCount = 7
77
+ MultipleConnectToNotSupported = 8
78
+ MultipleBindToNotSupported = 9
79
+ InitialConnectFailedWithInProgress = 10
80
+ SendMessageRequestCouldNotComplete = 11
81
+ SetSockOptNonFatalFailure = 12
82
+ IPv6NotSupported = 13
83
+ RemoteEndDisconnectedOnSocketWithoutGuaranteedDelivery = 14
84
+ ConnectorSocketClosedByRemoteEnd = 15
85
+ IOSocketStopRequested = 16
86
+ BinderSendMessageWithNoAddress = 17
87
+
88
+ def explanation(self) -> str: ...
89
+
90
+ class YMQException(Exception):
91
+ code: ErrorCode
92
+ message: str
93
+
94
+ def __init__(self, /, code: ErrorCode, message: str) -> None: ...
95
+ def __repr__(self) -> str: ...
96
+ def __str__(self) -> str: ...
scaler/io/ymq/_ymq.so ADDED
Binary file
scaler/io/ymq/bytes.h ADDED
@@ -0,0 +1,114 @@
1
+ #pragma once
2
+
3
+ // C
4
+ #include <string.h> // memcmp
5
+
6
+ #include <algorithm>
7
+ #include <compare>
8
+ #include <cstddef>
9
+ #include <cstdint>
10
+ #include <cstring>
11
+
12
+ // C++
13
+ #include <optional>
14
+ #include <string>
15
+
16
+ // First-party
17
+ #include "scaler/io/ymq/common.h"
18
+
19
+ // TODO: This is not in the namespace
20
+ class Bytes {
21
+ uint8_t* _data;
22
+ size_t _len;
23
+
24
+ void free()
25
+ {
26
+ if (is_null())
27
+ return;
28
+ delete[] _data;
29
+ _data = nullptr;
30
+ }
31
+
32
+ explicit Bytes(uint8_t* data, size_t len): _data(data), _len(len) {}
33
+
34
+ public:
35
+ Bytes(char* data, size_t len): _data(datadup((uint8_t*)data, len)), _len(len) {}
36
+
37
+ explicit Bytes(const std::string& s): _data(datadup((uint8_t*)s.data(), s.length())), _len(s.length()) {}
38
+
39
+ Bytes(): _data {}, _len {} {}
40
+
41
+ Bytes(const Bytes& other) noexcept
42
+ {
43
+ this->_data = datadup(other._data, other._len);
44
+ this->_len = other._len;
45
+ }
46
+
47
+ Bytes& operator=(const Bytes& other) noexcept
48
+ {
49
+ Bytes tmp(other);
50
+ swap(*this, tmp);
51
+ return *this;
52
+ }
53
+
54
+ friend void swap(Bytes& x, Bytes& y) noexcept
55
+ {
56
+ using std::swap;
57
+ swap(x._len, y._len);
58
+ swap(x._data, y._data);
59
+ }
60
+
61
+ Bytes(Bytes&& other) noexcept: _data(other._data), _len(other._len)
62
+ {
63
+ other._data = nullptr;
64
+ other._len = 0;
65
+ }
66
+
67
+ friend std::strong_ordering operator<=>(const Bytes& x, const Bytes& y) noexcept
68
+ {
69
+ return std::lexicographical_compare_three_way(x._data, x._data + x._len, y._data, y._data + y._len);
70
+ }
71
+
72
+ // https://stackoverflow.com/questions/68221024/why-must-i-provide-operator-when-operator-is-enough
73
+ friend bool operator==(const Bytes& x, const Bytes& y) noexcept { return x <=> y == 0; }
74
+
75
+ Bytes& operator=(Bytes&& other) noexcept
76
+ {
77
+ if (this != &other) {
78
+ this->free(); // free current data
79
+
80
+ _data = other._data;
81
+ _len = other._len;
82
+
83
+ other._data = nullptr;
84
+ other._len = 0;
85
+ }
86
+ return *this;
87
+ }
88
+
89
+ ~Bytes() { this->free(); }
90
+
91
+ [[nodiscard]] constexpr bool operator!() const noexcept { return is_null(); }
92
+
93
+ [[nodiscard]] constexpr bool is_null() const noexcept { return !this->_data; }
94
+
95
+ std::optional<std::string> as_string() const
96
+ {
97
+ if (is_null())
98
+ return std::nullopt;
99
+
100
+ return std::string((char*)_data, _len);
101
+ }
102
+
103
+ [[nodiscard("Allocated Bytes is not used, likely causing a memory leak")]]
104
+ static Bytes alloc(size_t len) noexcept
105
+ {
106
+ auto ptr = new uint8_t[len]; // we just assume the allocation will succeed
107
+ return Bytes {ptr, len};
108
+ }
109
+
110
+ [[nodiscard]] constexpr size_t len() const { return _len; }
111
+ [[nodiscard]] constexpr size_t size() const { return _len; }
112
+ [[nodiscard]] constexpr const uint8_t* data() const { return _data; }
113
+ [[nodiscard]] constexpr uint8_t* data() { return _data; }
114
+ };
scaler/io/ymq/common.h ADDED
@@ -0,0 +1,29 @@
1
+ #pragma once
2
+
3
+ // C++
4
+ #include <cstdint>
5
+ #include <cstdlib>
6
+ #include <cstring>
7
+
8
+ using Errno = int;
9
+
10
+ [[nodiscard("Memory is allocated but not used, likely causing a memory leak")]]
11
+ inline uint8_t* datadup(const uint8_t* data, size_t len) noexcept
12
+ {
13
+ uint8_t* dup = new uint8_t[len]; // we just assume allocation will succeed
14
+ std::memcpy(dup, data, len);
15
+ return dup;
16
+ }
17
+
18
+ inline void serialize_u32(uint32_t x, uint8_t buffer[4])
19
+ {
20
+ buffer[0] = x & 0xFF;
21
+ buffer[1] = (x >> 8) & 0xFF;
22
+ buffer[2] = (x >> 16) & 0xFF;
23
+ buffer[3] = (x >> 24) & 0xFF;
24
+ }
25
+
26
+ inline void deserialize_u32(const uint8_t buffer[4], uint32_t* x)
27
+ {
28
+ *x = buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24;
29
+ }
@@ -0,0 +1,60 @@
1
+ #pragma once
2
+
3
+ // C++
4
+ #include <expected>
5
+ #include <functional>
6
+ #include <memory>
7
+ #include <string>
8
+
9
+ // Because the devil says "You shall live with errors".
10
+ // ^-- The linker complains when the file is not here.
11
+ #include "scaler/io/ymq/error.h"
12
+
13
+ // Because the devil casts spells in plain English.
14
+ #ifdef _WIN32
15
+ #undef SendMessageCallback
16
+ #define __PRETTY_FUNCTION__ __FUNCSIG__
17
+ #endif // _WIN32
18
+
19
+ namespace scaler {
20
+ namespace ymq {
21
+
22
+ class EpollContext;
23
+ class IocpContext;
24
+ class Message;
25
+ class IOSocket;
26
+
27
+ // Use feature-test macro to detect support for std::move_only_function.
28
+ // This works across GCC, Clang, and MSVC on all platforms.
29
+ #if defined(__cpp_lib_move_only_function) && __cpp_lib_move_only_function >= 202110L
30
+ template <typename T>
31
+ using MoveOnlyFunction = std::move_only_function<T>;
32
+ #else
33
+ template <typename T>
34
+ using MoveOnlyFunction = std::function<T>;
35
+ #endif
36
+
37
+ constexpr const uint64_t IOCP_SOCKET_CLOSED = 4;
38
+ constexpr const uint64_t LARGEST_PAYLOAD_SIZE = 6000'000'000'000; // 6TB
39
+
40
+ struct Configuration {
41
+ #ifdef __linux__
42
+ using PollingContext = EpollContext;
43
+ #endif // __linux__
44
+ #ifdef _WIN32
45
+ using PollingContext = IocpContext;
46
+ #endif // _WIN32
47
+
48
+ using IOSocketIdentity = std::string;
49
+ using SendMessageCallback = MoveOnlyFunction<void(std::expected<void, Error>)>;
50
+ using RecvMessageCallback = MoveOnlyFunction<void(std::pair<Message, Error>)>;
51
+ using ConnectReturnCallback = MoveOnlyFunction<void(std::expected<void, Error>)>;
52
+ using BindReturnCallback = MoveOnlyFunction<void(std::expected<void, Error>)>;
53
+ using CreateIOSocketCallback = MoveOnlyFunction<void(std::shared_ptr<IOSocket>)>;
54
+ using TimedQueueCallback = MoveOnlyFunction<void()>;
55
+ using ExecutionFunction = MoveOnlyFunction<void()>;
56
+ using ExecutionCancellationIdentifier = size_t;
57
+ };
58
+
59
+ } // namespace ymq
60
+ } // namespace scaler
@@ -0,0 +1,185 @@
1
+ #ifdef __linux__
2
+
3
+ #include "scaler/io/ymq/epoll_context.h"
4
+
5
+ #include <sys/epoll.h>
6
+
7
+ #include <cerrno>
8
+ #include <functional>
9
+
10
+ #include "scaler/io/ymq/error.h"
11
+ #include "scaler/io/ymq/event_manager.h"
12
+
13
+ namespace scaler {
14
+ namespace ymq {
15
+
16
+ void EpollContext::execPendingFunctions()
17
+ {
18
+ while (_delayedFunctions.size()) {
19
+ auto top = std::move(_delayedFunctions.front());
20
+ top();
21
+ _delayedFunctions.pop();
22
+ }
23
+ }
24
+
25
+ void EpollContext::loop()
26
+ {
27
+ std::array<epoll_event, _reventSize> events {};
28
+ int n = epoll_wait(_epfd, events.data(), _reventSize, -1);
29
+ if (n == -1) {
30
+ const int myErrno = errno;
31
+ switch (myErrno) {
32
+ case EINTR:
33
+ // unrecoverableError({
34
+ // Error::ErrorCode::SignalNotSupported,
35
+ // "Originated from",
36
+ // "epoll_wait(2)",
37
+ // "Errno is",
38
+ // strerror(errno),
39
+ // });
40
+
41
+ // todo: investigate better error handling
42
+ // the epoll thread is not expected to receive signals(?)
43
+ // but occasionally does (e.g. sigwinch) and we shouldn't stop the thread in that case
44
+ break;
45
+ case EBADF:
46
+ case EFAULT:
47
+ case EINVAL:
48
+ default:
49
+ unrecoverableError({
50
+ Error::ErrorCode::CoreBug,
51
+ "Originated from",
52
+ "epoll_wait(2)",
53
+ "Errno is",
54
+ strerror(errno),
55
+ "_epfd",
56
+ _epfd,
57
+ "_reventSize",
58
+ _reventSize,
59
+ });
60
+ break;
61
+ }
62
+ }
63
+
64
+ for (auto it = events.begin(); it != events.begin() + n; ++it) {
65
+ epoll_event current_event = *it;
66
+ auto* event = (EventManager*)current_event.data.ptr;
67
+ if (event == (void*)_isInterruptiveFd) {
68
+ auto vec = _interruptiveFunctions.dequeue();
69
+ std::ranges::for_each(vec, [](auto&& x) { x(); });
70
+ } else if (event == (void*)_isTimingFd) {
71
+ auto vec = _timingFunctions.dequeue();
72
+ std::ranges::for_each(vec, [](auto& x) { x(); });
73
+ } else {
74
+ event->onEvents(current_event.events);
75
+ }
76
+ }
77
+
78
+ execPendingFunctions();
79
+ }
80
+
81
+ void EpollContext::addFdToLoop(int fd, uint64_t events, EventManager* manager)
82
+ {
83
+ epoll_event event {};
84
+ event.events = (int)events & (EPOLLIN | EPOLLOUT | EPOLLET);
85
+ event.data.ptr = (void*)manager;
86
+ int res = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &event);
87
+ if (res == 0) {
88
+ return;
89
+ }
90
+
91
+ if (errno == EEXIST) {
92
+ if (epoll_ctl(_epfd, EPOLL_CTL_MOD, fd, &event) == 0) {
93
+ return;
94
+ }
95
+ }
96
+
97
+ const int myErrno = errno;
98
+ switch (myErrno) {
99
+ case ENOMEM:
100
+ case ENOSPC:
101
+ unrecoverableError({
102
+ Error::ErrorCode::ConfigurationError,
103
+ "Originated from",
104
+ "epoll_ctl(2)",
105
+ "Errno is",
106
+ strerror(errno),
107
+ "_epfd",
108
+ _epfd,
109
+ "fd",
110
+ fd,
111
+ "event.events",
112
+ (int)event.events,
113
+ });
114
+ break;
115
+
116
+ case EBADF:
117
+ case EPERM:
118
+ case EINVAL:
119
+ case ELOOP:
120
+ case ENOENT:
121
+ default:
122
+ unrecoverableError({
123
+ Error::ErrorCode::CoreBug,
124
+ "Originated from",
125
+ "epoll_ctl(2)",
126
+ "Errno is",
127
+ strerror(errno),
128
+ "_epfd",
129
+ _epfd,
130
+ "fd",
131
+ fd,
132
+ "event.events",
133
+ (int)event.events,
134
+ });
135
+ break;
136
+ }
137
+ }
138
+
139
+ void EpollContext::removeFdFromLoop(int fd)
140
+ {
141
+ if (epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr) == 0) {
142
+ return;
143
+ }
144
+ const int myErrno = errno;
145
+ switch (myErrno) {
146
+ case ENOMEM:
147
+ case ENOSPC:
148
+ unrecoverableError({
149
+ Error::ErrorCode::ConfigurationError,
150
+ "Originated from",
151
+ __PRETTY_FUNCTION__,
152
+ "Errno is",
153
+ strerror(errno),
154
+ "_epfd",
155
+ _epfd,
156
+ "fd",
157
+ fd,
158
+ });
159
+ break;
160
+
161
+ case EBADF:
162
+ case EPERM:
163
+ case EINVAL:
164
+ case ELOOP:
165
+ case ENOENT:
166
+ default:
167
+ unrecoverableError({
168
+ Error::ErrorCode::CoreBug,
169
+ "Originated from",
170
+ __PRETTY_FUNCTION__,
171
+ "Errno is",
172
+ strerror(errno),
173
+ "_epfd",
174
+ _epfd,
175
+ "fd",
176
+ fd,
177
+ });
178
+ break;
179
+ }
180
+ }
181
+
182
+ } // namespace ymq
183
+ } // namespace scaler
184
+
185
+ #endif // __linux__