From 015e4a4e9e02773708038ba1e6caefefd278f236 Mon Sep 17 00:00:00 2001 From: Aminorjourney Date: Thu, 2 Jul 2026 21:15:00 +0100 Subject: [PATCH] Session-recovery robustness (from PR #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Suppress ConnectionError log noise from in-flight requests draining after a session close (poll_scheduler + keepalive log at DEBUG). - Null KeepaliveTask.on_unreachable during a forced reconnect so the dying session's keepalive can't flip HA availability offline after the new session is already healthy. Ported into PR #5's _maybe_force_reconnect flow. Drops the _session_stop / publish-health force-close mechanism from the original PR — PR #5's last_success_ts + _maybe_force_reconnect already covers the 'session dead, restart it' goal via a different path, and running both means two paths force-closing the same session on the same failure. --- samsung_appliance/appliances/__init__.py | 1 + samsung_appliance/bridge.py | 5 +++++ samsung_appliance/keepalive.py | 2 ++ samsung_appliance/poll_scheduler.py | 12 +++++++++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/samsung_appliance/appliances/__init__.py b/samsung_appliance/appliances/__init__.py index 5a6864d..20657a9 100644 --- a/samsung_appliance/appliances/__init__.py +++ b/samsung_appliance/appliances/__init__.py @@ -15,6 +15,7 @@ from .oven import OVEN from .fridge import FRIDGE + DESCRIPTORS: dict[str, ApplianceDescriptor] = { DRYER.name: DRYER, OVEN.name: OVEN, diff --git a/samsung_appliance/bridge.py b/samsung_appliance/bridge.py index d7d66c8..43dec1a 100644 --- a/samsung_appliance/bridge.py +++ b/samsung_appliance/bridge.py @@ -409,6 +409,11 @@ class PushBridge: self.log.warning( "unreachable for %.0fs — forcing session reconnect", elapsed) self._force_close_in_flight = True + # Null the dying session's on_unreachable so its keepalive thread + # can't flip availability offline after the new session takes over. + ka = self.keepalive + if ka is not None: + ka.on_unreachable = None try: sess.close() except Exception as e: diff --git a/samsung_appliance/keepalive.py b/samsung_appliance/keepalive.py index 9e47ac3..11fe464 100644 --- a/samsung_appliance/keepalive.py +++ b/samsung_appliance/keepalive.py @@ -67,6 +67,8 @@ class KeepaliveTask: try: self.session.ping() ok = True + except ConnectionError as e: + if self.log: self.log.debug("ping: %s", e) except Exception as e: if self.log: self.log.warning("ping: %s", e) # Real half-open detection: ping sends can succeed against a diff --git a/samsung_appliance/poll_scheduler.py b/samsung_appliance/poll_scheduler.py index a18ab15..ff8e6ba 100644 --- a/samsung_appliance/poll_scheduler.py +++ b/samsung_appliance/poll_scheduler.py @@ -237,6 +237,11 @@ class PollScheduler: self.log.warning("poll %s timeout (cooldown %.0fs)", href, cooldown) return + except ConnectionError as e: + self._poll_error_count += 1 + self._record_rtt((time.monotonic() - t0) * 1000.0) + if self.log: self.log.debug("poll %s: %s", href, e) + return except Exception as e: self._poll_error_count += 1 self._record_rtt((time.monotonic() - t0) * 1000.0) @@ -274,6 +279,11 @@ class PollScheduler: self.log.warning("sweep %s timeout (cooldown %.0fs)", path, cooldown) return + except ConnectionError as e: + self._poll_error_count += 1 + self._record_rtt((time.monotonic() - t0) * 1000.0) + if self.log: self.log.debug("sweep %s: %s", path, e) + return except Exception as e: self._poll_error_count += 1 self._record_rtt((time.monotonic() - t0) * 1000.0) @@ -300,4 +310,4 @@ class PollScheduler: if self.log: elapsed_ms = (time.monotonic() - t0) * 1000.0 self.log.info("sweep complete (%d links, %.0fms)", - len(indexed), elapsed_ms) + len(indexed), elapsed_ms) \ No newline at end of file