Skip to content

fix(cloudxr): refuse to start over a live runtime; explain -35 - #911

Merged
jiwenc-nv merged 1 commit into
mainfrom
jiwenc-nv/IT908
Aug 10, 2026
Merged

fix(cloudxr): refuse to start over a live runtime; explain -35#911
jiwenc-nv merged 1 commit into
mainfrom
jiwenc-nv/IT908

Conversation

@jiwenc-nv

@jiwenc-nv jiwenc-nv commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Description

Items 1 and 2 of #736.

_cleanup_stale_runtime treated an existing ipc_cloudxr as proof of staleness and fuser -k -TERMed its holder — including a healthy runtime mid-session, which the first session saw as a broken pipe. With fuser absent the error was swallowed and the socket unlinked anyway, so the old process survived; hence the manual rm.

Liveness is now a connect(). Live → refuse, naming the resolved env file and --no-launch-cloudxr-runtime; replacing it means stopping it yourself. Dead → clean, at WARNING. Ambiguous probe errors count as live. Nothing signals another process now, so the undeclared fuser/psmisc dependency goes too.

Both -35 throw sites now name XR_ERROR_FORM_FACTOR_UNAVAILABLE and point at the one new docs section, carrying the -51 contrast and the checklist. The banner prints the resolved device profile, and the two device-profile defaults collapse onto one constant — docs said auto-webrtc, Quest3 always won.

#908 also proposed --force and --cloudxr-device-profile; both dropped as unnecessary.

Fixes #908

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Testing

  • 193 tests pass (src/core/cloudxr_tests/python/), covering the probe and the refusal.
  • C++ compiles; clang-format, Sphinx, and SKIP=check-copyright-year pre-commit run --all-files clean.
  • Jetson Thor, live runtime: second launch refused, exit 1, runtime survived. It unlinks its socket on clean exit, so the WARNING marks a crash.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the linter and formatter with SKIP=check-copyright-year pre-commit run --all-files
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix/feature works (or explained why not)
  • I have signed off all my commits (git commit -s) per the DCO

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c0aeac0e-d103-454b-9384-dc21746f6e53

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

CloudXR runtime startup now probes ipc_cloudxr to distinguish live runtimes from stale socket files. A second launch fails unless --force is used, which sends SIGTERM and waits for termination. The default NV_DEVICE_PROFILE is now Quest3, and startup reports the resolved profile. OpenXR errors for unavailable headsets now include diagnostics. Documentation and tests cover runtime takeover, profile resolution, socket liveness, and wait behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant CloudXRLauncher
  participant RuntimeIPC
  participant ExistingRuntime
  CLI->>CloudXRLauncher: Start with optional --force
  CloudXRLauncher->>RuntimeIPC: Probe ipc_cloudxr
  RuntimeIPC-->>CloudXRLauncher: Return liveness
  alt Existing runtime is live
    CloudXRLauncher->>ExistingRuntime: Send SIGTERM when forced
    CloudXRLauncher->>RuntimeIPC: Wait for termination
  else Socket is stale
    CloudXRLauncher->>RuntimeIPC: Remove stale socket
  end
  CloudXRLauncher-->>CLI: Start runtime or report error
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR meets the liveness, takeover, cleanup, profile, -35, testing, and documentation objectives, but omits the requested device-profile CLI flag [#908]. Add a --cloudxr-device-profile option and pass its value into launcher or environment configuration before startup.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The code, tests, and documentation changes directly support the linked issue objectives and do not introduce unrelated scope.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: preventing startup over a live runtime and improving explanation of error -35.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jiwenc-nv/IT908

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/python/isaacteleop/cloudxr/launcher.py`:
- Around line 594-636: The launch flow currently relies on the point-in-time
is_runtime_live check and force-mode socket disappearance, so runtime ownership
is not atomic and shutdown is not confirmed. Add an exclusive run-directory
ownership lock that remains held from startup through the runtime lifetime,
acquire it before checking or removing stale state, and ensure it is released
only when the launcher exits. Update _terminate_live_runtime to identify and
wait for the prior owner process to exit, not merely until is_runtime_live
returns false, before cleanup and replacement startup proceed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 90cb1f88-3f35-4695-a691-bdb2dabd7a7d

📥 Commits

Reviewing files that changed from the base of the PR and between 805b95f and 14c8c2e.

📒 Files selected for processing (11)
  • docs/source/getting_started/quick_start.rst
  • docs/source/getting_started/televiz.rst
  • docs/source/references/cloudxr.rst
  • src/core/cloudxr_tests/python/test_launcher.py
  • src/core/cloudxr_tests/python/test_runtime.py
  • src/core/oxr/cpp/oxr_session.cpp
  • src/python/isaacteleop/cloudxr/__main__.py
  • src/python/isaacteleop/cloudxr/env_config.py
  • src/python/isaacteleop/cloudxr/launcher.py
  • src/python/isaacteleop/cloudxr/runtime.py
  • src/viz/xr/cpp/openxr_session.cpp

Comment thread src/python/isaacteleop/cloudxr/launcher.py Outdated
Comment thread docs/source/getting_started/quick_start.rst

@yanziz-nvidia yanziz-nvidia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed by yanziz-reviewer-bot

Summary

Replaces the fuser -k -TERM liveness check with a connect() probe in _cleanup_stale_runtime, and adds a device profile banner and a DEFAULT_DEVICE_PROFILE constant. CI state unknown from here.

Legend: 🚫 Blocker · 💡 Suggestion · 🔍 Nit

Finding
🚫 None
💡 src/python/isaacteleop/cloudxr/runtime.py:274sock.connect(full_abs_path) will raise OSError: ENAMETOOLONG on long TMPDIR paths (CI, nested job isolation), which falls into except OSError: return True and misreports a dead socket as live. The PR already solved this for the test helper (_live_ipc_socket uses os.chdir + relative path). Apply the same fix here: os.chdir(run_dir) before sock.connect("ipc_cloudxr"), restore CWD in finally.
🔍 src/python/isaacteleop/cloudxr/__main__.py:238env_cfg.resolved("NV_DEVICE_PROFILE") returns str | None; f-string silently prints None if resolution hasn't run. Change to env_cfg.resolved("NV_DEVICE_PROFILE") or DEFAULT_DEVICE_PROFILE.

Actionables (for bots — copy-paste-ready for AI)

Fix if it makes sense in context — these are agent-generated suggestions, not human-vetted obligations. Skip anything that's wrong, already addressed, or not worth the churn.

  • src/python/isaacteleop/cloudxr/runtime.py:274 — In is_runtime_live(), replace sock.connect(path) with os.chdir(run_dir); sock.connect("ipc_cloudxr") (relative), restoring CWD in finally — mirrors the _live_ipc_socket test helper pattern; avoids ENAMETOOLONG on long paths.
  • src/python/isaacteleop/cloudxr/__main__.py:238 — Change to profile = env_cfg.resolved("NV_DEVICE_PROFILE") or DEFAULT_DEVICE_PROFILE to avoid printing None.

A second launch treated the existence of `ipc_cloudxr` as proof of a stale
runtime and `fuser -k -TERM`ed whoever held it — including a healthy runtime
mid-session, which the first session saw as a broken pipe. When `fuser` was
missing the error was swallowed and the socket unlinked anyway, leaving the old
process running and the operator to clean `~/.cloudxr/run/` by hand.

Liveness is now decided by connecting to the socket rather than by stat-ing it.
A live runtime makes the launcher refuse, pointing at the env file and
`--no-launch-cloudxr-runtime`; replacing it means stopping it yourself. A dead
socket is cleaned with a warning. Ambiguous probe errors count as live, since
refusing is recoverable and clobbering a session is not.

`-35` is thrown after `xrCreateInstance` succeeds, so the runtime was found and
only the headset is missing, but library defaults are fail-fast and a direct
consumer got a bare `Failed to get OpenXR system: -35`. Both throw sites now
name `XR_ERROR_FORM_FACTOR_UNAVAILABLE` and point at the docs. The device
profile is the usual culprit, so the startup banner prints the resolved value
and the launcher/env-config defaults agree on one constant — the docs claimed
`auto-webrtc` where `Quest3` always won. Overriding it stays an env-file or
`CloudXRLauncher(device_profile=...)` job; Apple Vision Pro needs
`auto-native`.

Closes #908

Signed-off-by: Jiwen Cai <jiwenc@nvidia.com>
@jiwenc-nv
jiwenc-nv enabled auto-merge (rebase) August 10, 2026 15:22
@jiwenc-nv
jiwenc-nv merged commit 53a1d7a into main Aug 10, 2026
42 checks passed
@jiwenc-nv
jiwenc-nv deleted the jiwenc-nv/IT908 branch August 10, 2026 15:40
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.

CloudXR runtime: a second launch SIGTERMs the live one, and -35 is unexplained outside the example entry points

2 participants