Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/core/cloudxr/python/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import re
import signal
import subprocess
import sys
import time

Expand Down Expand Up @@ -59,6 +60,18 @@ def _parse_args() -> argparse.Namespace:
action="store_true",
help="Accept the NVIDIA CloudXR EULA non-interactively (e.g. for CI or containers).",
)
parser.add_argument(
"--clean",
action="store_true",
default=False,
help=(
"Before launching, run `pkill -f 'isaacteleop.cloudxr.runtime'` "
"to release any stale CloudXR runtime worker subprocess left over "
"from a previous (crashed or detached) run. The current parent "
"process is not matched because its argv does not contain "
"`.runtime`."
),
)
parser.add_argument(
"--setup-oob",
action="store_true",
Expand Down Expand Up @@ -93,10 +106,40 @@ def _parse_args() -> argparse.Namespace:
return parser.parse_args()


def _clear_stale_runtime() -> None:
# pkill exit codes: 0 = matched & signalled, 1 = no match. Treat both as
# success; only louder codes (or a missing binary) get a warning so a stale
# runtime never silently blocks the new launch on the same ports.
print("\033[36mclear:\033[0m pkill -f 'isaacteleop.cloudxr.runtime'")
try:
result = subprocess.run(
["pkill", "-f", "isaacteleop.cloudxr.runtime"],
check=False,
)
Comment on lines +115 to +118

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add timeout to prevent indefinite blocking.

The subprocess.run() call lacks a timeout, so if pkill hangs the main process will block indefinitely. The established cleanup pattern in launcher.py (context snippet 1) uses timeout=5 for the same reason.

🛡️ Proposed fix: add timeout parameter
         result = subprocess.run(
             ["pkill", "-f", "isaacteleop.cloudxr.runtime"],
             check=False,
+            timeout=5,
         )
-    except FileNotFoundError:
+    except (FileNotFoundError, subprocess.TimeoutExpired):
         print(
-            "warning: pkill not found on PATH; cannot clear stale runtime",
+            "warning: pkill not found or timed out; cannot clear stale runtime",
             file=sys.stderr,
         )

Based on learnings from the existing cleanup implementation in launcher.py which uses timeout=5 and catches subprocess.TimeoutExpired.

🤖 Prompt for 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.

In `@src/core/cloudxr/python/__main__.py` around lines 115 - 118, The
subprocess.run call that executes ["pkill", "-f", "isaacteleop.cloudxr.runtime"]
(assigning to result) must include a timeout to avoid hanging; update that call
to pass timeout=5 and wrap it in a try/except that catches
subprocess.TimeoutExpired (similar to launcher.py) so you can handle the timeout
case (log or ignore) rather than blocking indefinitely.

except FileNotFoundError:
print(
"warning: pkill not found on PATH; cannot clear stale runtime",
file=sys.stderr,
)
return
if result.returncode == 0:
# Give the OS a brief moment to reap the killed worker and release the
# listening ports before CloudXRLauncher starts a fresh runtime.
time.sleep(0.5)
elif result.returncode != 1:
print(
f"warning: pkill exited {result.returncode}; continuing",
file=sys.stderr,
)


def main() -> None:
"""Launch the CloudXR runtime and WSS proxy, then block until interrupted."""
args = _parse_args()

if args.clean:
_clear_stale_runtime()

if args.usb_local and not args.setup_oob:
print(
"error: --usb-local requires --setup-oob",
Expand Down
Loading