feat(governance): non-blocking LiveTrackEventDispatcher for /runtime/log#1776
feat(governance): non-blocking LiveTrackEventDispatcher for /runtime/log#1776viswa-uipath wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new non-blocking adapter in uipath-platform to dispatch governance track_event calls from a background thread pool, so runtime audit logging won’t block on the /runtime/log HTTP round-trip.
Changes:
- Introduces
LiveTrackEventDispatcherto submitprovider.track_event(...)work to aThreadPoolExecutorand provides ashutdown()drain hook. - Exports the new dispatcher from
uipath.platform.governanceand adds a dedicated pytest suite validating non-blocking behavior, forwarding, exception swallowing, and shutdown semantics. - Bumps
uipath-platformversion to0.1.85and updates workspace lockfiles accordingly.
Reviewed changes
Copilot reviewed 4 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/uipath/uv.lock | Updates workspace lock to uipath-platform==0.1.85. |
| packages/uipath-platform/uv.lock | Updates package lock to uipath-platform==0.1.85. |
| packages/uipath-platform/src/uipath/platform/governance/_live_track_event_dispatcher.py | Adds the non-blocking dispatcher implementation around provider.track_event. |
| packages/uipath-platform/src/uipath/platform/governance/init.py | Exports LiveTrackEventDispatcher on the governance public surface. |
| packages/uipath-platform/tests/services/test_live_track_event_dispatcher.py | Adds dispatcher unit tests (non-blocking, forwarding, concurrency, shutdown). |
| packages/uipath-platform/pyproject.toml | Bumps uipath-platform version to 0.1.85. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
72193cd to
5e8b496
Compare
…eTrackEventDispatcher Addresses Copilot review on #1776: - **Bounded in-flight cap.** ``ThreadPoolExecutor``'s own work queue is unbounded — without an explicit cap, a slow backend would accumulate submissions in memory without limit. Added a ``threading.BoundedSemaphore(max_workers * inflight_oversubscription)`` that ``dispatch`` acquires non-blocking; saturated calls drop with a warning rather than queueing forever. Mirrors the pattern in ``GuardrailCompensator`` so both governance dispatchers share one shape. - **Post-shutdown safety.** ``executor.submit`` raises ``RuntimeError`` after :meth:`shutdown`. Wrapped in try/except so a late dispatch (e.g. from an atexit hook after the pool drained) can't crash the hook thread. - **Worker tracebacks.** Added ``exc_info=True`` to the worker-failure debug log so backend / serialisation failures surface a stack trace for diagnostics. - **Docstring** rewritten to describe the actual failure modes (saturated pool, pool shut down, worker exception) — replaces the prior inaccurate claim that ``executor.submit`` provided backpressure. Tests ----- - ``test_dispatch_after_shutdown_is_silent`` — dispatch after shutdown doesn't raise and doesn't call the provider. - ``test_dispatch_drops_when_inflight_saturated`` — fills the in-flight cap with blocked workers, asserts the next submission is dropped and the provider is never called for the dropped event. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runtime's TrackEventAuditSink invokes its injected ``track_event`` callable synchronously on the agent's hook thread. Wiring the platform provider's method directly would block agent execution on governance telemetry's HTTP round-trip (the ``POST /runtime/log`` write to App Insights). LiveTrackEventDispatcher wraps ``provider.track_event`` in a bounded ``ThreadPoolExecutor`` (default 10 workers, matching ``LiveTrackingSpanProcessor`` and sized for the bursty-but-not- sustained shape of governance events). The host wires ``dispatcher.dispatch`` into ``AuditManager(track_event=...)`` instead of the provider's method — same kwargs, but the HTTP work runs on a background worker. Failure modes — all silent on the hook thread --------------------------------------------- - **Saturated pool**: ThreadPoolExecutor's own queue is unbounded, so the dispatcher uses a ``threading.BoundedSemaphore`` capped at ``max_workers * inflight_oversubscription`` (default 10 × 4 = 40). When the cap is reached, ``dispatch`` drops with a warning rather than queueing forever. Mirrors GuardrailCompensator so both governance dispatchers share one shape. - **Pool shut down**: ``executor.submit`` raises ``RuntimeError`` after shutdown — caught, semaphore slot released, logged at debug. A late dispatch (e.g. atexit hook after the pool drained) never crashes the hook thread. - **Worker exception**: provider HTTP call may raise (serialisation, 5xx, transport). Worker catches, logs at debug with ``exc_info=True`` for diagnostics, releases the in-flight slot. ``shutdown(wait=True)`` drains in-flight submissions so process teardown doesn't lose telemetry. Idempotent — safe to call from multiple atexit hooks. Public-surface discipline ------------------------- The class is intentionally **not** re-exported from ``governance/__init__.py``. It's host-wiring glue for the runtime sink, not a customer-facing API — same principle radu applied to ``_track_event`` on ``GovernanceService`` (PR #1745): internal seams shouldn't sit on the docs-facing ``uipath.platform.governance`` import path that ``mkdocstrings`` auto-discovers. Internal callers use the explicit private path: from uipath.platform.governance._live_track_event_dispatcher import ( LiveTrackEventDispatcher, ) Tests cover the boundary contract (dispatch non-blocking via a ``threading.Event`` sync proof), kwarg forwarding, default flow-through, exception swallowing, concurrency under burst, shutdown drain, shutdown idempotence, post-shutdown silent dispatch, and saturated-pool drop semantics. Bumps: - uipath-platform 0.1.84 → 0.1.85 (new class needs a fresh wheel; ``0.1.84`` is already on PyPI) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bedb22a to
f296730
Compare
🚨 Heads up:
|
|
radu-mocanu
left a comment
There was a problem hiding this comment.
changes look good but please follow-up with the async variant as well. we should not rely on synchronous API calls
🚨 Heads up:
|



