Source code for FIREQ_CLIENT.network.receive_worker

"""Receive worker thread class."""

import queue
import socket
import struct
import threading

import msgpack

from .protocol import Message


[docs] class ReceiveWorker: """Reads framed messages from a socket in a background thread.""" def __init__(self, sock: socket.socket) -> None: """Initialize the receiver with the socket and a background thread. :param sock: the socket used to receive messages. :type sock: socket.socket """ self._sock = sock self._queue = queue.Queue() self._stop_event = threading.Event() self._thread = threading.Thread(target=self._reader_loop, daemon=True)
[docs] def start(self) -> None: """Start the background reading thread.""" self._thread.start()
[docs] def stop(self, timeout: float | None = None) -> None: """Stop the reading thread and close the socket. :param timeout: how long to wait for the thread to stop. :type timeout: float | None """ self._stop_event.set() try: self._sock.shutdown(0) except OSError: pass self._sock.close() self._thread.join(timeout)
[docs] def get_message(self, block: bool = True, timeout: float | None = None) -> Message: """Retrieve the next message from the queue. :param block: block until a message is available. :type block: bool :param timeout: how long to wait before raising queue.Empty. :type timeout: float | None :return: the next message. :rtype: Message """ return self._queue.get(block, timeout)
@property def message_queue(self) -> queue.Queue: """Return the underlying queue.""" return self._queue def _recv_exactly(self, n: int) -> bytes: """Read exactly n bytes from the socket. :param n: number of bytes to read. :type n: int :return: the read bytes. :rtype: bytes """ data = b"" while len(data) < n: if self._stop_event.is_set(): raise ConnectionError("Stopped by user") try: chunk = self._sock.recv(n - len(data)) except OSError: raise ConnectionError("Socket error") from None if not chunk: raise ConnectionError("Connection closed") data += chunk return data def _reader_loop(self) -> None: """Loop reading framed messages until the worker is stopped.""" try: while not self._stop_event.is_set(): # read the first 4 bytes which define the length of the header size_bytes = self._recv_exactly(4) header_size = struct.unpack("!I", size_bytes)[0] # receive the header header_bytes = self._recv_exactly(header_size) header = msgpack.unpackb(header_bytes, raw=False) # get the size of the traling data and receive it if needed tsize = header.get("tsize") data = self._recv_exactly(tsize) if tsize is not None else b"" self._queue.put(Message(header=header, data=data)) except ( ConnectionError, OSError, struct.error, msgpack.exceptions.ExtraData, msgpack.exceptions.UnpackException, ): if not self._stop_event.is_set(): raise