From b6da0ebb0d2f4d7078dbbf91d8c03715412d89ea Mon Sep 17 00:00:00 2001 From: Hemna Date: Sat, 15 Feb 2025 18:55:58 -0500 Subject: [PATCH] Fix runaway KISS driver on failed connnection This patch fixes an issue when the KISS connection fails to start and or goes away during the lifetime of the active connection. Aprsd would runaway in a tight loop eating 100% cpu. We now detect when the underlying asyncio connection has failed and raise, which induces a sleep in the consumer to try again. --- aprsd/client/drivers/kiss.py | 33 ++++++++++++++++++++++++++++----- aprsd/client/kiss.py | 1 + 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/aprsd/client/drivers/kiss.py b/aprsd/client/drivers/kiss.py index e5ada24..944a44e 100644 --- a/aprsd/client/drivers/kiss.py +++ b/aprsd/client/drivers/kiss.py @@ -18,12 +18,13 @@ class KISS3Client: # date for last time we heard from the server aprsd_keepalive = datetime.datetime.now() + _connected = False def __init__(self): self.setup() def is_alive(self): - return True + return self._connected def setup(self): # we can be TCP kiss or Serial kiss @@ -56,17 +57,33 @@ class KISS3Client: self.path = CONF.kiss_tcp.path LOG.debug('Starting KISS interface connection') - self.kiss.start() + try: + self.kiss.start() + if self.kiss.protocol.transport.is_closing(): + LOG.warning('KISS transport is closing, not setting consumer callback') + self._connected = False + else: + self._connected = True + except Exception: + LOG.error('Failed to start KISS interface.') + self._connected = False @trace.trace def stop(self): + if not self._connected: + # do nothing since we aren't connected + return + try: self.kiss.stop() self.kiss.loop.call_soon_threadsafe( self.kiss.protocol.transport.close, ) - except Exception as ex: - LOG.exception(ex) + except Exception: + LOG.error('Failed to stop KISS interface.') + + def close(self): + self.stop() def set_filter(self, filter): # This does nothing right now. @@ -86,8 +103,14 @@ class KISS3Client: LOG.exception(ex) def consumer(self, callback): + if not self._connected: + raise Exception('KISS transport is not connected') + self._parse_callback = callback - self.kiss.read(callback=self.parse_frame, min_frames=None) + if not self.kiss.protocol.transport.is_closing(): + self.kiss.read(callback=self.parse_frame, min_frames=1) + else: + self._connected = False def send(self, packet): """Send an APRS Message object.""" diff --git a/aprsd/client/kiss.py b/aprsd/client/kiss.py index e3ba162..cb282a0 100644 --- a/aprsd/client/kiss.py +++ b/aprsd/client/kiss.py @@ -140,3 +140,4 @@ class KISSClient(base.APRSClient): except Exception as ex: LOG.error(f'Consumer failed {ex}') LOG.error(ex) + raise ex