From 604cd341f2ba9d1ecb673c925d018392bbf009eb Mon Sep 17 00:00:00 2001 From: Amey Date: Tue, 31 Mar 2026 15:50:43 -0400 Subject: [PATCH] fix: add timeout to AppInsights flush to prevent blocking process exit Run flush in a daemon thread with a 5s timeout so AppInsights telemetry never blocks process exit when the Azure endpoint is unreachable (e.g. Automation Suite / CI environments with restricted egress). Previously the synchronous flush blocked indefinitely on URLError retries, keeping the process alive and leaving eval runs stuck in "Running" state. --- packages/uipath/pyproject.toml | 2 +- .../uipath/src/uipath/telemetry/_track.py | 35 +++++++++++++++---- packages/uipath/uv.lock | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/uipath/pyproject.toml b/packages/uipath/pyproject.toml index 380ae367d..49a172d07 100644 --- a/packages/uipath/pyproject.toml +++ b/packages/uipath/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.10.39" +version = "2.10.40" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath/src/uipath/telemetry/_track.py b/packages/uipath/src/uipath/telemetry/_track.py index 2d3f11ebf..0b5384398 100644 --- a/packages/uipath/src/uipath/telemetry/_track.py +++ b/packages/uipath/src/uipath/telemetry/_track.py @@ -325,15 +325,29 @@ def track_event( _logger.debug(f"Event tracking error for '{name}'", exc_info=True) @staticmethod - def flush() -> None: - """Flush any pending telemetry events.""" - if _AppInsightsEventClient._client: + def flush(timeout: float = 5.0) -> None: + """Flush any pending telemetry events. + + Blocks for up to ``timeout`` seconds waiting for the flush to complete. + On the happy path (network reachable) the flush finishes well within the + timeout and no data is lost. When the ingestion endpoint is unreachable + (e.g. Automation Suite / CI with restricted egress) the synchronous sender + would otherwise retry indefinitely, preventing the process from exiting and + leaving eval runs stuck in Running state. The flush runs in a daemon thread + so that if it does not finish within ``timeout`` seconds it is abandoned + rather than blocking the process indefinitely. + """ + import threading + + if not _AppInsightsEventClient._client: + return + + def _do_flush() -> None: try: - _AppInsightsEventClient._client.flush() - # Check if items remain after flush (indicates send failure) + _AppInsightsEventClient._client.flush() # type: ignore[union-attr] try: remaining = ( - _AppInsightsEventClient._client.channel.queue._queue.qsize() + _AppInsightsEventClient._client.channel.queue._queue.qsize() # type: ignore[union-attr] ) if remaining > 0: _logger.warning( @@ -343,10 +357,17 @@ def flush() -> None: except Exception: pass except Exception as e: - # Log but don't raise - telemetry should never break the main application _logger.warning(f"Failed to flush telemetry events: {e}") _logger.debug("Telemetry flush error", exc_info=True) + thread = threading.Thread(target=_do_flush, daemon=True) + thread.start() + thread.join(timeout=timeout) + if thread.is_alive(): + _logger.warning( + "AppInsights flush: timed out after %.1fs, abandoning send", timeout + ) + @staticmethod def register_atexit_flush() -> None: """Register an atexit handler to flush events on process exit.""" diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 3214375f9..029535586 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2543,7 +2543,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.10.39" +version = "2.10.40" source = { editable = "." } dependencies = [ { name = "applicationinsights" },