Summary
The runtime's
TrackEventAuditSinkinvokes its injectedtrack_eventcallable synchronously on the agent's hook thread. WiringUiPathPlatformGovernanceProvider.track_eventdirectly would block agent execution on thePOST /runtime/logHTTP round-trip.LiveTrackEventDispatcherwrapsprovider.track_eventin a boundedThreadPoolExecutor. The host wiresdispatcher.dispatchintoAuditManager(track_event=...)instead — same kwargs, but the HTTP work runs on a background worker.What's in
LiveTrackEventDispatcher(packages/uipath-platform/src/uipath/platform/governance/_live_track_event_dispatcher.py)max_workers=10, mirrorsLiveTrackingSpanProcessor)dispatch(*, event_name, data=None, operation_id=None)— drop-in for the provider'strack_eventsignaturedispatchreturns immediately; worker exceptions are caught and logged at debugshutdown(wait=True)drains in-flight submissions; idempotent (safe under multiple atexit hooks)uipath.platform.governancealongside the existingUiPathPlatformGovernanceProviderthreading.Eventsync, kwarg forwarding, default flow-through (data=None,operation_id=None), exception swallowing, concurrency under burst (20 submissions),shutdowndrain semantics,shutdownidempotenceuipath-platform 0.1.84 → 0.1.85(new public class needs a fresh wheel —0.1.84is already on PyPI)Why a separate adapter (not built into the provider)
The provider's
track_eventis the synchronous platform call. Tests and other internal seams might legitimately want to block on it. Wrapping it in a thread pool by default would force every caller into a fire-and-forget shape and hide HTTP errors. Keeping the non-blocking concern as a separate class lets the host opt into it deliberately for the runtime-sink wiring path.Test plan
uv run ruff check .— cleanuv run ruff format --check .— cleanuv run mypy src tests— clean (205 source files)uv run pytest tests/services/test_live_track_event_dispatcher.py tests/services/test_governance_service.py tests/services/test_governance_provider.py— 63 passeduv sync --locked— no drift across all three workspace packages0.1.85not yet published;0.1.84is the current latest🤖 Generated with Claude Code
Development Packages
uipath-platform
uipath