Skip to content

feat(cli): stream job logs and result to the handler over uipath-ipc - #1883

Open
eduard-dumitru wants to merge 2 commits into
mainfrom
coded_ipc
Open

feat(cli): stream job logs and result to the handler over uipath-ipc#1883
eduard-dumitru wants to merge 2 commits into
mainfrom
coded_ipc

Conversation

@eduard-dumitru

@eduard-dumitru eduard-dumitru commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

What

uipath-python owns the uipath-ipc contract and connection (_job_api.py) and points uipath-runtime's in-memory sinks (from #167) at the handler, so a Python serverless job streams its logs and result back over CoreIPC instead of the files. Covers both transport roles. Version 2.14.10 → 2.14.11.

Two roles, one contract — IJobInvocationCommonApi (SendLog + SetResult)

  • Pooled (uipath server)Register(message) grabs the handler's callback (message.client.get_callback(IJobInvocationCommonApi)) on the existing pooled pipe and installs the runtime sinks for the job. Gated per-job by a StreamOutputOverIpc flag on the run request; jobs run in-process (serialized), so the process-global sinks genuinely reach the callback.
  • Non-pooled (uipath run --handler-ipc-pipe) — dials the handler's per-job server (get_proxy) and installs the sinks on a dedicated loop/thread, so the result's blocking ack can't deadlock the job's own event loop.

SendLog carries logs; SetResult carries a bounded envelope + a pointer to the output-arguments file the runtime spilled (the payload never crosses IPC). Best-effort and version-guarded: a no-op if the runtime lacks output_sinks or uipath-ipc is absent, so an older runtime keeps the files.

Related PRs — one endeavor (merge in this order)

Three PRs, one endeavor — stream Python serverless job logs + result to the .NET Robot handler over CoreIPC:

  1. uipath-runtime — the in-memory log/result sinks the host installs: feat(runtime): in-memory output sinks for host-driven log/result delivery uipath-runtime-python#167
  2. uipath-python — owns the uipath-ipc contract; installs those sinks over the pipe: feat(cli): stream job logs and result to the handler over uipath-ipc #1883 ← this PR
  3. hdens — the .NET handler side: UiPath/hdens#8031

Testing

CLI/IPC tests green, incl. the non-pooled result-ack (deadlock) regression + DTO wire-key tests; ruff/format/mypy clean.

Jira

Tracking: ROBO-5981.

🤖 Generated with Claude Code

The pooled `uipath server` now grabs the handler's IIpcLogSink callback during Register
(via the injected Message) and points uipath-runtime's process-global log sink at it, so
a pooled job's logs stream back over the existing pipe. Best-effort and version-guarded:
an older uipath-runtime without the pooled sink API is a no-op and jobs keep the file path.

Register(self, message) gains the reach-back handle; the sink forwards each SendLog onto
the server loop from the job's worker thread. Bump 2.14.10 -> 2.14.11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The pooled log forwarding path is documented as best-effort but currently can surface exceptions during callback acquisition/forwarding, and the new test includes a timing-based sleep that can be flaky.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR adds the “pooled” half of IPC log streaming by wiring a pooled job’s process-global runtime log sink to an IIpcLogSink callback provided by the handler over the existing uipath-ipc connection. The Register handshake is extended to receive an injected Message (reach-back handle) so the server can obtain the callback and forward logs from worker threads onto the server event loop.

Changes:

  • Extend the IPC contract so Register(self, message) receives the injected uipath_ipc.Message and uses it to wire a pooled log sink callback.
  • Add server-side forwarding logic (run_coroutine_threadsafe + drain) and lifecycle cleanup to unset the process-global sink when the IPC server exits.
  • Add tests that lock in the callback seam behavior and bump package version to 2.14.11.
File summaries
File Description
packages/uipath/src/uipath/_cli/cli_server_ipc.py Add Message-aware Register, wire pooled log sink to handler callback, and unset sink on server shutdown.
packages/uipath/tests/cli/test_server_ipc.py Update contract test for new Register signature and add pooled log sink seam tests.
packages/uipath/pyproject.toml Bump uipath version from 2.14.10 to 2.14.11.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +151 to +158
sink_proxy = client.get_callback(IIpcLogSink)
loop = asyncio.get_running_loop()

def _forward(job_id: str, log: object) -> None:
# Runs on the job's worker thread: hand the one-way SendLog to the server loop and return at
# once. A dropped log (pipe down) must never surface into the job, so failures are swallowed.
future = asyncio.run_coroutine_threadsafe(sink_proxy.SendLog(job_id, log), loop)
future.add_done_callback(_drain)
Comment on lines +389 to +393
sink = captured["sink"]
log = {"Message": "hello", "LogLevel": 2}
sink("job-key-42", log)
await asyncio.sleep(0.05)
assert sent == [("job-key-42", log)]
@eduard-dumitru eduard-dumitru changed the title feat: stream pooled job logs to the handler over uipath-ipc feat(cli): stream job logs and result to the handler over uipath-ipc Sep 9, 2026
@eduard-dumitru
eduard-dumitru force-pushed the coded_ipc branch 2 times, most recently from 343fa8b to e9af2ca Compare September 9, 2026 15:09
uipath-python owns the uipath-ipc contract and connection (_job_api.py) and
points uipath-runtime's in-memory sinks at it. The contract is
IJobInvocationCommonApi (SendLog + SetResult), the runtime-agnostic base the
handler hosts; the JS-only telemetry channel lives on a .NET superset Python
doesn't use, so Python keys on the common base directly. Pooled: `uipath server`
grabs the handler's IJobInvocationCommonApi callback at Register and installs the
sinks per job. Non-pooled: `uipath run --handler-ipc-pipe` dials the handler's
per-job server. The result envelope travels inline; output arguments go off-heap
via a file pointer.

Review fixes:
- run the non-pooled handler connection on its own loop/thread so the result
  sink's blocking ack can't deadlock the job's own event loop
- install/clear the process-global sinks inside the job core's lock so
  concurrently-dispatched RunJobs can't corrupt each other's routing
- log (not silently swallow) an IPC result-delivery failure
- always disconnect the handler IPC on the run's exit path (async context manager)
- register the default runtime factory in start_ipc_server
- pin the DTO wire key sets and the log-level mapping in tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants