Skip to content

Release OpenXR session deterministically at context-manager exit#770

Open
jiwenc-nv wants to merge 1 commit into
NVIDIA:mainfrom
jiwenc-nv:fix_cxr_launcher
Open

Release OpenXR session deterministically at context-manager exit#770
jiwenc-nv wants to merge 1 commit into
NVIDIA:mainfrom
jiwenc-nv:fix_cxr_launcher

Conversation

@jiwenc-nv

@jiwenc-nv jiwenc-nv commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

CloudXR-backed runs (e.g. examples/mcap_record_replay/record_hand.py) flooded the console with "Broken pipe" IPC errors on shutdown:

ERROR [ipc_send] sendmsg(9) failed: '32' 'Broken pipe'!
ERROR [ref_space_dec] ipc_call_space_unmark_ref_space_in_use failed
ERROR [ipc_client_session_destroy] ipc_call_session_destroy failed

Root cause is timing, not ordering: OpenXRSession.exit was a no-op, so the OpenXR handles (space -> session -> instance) were only released by the C++ destructor at Python GC -- after CloudXRLauncher.exit had already SIGTERM'd the runtime and closed its IPC socket. The client-side xrDestroySpace/xrDestroySession then hit a dead socket -> EPIPE.

Give OpenXRSession an explicit, idempotent close() that resets the handles in reverse dependency order, and wire it into exit (mirroring the existing DeviceIOSession close()/exit() precedent). The ExitStack in TeleopSession already tears DeviceIOSession down before the OpenXR session, so handles are released while the runtime is still alive. Destroying a RUNNING session is spec-legal; the xrRequestExitSession / xrEndSession handshake is deliberately omitted (needs an event pump this class lacks and does not affect the dead-socket EPIPE).

TeleopSession.exit also drops its _oxr_session reference so the public oxr_session property honors its documented None contract post-exit.

Verified: oxr_py rebuilds clean; teleop_session_manager suite passes (121 passed); the new test_exit_clears_oxr_session fails on the pre-fix code (real guard). The native EPIPE fix itself requires a live CloudXR runtime to observe end-to-end (manual smoke test).

Designed and reviewed via a multi-agent panel (coder, tech-lead, bar-raiser, robotics-expert, external-developer, YAGNI): 3 planning rounds to consensus, unanimous ratification, 1 review round (all approve/approve-with-nits).

Description

Fixes #(issue)

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

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

Summary by CodeRabbit

  • New Features

    • Added explicit OpenXR session cleanup through a public close() method.
    • Python sessions now support direct cleanup and reliably close when leaving a context manager.
  • Bug Fixes

    • Teleoperation sessions no longer expose a closed OpenXR session after context-manager exit.
    • Session handle cleanup is now safer and repeatable.
  • Tests

    • Added coverage confirming the OpenXR session is cleared after session exit.

@github-actions

Copy link
Copy Markdown
Contributor

📝 Docs preview is not auto-deployed for fork PRs.

A maintainer with write access to NVIDIA/IsaacTeleop can deploy a preview by
commenting /preview-docs on this PR. Once deployed, the preview
will live at:

https://nvidia.github.io/IsaacTeleop/preview/pr-770/

@coderabbitai

coderabbitai Bot commented Jul 14, 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: 68e08174-072b-42e3-a85c-983e472c4f4e

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

OpenXRSession now provides explicit noexcept cleanup that resets its native handles without an OpenXR exit handshake. Its destructor calls close(). Python bindings expose close() and invoke it from __exit__. TeleopSession clears its internal OpenXR session reference after context-manager cleanup, and a test verifies the reference is available inside the context and None afterward.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TeleopSession
  participant ExitStack
  participant OpenXRSession
  participant NativeHandles
  TeleopSession->>ExitStack: exit context
  ExitStack->>OpenXRSession: invoke close()
  OpenXRSession->>NativeHandles: reset space, session, and instance
  TeleopSession->>TeleopSession: clear _oxr_session
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: deterministic OpenXR session release when the context manager exits.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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/core/teleop_session_manager/python/teleop_session.py`:
- Around line 1060-1063: Update the exit/cleanup method around
self._exit_stack.__exit__() to assign self._oxr_session = None in a finally
block, ensuring the reference is cleared even when managed cleanup raises.
Preserve the existing cleanup behavior and exception propagation while
guaranteeing the public oxr_session property returns None after exit.
🪄 Autofix (Beta)

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: 4ddb600d-f308-4558-bfa0-834c2bbb0a76

📥 Commits

Reviewing files that changed from the base of the PR and between 615cf8c and f54bfbd.

📒 Files selected for processing (5)
  • src/core/oxr/cpp/inc/oxr/oxr_session.hpp
  • src/core/oxr/cpp/oxr_session.cpp
  • src/core/oxr/python/oxr_bindings.cpp
  • src/core/teleop_session_manager/python/teleop_session.py
  • src/core/teleop_session_manager_tests/python/test_teleop_session.py

Comment thread src/core/teleop_session_manager/python/teleop_session.py Outdated
@jiwenc-nv jiwenc-nv requested a review from nv-jakob July 14, 2026 00:55
CloudXR-backed runs (e.g. examples/mcap_record_replay/record_hand.py)
flooded the console with "Broken pipe" IPC errors on shutdown:

    ERROR [ipc_send] sendmsg(9) failed: '32' 'Broken pipe'!
    ERROR [ref_space_dec] ipc_call_space_unmark_ref_space_in_use failed
    ERROR [ipc_client_session_destroy] ipc_call_session_destroy failed

Root cause is timing, not ordering: OpenXRSession.__exit__ was a no-op,
so the OpenXR handles (space -> session -> instance) were only released
by the C++ destructor at Python GC -- after CloudXRLauncher.__exit__ had
already SIGTERM'd the runtime and closed its IPC socket. The client-side
xrDestroySpace/xrDestroySession then hit a dead socket -> EPIPE.

Give OpenXRSession an explicit, idempotent close() that resets the
handles in reverse dependency order, and wire it into __exit__ (mirroring
the existing DeviceIOSession close()/exit() precedent). The ExitStack in
TeleopSession already tears DeviceIOSession down before the OpenXR
session, so handles are released while the runtime is still alive.
Destroying a RUNNING session is spec-legal; the xrRequestExitSession /
xrEndSession handshake is deliberately omitted (needs an event pump this
class lacks and does not affect the dead-socket EPIPE).

TeleopSession.__exit__ also drops its _oxr_session reference so the
public oxr_session property honors its documented None contract post-exit.

Verified: oxr_py rebuilds clean; teleop_session_manager suite passes
(121 passed); the new test_exit_clears_oxr_session fails on the pre-fix
code (real guard). The native EPIPE fix itself requires a live CloudXR
runtime to observe end-to-end (manual smoke test).

Designed and reviewed via a multi-agent panel (coder, tech-lead,
bar-raiser, robotics-expert, external-developer, YAGNI): 3 planning
rounds to consensus, unanimous ratification, 1 review round (all
approve/approve-with-nits).

Signed-off-by: Jiwen Cai <jiwenc@nvidia.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.

1 participant