diff --git a/freecad/silo_commands.py b/freecad/silo_commands.py index 3792e54..7aac574 100644 --- a/freecad/silo_commands.py +++ b/freecad/silo_commands.py @@ -2366,13 +2366,13 @@ class SiloEventListener(QtCore.QThread): item_updated = QtCore.Signal(str) # part_number revision_created = QtCore.Signal(str, int) # part_number, revision connection_status = QtCore.Signal( - str - ) # "connected" / "disconnected" / "unsupported" + str, int, str + ) # (status, retry_count, error_message) server_mode_changed = QtCore.Signal(str) # "normal" / "read-only" / "degraded" - _MAX_FAST_RETRIES = 3 - _FAST_RETRY_SECS = 5 - _SLOW_RETRY_SECS = 30 + _MAX_RETRIES = 10 + _BASE_DELAY = 1 # seconds, doubles each retry + _MAX_DELAY = 60 # seconds, backoff cap def __init__(self, parent=None): super().__init__(parent) @@ -2395,6 +2395,7 @@ class SiloEventListener(QtCore.QThread): def run(self): retries = 0 + last_error = "" while not self._stop_flag: try: self._listen() @@ -2402,21 +2403,24 @@ class SiloEventListener(QtCore.QThread): if self._stop_flag: return retries += 1 + last_error = "connection closed" except _SSEUnsupported: - self.connection_status.emit("unsupported") + self.connection_status.emit("unsupported", 0, "") return - except Exception: + except Exception as exc: retries += 1 + last_error = str(exc) or "unknown error" - self.connection_status.emit("disconnected") + if retries > self._MAX_RETRIES: + self.connection_status.emit("gave_up", retries - 1, last_error) + return - if retries <= self._MAX_FAST_RETRIES: - delay = self._FAST_RETRY_SECS - else: - delay = self._SLOW_RETRY_SECS + self.connection_status.emit("disconnected", retries, last_error) + + delay = min(self._BASE_DELAY * (2 ** (retries - 1)), self._MAX_DELAY) # Interruptible sleep - for _ in range(delay): + for _ in range(int(delay)): if self._stop_flag: return self.msleep(1000) @@ -2440,7 +2444,7 @@ class SiloEventListener(QtCore.QThread): except urllib.error.URLError: raise - self.connection_status.emit("connected") + self.connection_status.emit("connected", 0, "") event_type = "" data_buf = "" @@ -2706,13 +2710,28 @@ class SiloAuthDockWidget: self._event_listener.stop() self._sse_label.setText("") - def _on_sse_status(self, status): + def _on_sse_status(self, status, retry, error): if status == "connected": self._sse_label.setText("Listening") self._sse_label.setStyleSheet("font-size: 11px; color: #4CAF50;") + self._sse_label.setToolTip("") + FreeCAD.Console.PrintMessage("Silo: SSE connected\n") elif status == "disconnected": - self._sse_label.setText("Reconnecting...") + self._sse_label.setText( + f"Reconnecting ({retry}/{SiloEventListener._MAX_RETRIES})..." + ) self._sse_label.setStyleSheet("font-size: 11px; color: #FF9800;") + self._sse_label.setToolTip(error or "Connection lost") + FreeCAD.Console.PrintWarning( + f"Silo: SSE reconnecting ({retry}/{SiloEventListener._MAX_RETRIES}): {error}\n" + ) + elif status == "gave_up": + self._sse_label.setText("Disconnected") + self._sse_label.setStyleSheet("font-size: 11px; color: #F44336;") + self._sse_label.setToolTip(error or "Max retries reached") + FreeCAD.Console.PrintError( + f"Silo: SSE gave up after {retry} retries: {error}\n" + ) elif status == "unsupported": self._sse_label.setText("Not available") self._sse_label.setStyleSheet("font-size: 11px; color: #888;")