Further json isolation and endpoint implementation

Further isolated json specifics to their own module under protocols/json

Moved global packet classes to their own protocols/packets module

Implemented ConnectorEndpoint and ConnectorRegistry as transport
agnostic connector endpoints for universal connector handling, exposed a
ConnectorRegistry API that will need to be made available to the
JsonServer for it to be able to register and unregister endpoints as it
opens and closes connections

Implemented a top down shutdown architecture where each object is
responsible for its children, this allows for a logical and heirachical
flow, each object has its own stop event, which when its stop method is
called, it sets and then awaits the stopped event to be set by any
worker threads etc, with a mandatory timeout to avoid hanging on
shutdown
This commit is contained in:
2026-08-03 22:56:21 +01:00
parent 482c093f38
commit 03279597e7
11 changed files with 198 additions and 26 deletions
+119
View File
@@ -0,0 +1,119 @@
# Copyright (C) 2026 Hector van der Aa <hector@h3cx.dev>
# Copyright (C) 2026 Association Exergie <association.exergie@gmail.com>
# SPDX-License-Identifier: GPL-3.0-or-later
from queue import Empty, Full, Queue
from threading import RLock, Thread
import threading
from uuid import UUID
from dynalab_core.protocols.errors import (
ConnectorEndpointQueueFullError,
ConnectorRegistryAlreadyRegisteredError,
)
from dynalab_core.protocols.packets import ProtocolMessage
from dynalab_core.protocols.packets.handshake import ConnectorHello
class ConnectorEndpoint:
def __init__(self, hello: ConnectorHello) -> None:
self._packet_ingress_queue: Queue[ProtocolMessage] = Queue(524288)
self._packet_egress_queue: Queue[ProtocolMessage] = Queue(524288)
self._connector_hello = hello
self._worker_thread = Thread(
target=self._worker,
name=f"endpoint_worker_{self._connector_hello.connector_uuid}",
daemon=True,
)
self._stop_event = threading.Event()
self._stopped_event = threading.Event()
def stop(self) -> None:
self._stop_event.set()
if not self._worker_thread.is_alive():
self._stopped_event.set()
self._stopped_event.wait(10)
def uuid(self) -> UUID:
return self._connector_hello.connector_uuid
def put_ingress_packet(self, packet: ProtocolMessage) -> None:
try:
self._packet_ingress_queue.put_nowait(packet)
except Full:
raise ConnectorEndpointQueueFullError
def get_egress_packet(self, timeout: float | None) -> ProtocolMessage | None:
try:
packet = self._packet_egress_queue.get(block=True, timeout=timeout)
except Empty:
return None
else:
return packet
def get_egress_packet_no_wait(self) -> ProtocolMessage | None:
try:
packet = self._packet_egress_queue.get_nowait()
except Empty:
return None
else:
return packet
def _get_ingress_packet(self, timeout: float | None) -> ProtocolMessage | None:
try:
packet = self._packet_ingress_queue.get(block=True, timeout=timeout)
except Empty:
return None
else:
return packet
def _get_ingress_packet_no_wait(self) -> ProtocolMessage | None:
try:
packet = self._packet_ingress_queue.get_nowait()
except Empty:
return None
else:
return packet
def _put_egress_packet(self, packet: ProtocolMessage) -> None:
try:
self._packet_egress_queue.put_nowait(packet)
except Full:
raise ConnectorEndpointQueueFullError
def _worker(self) -> None:
# TODO: build endpoint worker thread
pass
class ConnectorRegistry:
def __init__(self) -> None:
self._endpoints: dict[UUID, ConnectorEndpoint] = {}
self._lock = RLock()
def register(self, hello: ConnectorHello) -> None:
endpoint = ConnectorEndpoint(hello)
with self._lock:
current = self._endpoints.get(hello.connector_uuid)
if current is not None:
raise ConnectorRegistryAlreadyRegisteredError
self._endpoints[hello.connector_uuid] = endpoint
return endpoint
def unregister(self, endpoint: ConnectorEndpoint) -> None:
with self._lock:
current = self._endpoints.get(endpoint.uuid())
if current is endpoint:
del self._endpoints[endpoint.uuid()]
def get(self, connector_uuid: UUID) -> ConnectorEndpoint | None:
with self._lock:
return self._endpoints.get(connector_uuid)
def stop(self) -> None:
for endpoint in self._endpoints.values():
endpoint.stop()