fixing tcp_interface 100% cpu usage bug (#709)

This commit is contained in:
Derek Nicol
2025-04-14 13:22:46 -05:00
parent d4bc39153a
commit 2e73fe310c

View File

@@ -1,9 +1,11 @@
"""TCPInterface class for interfacing with http endpoint
"""
# pylint: disable=R0917
import contextlib
import logging
import socket
from typing import Optional, cast
import time
from typing import Optional
from meshtastic.stream_interface import StreamInterface
@@ -35,52 +37,63 @@ class TCPInterface(StreamInterface):
self.socket: Optional[socket.socket] = None
if connectNow:
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
server_address: tuple[str, int] = (hostname, portNumber)
sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock
self.myConnect()
else:
self.socket = None
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
)
super().__init__(debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes)
def _socket_shutdown(self) -> None:
"""Shutdown the socket.
Note: Broke out this line so the exception could be unit tested.
"""
if self.socket: #mian: please check that this should be "if self.socket:"
cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR)
if self.socket is not None:
self.socket.shutdown(socket.SHUT_RDWR)
def myConnect(self) -> None:
"""Connect to socket"""
server_address: tuple[str, int] = (self.hostname, self.portNumber)
sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock
logging.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
server_address = (self.hostname, self.portNumber)
self.socket = socket.create_connection(server_address)
def close(self) -> None:
"""Close a connection to the device"""
logging.debug("Closing TCP stream")
StreamInterface.close(self)
super().close()
# Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here
self._wantExit: bool = True
if not self.socket is None:
try:
self._wantExit = True
if self.socket is not None:
with contextlib.suppress(Exception): # Ignore errors in shutdown, because we might have a race with the server
self._socket_shutdown()
except:
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
self.socket = None
def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush"""
if self.socket:
if self.socket is not None:
self.socket.send(b)
def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream"""
if self.socket:
return self.socket.recv(length)
else:
return None
if self.socket is not None:
data = self.socket.recv(length)
# empty byte indicates a disconnected socket,
# we need to handle it to avoid an infinite loop reading from null socket
if data == b'':
logging.debug("dead socket, re-connecting")
# cleanup and reconnect socket without breaking reader thread
with contextlib.suppress(Exception):
self._socket_shutdown()
self.socket.close()
self.socket = None
time.sleep(1)
self.myConnect()
self._startConfig()
return None
return data
# no socket, break reader thread
self._wantExit = True
return None