Skip to content

Commit ad9e827

Browse files
gijzelaerrclaude
andcommitted
Fix get_connected to detect lost network connections
Add check_connection() to ISOTCPConnection that performs a non-blocking socket peek to detect broken TCP connections. Update Client.get_connected() to use this active check instead of just returning a cached flag. Previously, get_connected() would return True even after the network connection was lost, leading to crashes on subsequent operations. Fixes #111 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 68af88d commit ad9e827

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

snap7/client.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,14 @@ def destroy(self) -> None:
184184
self.disconnect()
185185

186186
def get_connected(self) -> bool:
187-
"""Check if client is connected to PLC."""
188-
return self.connected and self.connection is not None and self.connection.connected
187+
"""Check if client is connected to PLC.
188+
189+
Performs an active check on the underlying TCP socket to detect
190+
broken connections, rather than just checking a cached flag.
191+
"""
192+
if not self.connected or self.connection is None:
193+
return False
194+
return self.connection.check_connection()
189195

190196
def db_read(self, db_number: int, start: int, size: int) -> bytearray:
191197
"""

snap7/connection.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,34 @@ def _recv_exact(self, size: int) -> bytes:
382382

383383
return bytes(data)
384384

385+
def check_connection(self) -> bool:
386+
"""Check if the TCP connection is still alive.
387+
388+
Uses a non-blocking socket peek to detect broken connections.
389+
"""
390+
if not self.connected or self.socket is None:
391+
return False
392+
393+
try:
394+
original_timeout = self.socket.gettimeout()
395+
self.socket.settimeout(0)
396+
try:
397+
data = self.socket.recv(1, socket.MSG_PEEK)
398+
if not data:
399+
self.connected = False
400+
return False
401+
return True
402+
except BlockingIOError:
403+
# No data available but connection is still alive
404+
return True
405+
except (socket.error, OSError):
406+
self.connected = False
407+
return False
408+
finally:
409+
self.socket.settimeout(original_timeout)
410+
except Exception:
411+
return False
412+
385413
def __enter__(self) -> "ISOTCPConnection":
386414
"""Context manager entry."""
387415
return self

0 commit comments

Comments
 (0)