async def _make_pings_task(self): """ Task that sends pings to all subscriptions in the queue. All subs in this queue have timed out. """ while True: await self._check_timeouts() while not self._ping_pong_tasks.empty(): try: subscriber = await self._ping_pong_tasks.get() if subscriber.is_dead(): self._delete_subscription(subscriber) else: await subscriber.ping() except (ConnectionResetError, BrokenPipeError): Log.err(f'Connection unexpectedly closed {subscriber}') self._delete_subscription(subscriber) finally: # We want to remove the task from the queue regardless # if it fails or completes. self._ping_pong_tasks.task_done() # Go idle so other tasks can run. await asyncio.sleep(TASK_DELAY_PING)
async def _enter_ping_pong(self) -> None: self._alive = True while self._alive: packet = await self.read() if packet.cmd == protocol.Commands.NEW_DATA: # New data published! await self._ack_new_data() # Unpack the data and send it to callback. data = packet.data.decode('utf-8') if self._data_received is not None: self._data_received(data) else: Log.err(f'No callback for new data provided!\n{data}') else: # Expecting a ping if not await protocol.async_cmd_ok(packet, protocol.Commands.PING): Log.err('Failed to get ping command. Exiting.') return # Send a PONG back. await self._pong() Log.debug('[Client] Pong') # If provided, call pong callback. if self._pong_callback is not None: self._pong_callback() await asyncio.sleep(SLEEP_DELAY)
async def start(self, topic: str) -> bool: """ Starts the subscription. param data_received is a callback that will be called when new data arrvies at the subscribed topic. """ # Start connection and initialize a new Subscription. await self.open() await self.send(protocol.Commands.SUBSCRIBE) # Wait for ACK, before sending the topic. packet = await self.read() if not await protocol.async_cmd_ok( packet, protocol.Commands.SUBSCRIBE_ACK, self._writer): return # Send the topic we want to subscribe to. await self.send(protocol.Commands.SUBSCRIBE, data=topic.encode('utf-8')) # Wait for OK/NOT OK. sub_ack = await self.read() if not await protocol.async_cmd_ok(sub_ack, protocol.Commands.SUBSCRIBE_OK): Log.err(f'Failed to subscribe to {topic}. Got no ACK.') return False # Enter ping-pong state where we just wait for data published to # the chosen topic and give the data to the callback function provided. await self._enter_ping_pong()
def _set_identifier(self, topic: str) -> None: """ Sets the identification of the subscription. This consists of: 1. Topic 2. File descripter number from reader/writer stream. """ self.topic = topic try: self.fd = self._writer.get_extra_info('socket').fileno() except AttributeError: # Streams are incorrect Log.err(f'Incorrect streams to subscription to {self.topic}') self.fd = None
async def ping(self) -> None: """ Pings the subscriber and waits for a PONG back. If the subscriber doesn't pong back, the subscription is closed. """ await protocol.send_packet(self._writer, protocol.Commands.PING) Log.debug(f'Ping {self}') pong = await protocol.read_packet(self._reader) if await protocol.async_cmd_ok(pong, protocol.Commands.PONG): # If PONG, reset timer. self._time = 0 else: Log.err(f'Bad ping! {self._alive} -> {self._state}') # If no PONG, advance to next state, and potentially close. alive = self._next_state() if not alive: self.die()
async def publish(self, topic: str, message: str) -> bool: """ Returns if the publish is succesful or not. Throws ConnectionRefusedError. """ publish = AsyncPublish(self._ip, self._port) try: pub_ok = await publish.start(topic, message) except ConnectionRefusedError: Log.err(f'[Client ]Failed to connect to server {self._ip} ' f'on port {self._port}') return if pub_ok: Log.info(f'[Client] Published "{message}" to topic "{topic}"') else: Log.info(f'[Client] Failed to publish "{message}" to' f' topic "{topic}"') return pub_ok
def get() -> dict: """ Returns the configurations. If can't find the configuration file, default parameters are used and a new one is created on disk. """ success = False # Only need to read the config once (assumes no live-changes). if Config._config is None: try: with open(CONFIG_PATH, 'rb') as f: Config._config = json.load(f) success = True except FileNotFoundError: Log.err(f'Failed to find config at {CONFIG_PATH}') except json.decoder.JSONDecodeError as e: Log.err(f'Failed to parse JSON {e}') if not success: Config._config = Config.__default Config._save_config(Config._config) return Config._config