NVIDIA / NVIDIA/IsaacTeleop

CloudXR + Apple Vision Pro: ~50% of connects permanently wedge xrWaitFrame (pthread_cond_clockwait in omni.kit.xr.core); session never torn down, reconnect impossible until process restart

Open
#1,000 0 comments 0 reactions 1 assignee View on GitHub

@rwiltz is already working on this.

Since Aug 29, 2026.

Dominant language
Python
Stars
385
Forks
88
Avg merge
3d 23h
Merged PRs (30d)
82

Description

Summary

In a headless Isaac Lab + IsaacTeleop teleoperation deployment streamed to an Apple Vision Pro via CloudXR, roughly 50% of first client connects permanently wedge the server inside xrWaitFrame. The session is never torn down, so client reconnects cannot renegotiate; only restarting the server process recovers. The other ~50% of connects work normally.

Environment

  • Isaac Lab 3.0 generation (isaaclab_physx backend), running headless with --xr
  • Kit XR extensions: omni.kit.xr.system.openxr-109.0.0, isaaclab.python.xr.openxr-3.0.0 (+ .headless-3.0.0)
  • IsaacTeleop commit 6ee69410efc453b3af0160f5e59fedf260ed941e
  • CloudXR runtime launched in-container by the IsaacTeleop launcher; runtime binaries identify as 6.3.0-rc4 (Monado-based OpenXR runtime)
  • GPU: RTX PRO 6000 Blackwell Server Edition, driver 580.159.03, Ubuntu 24.04
  • Kubernetes pod behind a cloud network load balancer, ports exactly per the Isaac Lab CloudXR teleoperation docs (TCP 48010 + UDP 47998/47999/48000/48005/48008/48012)
  • Client: Isaac XR Teleop Sample Client 3.0.1 on Apple Vision Pro, visionOS 26.3.1. Reproduced with CloudXRKit 6.0.3 (stock pin) and 6.2.0.

Symptom

On ~50% of first connects (intermittent; the rest work):

  1. Client connects; server logs IsaacTeleop session started; at most a handful of hand-tracking frames arrive, then the pose/tracker path stops permanently (an MCAP recording of the tracker stream stays at 0 bytes).

  2. Client display: with CloudXRKit 6.0.3, one frozen (stale) frame; with CloudXRKit 6.2.0 (which fixed the stale-frame-on-connect), a blank view. Same server-side failure either way.

  3. The server main thread is permanently blocked inside Kit's XR frame wait. Native stack captured during a live wedge (py-spy --native):

    Thread (MainThread):
        pthread_cond_clockwait (libc.so.6)
        libomni.kit.xr.core.plugin.so (+0x...506, +0x...741)
        libomni.usd.so
        libcarb.eventdispatcher.plugin.so
        libomni.kit.loop-isaac.plugin.so
        libomni.kit.app.plugin.so  (app update)
        ... Python: SimulationContext.render -> app update
    

    GPU utilization drops to ~0%. The wait never returns (observed > 30 minutes).

  4. The CloudXR opaque data channel outlives the wedge: client {"type":"ping"} keepalives keep arriving throughout (logged by omni.kit.xr.system.openxr.plugin as Message received from CloudXR has no 'message' or 'payload' field).

  5. Client disconnect/reconnect does not recover: the runtime never tears the session down (no teardown log ever appears) and no new session can be negotiated. Only a process/container restart recovers. Application-side teardown is impossible because Kit pumps the XR session state machine from the thread blocked inside the frame wait.

Timeline detail

Headless --xr creates the OpenXR session and trackers ~45–60 s before any client attaches. The wedge occurs during the session/pacing state cycle at client attach.

Mitigations tested (none prevent the wedge)

  • NV_ENABLE_POSE_WAIT=0 (present in the shipped avp-cloudxr.env) — wedge still occurs.
  • NV_PACER_FIXED_TIME_STEP_MS=33 — verified loaded via env override; wedge still occurred on the next connect with an identical native stack.
  • Server runtime already 6.3.0-rc4; client rebuilt on CloudXRKit 6.2.0 — changes the frozen frame to a blank view, does not prevent the wedge.
  • Container CPU allocation raised 14 → 48 cores — no effect on wedge frequency.

Possibly related upstream

  • Monado MR !2344 (supersedes !1934): frame-sync semaphore left at 0 across session state cycling makes the next xrWaitFrame block forever; fixed in Monado 25.0.0. Is the CloudXR runtime's Monado fork point past this fix?
  • Monado issue #492: a wedged per-client IPC thread leaves a zombie session the runtime can never tear down (teardown would run on the wedged thread) — matches the reconnect-impossible behavior exactly.
  • isaac-sim/IsaacLab#6214 and isaac-sim/IsaacLab#5575 (hand-pose stream dying while streaming works).

Ruled out

  • xrCreateHandTrackerEXT ... XDev does not support hand tracking spam at session start: expected XDev enumeration until the client hand device attaches (per #344). Occurs on healthy sessions too.
  • Load balancer / port configuration (matches the docs; the data channel survives the wedge, so transport is alive).

Questions

  1. What is the CloudXR OpenXR runtime's Monado fork point relative to Monado 25.0.0 / MR !2344?
  2. Which condition variable in omni.kit.xr.core is the main thread waiting on here, and what runtime-side signal is expected to satisfy it? Under what conditions can that wait be unbounded when the client pose/timing stream dies?
  3. Can the runtime detect a client whose pose stream died (data channel still live) and force session teardown so the app's frame wait returns with a session-loss event?

Attachments available on request

Full server logs of wedged and healthy connects, py-spy native stack dumps of the Kit process and the CloudXR runtime process captured during a live wedge, and deployment/load-balancer configuration.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.