Skip to content

Commit ee0f73a

Browse files
authored
Merge pull request #761 from derekn/master
fixing tcp_interface 100% cpu usage bug (#709)
2 parents d4bc391 + 2e73fe3 commit ee0f73a

File tree

1 file changed

+37
-24
lines changed

1 file changed

+37
-24
lines changed

meshtastic/tcp_interface.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""TCPInterface class for interfacing with http endpoint
22
"""
33
# pylint: disable=R0917
4+
import contextlib
45
import logging
56
import socket
6-
from typing import Optional, cast
7+
import time
8+
from typing import Optional
79

810
from meshtastic.stream_interface import StreamInterface
911

@@ -35,52 +37,63 @@ def __init__(
3537
self.socket: Optional[socket.socket] = None
3638

3739
if connectNow:
38-
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
39-
server_address: tuple[str, int] = (hostname, portNumber)
40-
sock: Optional[socket.socket] = socket.create_connection(server_address)
41-
self.socket = sock
40+
self.myConnect()
4241
else:
4342
self.socket = None
4443

45-
StreamInterface.__init__(
46-
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
47-
)
44+
super().__init__(debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes)
4845

4946
def _socket_shutdown(self) -> None:
5047
"""Shutdown the socket.
5148
Note: Broke out this line so the exception could be unit tested.
5249
"""
53-
if self.socket: #mian: please check that this should be "if self.socket:"
54-
cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR)
50+
if self.socket is not None:
51+
self.socket.shutdown(socket.SHUT_RDWR)
5552

5653
def myConnect(self) -> None:
5754
"""Connect to socket"""
58-
server_address: tuple[str, int] = (self.hostname, self.portNumber)
59-
sock: Optional[socket.socket] = socket.create_connection(server_address)
60-
self.socket = sock
55+
logging.debug(f"Connecting to {self.hostname}") # type: ignore[str-bytes-safe]
56+
server_address = (self.hostname, self.portNumber)
57+
self.socket = socket.create_connection(server_address)
6158

6259
def close(self) -> None:
6360
"""Close a connection to the device"""
6461
logging.debug("Closing TCP stream")
65-
StreamInterface.close(self)
62+
super().close()
6663
# Sometimes the socket read might be blocked in the reader thread.
6764
# Therefore we force the shutdown by closing the socket here
68-
self._wantExit: bool = True
69-
if not self.socket is None:
70-
try:
65+
self._wantExit = True
66+
if self.socket is not None:
67+
with contextlib.suppress(Exception): # Ignore errors in shutdown, because we might have a race with the server
7168
self._socket_shutdown()
72-
except:
73-
pass # Ignore errors in shutdown, because we might have a race with the server
7469
self.socket.close()
7570

71+
self.socket = None
72+
7673
def _writeBytes(self, b: bytes) -> None:
7774
"""Write an array of bytes to our stream and flush"""
78-
if self.socket:
75+
if self.socket is not None:
7976
self.socket.send(b)
8077

8178
def _readBytes(self, length) -> Optional[bytes]:
8279
"""Read an array of bytes from our stream"""
83-
if self.socket:
84-
return self.socket.recv(length)
85-
else:
86-
return None
80+
if self.socket is not None:
81+
data = self.socket.recv(length)
82+
# empty byte indicates a disconnected socket,
83+
# we need to handle it to avoid an infinite loop reading from null socket
84+
if data == b'':
85+
logging.debug("dead socket, re-connecting")
86+
# cleanup and reconnect socket without breaking reader thread
87+
with contextlib.suppress(Exception):
88+
self._socket_shutdown()
89+
self.socket.close()
90+
self.socket = None
91+
time.sleep(1)
92+
self.myConnect()
93+
self._startConfig()
94+
return None
95+
return data
96+
97+
# no socket, break reader thread
98+
self._wantExit = True
99+
return None

0 commit comments

Comments
 (0)