Skip to content

Switch assets.py omni.client calls to async#5553

Open
dengyuchenkit wants to merge 2 commits into
isaac-sim:developfrom
dengyuchenkit:ydeng/fix-omniclient-blocking
Open

Switch assets.py omni.client calls to async#5553
dengyuchenkit wants to merge 2 commits into
isaac-sim:developfrom
dengyuchenkit:ydeng/fix-omniclient-blocking

Conversation

@dengyuchenkit
Copy link
Copy Markdown

@dengyuchenkit dengyuchenkit commented May 8, 2026

The four sync calls (stat, copy, read_file) in check_file_path, retrieve_file_path, and read_file parked Kit's main thread on every Nucleus round-trip and triggered Kit's "Detected a blocking function" warning during env init.

They now run through a new _drive_kit_async helper, which schedules the *_async coroutine on Kit's asyncio loop and ticks app.update() until it resolves -- same wall time, but Kit keeps rendering during the wait.

Fixes # (issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (existing functionality will not work without user modification)
  • Documentation update

Screenshots

Please attach before and after screenshots of the change if applicable.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions Bot added bug Something isn't working isaac-sim Related to Isaac Sim team isaac-lab Related to Isaac Lab team labels May 8, 2026
@dengyuchenkit dengyuchenkit marked this pull request as draft May 8, 2026 20:58
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR replaces four synchronous omni.client calls (stat, copy, read_file) in assets.py with their _async counterparts, driven through a new _drive_kit_async helper that schedules work on Kit's asyncio loop and ticks app.update() until completion — eliminating the "Detected a blocking function" warning that fired on every Nucleus round-trip during env init. All six apps/*.kit configs gain an explicit omni.kit.async_engine dependency to ensure the extension is available on every execution path.

  • _drive_kit_async uses omni.kit.async_engine.run_coroutine() when Kit is running, and falls back to asyncio.run() when it is not, keeping the helper safe for both Kit and standalone Python contexts.
  • Six .kit files each add \"omni.kit.async_engine\" = {} to their dependency list; three of these (headless.rendering, rendering, xr.openxr.headless) already inherit the extension transitively from their parent configs, making those additions redundant but harmless.

Confidence Score: 5/5

The change is safe to merge — it replaces blocking sync Nucleus calls with async equivalents using a well-scoped helper, and all six app configs are updated to carry the required extension dependency.

The async migration is mechanically straightforward: each replaced call maps 1-to-1 onto its _async counterpart, the return-value indexing is preserved, and the driving loop correctly delegates to app.update() to keep Kit rendering. Both Kit and non-Kit execution paths are handled, and the omni.kit.async_engine extension is now declared in every affected config.

source/isaaclab/isaaclab/utils/assets.py — specifically the _drive_kit_async helper, which carries the core logic of the async scheduling approach.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/utils/assets.py Adds _kit_app() and _drive_kit_async() helpers; replaces four synchronous omni.client calls (stat, copy, read_file) with their async counterparts driven through the new helper.
apps/isaaclab.python.headless.kit Adds omni.kit.async_engine dependency to the headless app config, ensuring the helper function can resolve the extension on the most common training/CI path.
apps/isaaclab.python.kit Adds omni.kit.async_engine dependency to the GUI app config.
apps/isaaclab.python.headless.rendering.kit Adds omni.kit.async_engine dependency; already transitively available through its isaaclab.python.headless parent, so this is a redundant but harmless explicit declaration.
apps/isaaclab.python.rendering.kit Adds omni.kit.async_engine dependency; already transitively available through isaaclab.python parent.
apps/isaaclab.python.xr.openxr.kit Adds omni.kit.async_engine dependency to the XR OpenXR app config.
apps/isaaclab.python.xr.openxr.headless.kit Adds omni.kit.async_engine dependency; already transitively available via isaaclab.python.xr.openxr parent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["_drive_kit_async(coro)"] --> B{"Kit app running?"}
    B -- "No" --> C["asyncio.run(coro)"]
    B -- "Yes" --> D["import omni.kit.async_engine"]
    D -- "OK" --> E["run_coroutine(coro) → task"]
    D -- "Exception" --> F["ensure_future(coro) → task"]
    E --> G["while not task.done(): app.update()"]
    F --> G
    G --> H["task.result()"]
    C --> I["return result"]
    H --> I
Loading

Reviews (2): Last reviewed commit: "Switch assets.py omni.client calls to as..." | Re-trigger Greptile

Comment on lines +74 to +76
app = _kit_app()
if app is None:
return asyncio.get_event_loop().run_until_complete(coro)
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.

P1 Replace the deprecated asyncio.get_event_loop().run_until_complete() call with asyncio.run(), which is safe in Python 3.10+ and always creates a fresh, clean event loop for the non-Kit path.

Suggested change
app = _kit_app()
if app is None:
return asyncio.get_event_loop().run_until_complete(coro)
app = _kit_app()
if app is None:
return asyncio.run(coro)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated in latest commit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good, thanks!

Comment on lines +77 to +85
try:
import omni.kit.async_engine # noqa: PLC0415

task = omni.kit.async_engine.run_coroutine(coro)
except Exception:
task = asyncio.ensure_future(coro)
while not task.done():
app.update()
time.sleep(0)
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.

P1 Silent fallback to asyncio.ensure_future() can cause an infinite loop

The broad except Exception catches any failure from importing or calling omni.kit.async_engine.run_coroutine(). When the import fails (e.g., headless kit configs that don't load omni.kit.async_engine — all five other .kit files in apps/ are missing this dependency), execution falls through to asyncio.ensure_future(coro). In synchronous context on Kit's main thread there is no running asyncio loop, so ensure_future schedules the task on whatever loop asyncio.get_event_loop() returns — which is not the loop that app.update() pumps. The while not task.done() loop then spins forever, hanging the application. isaaclab.python.headless.kit (the most common training/CI variant) does not list omni.kit.async_engine and will reliably hit this path.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

  1. This PR adds async_engine to all apps/*.kit files, so calling async_engine won't hit fallback and won't fail in all configs.
  2. Also removed the try/except so if async_engine is not imported, it fails loudly instead of silently hits fallback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the clarification! I see "omni.kit.async_engine" = {} is now added to all the apps/*.kit files, which ensures the extension is loaded in all supported configurations.

The fallback to asyncio.ensure_future is still there as defensive code, but it should no longer be hit since async_engine is now guaranteed to be available. This looks good to me. 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This PR is updated and removed the fallback to asyncio.ensure_future.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great, that's cleaner. Thanks for the update!

The four sync calls (stat, copy, read_file) in check_file_path,
retrieve_file_path, and read_file parked Kit's main thread on every
Nucleus round-trip and triggered Kit's "Detected a blocking function"
warning during env init.

They now run through a new _drive_kit_async helper, which schedules
the *_async coroutine on Kit's asyncio loop and ticks app.update()
until it resolves -- same wall time, but Kit keeps rendering during
the wait.

Add omni.kit.async_engine to every IsaacLab .kit file so its observer
steps the asyncio loop each Kit frame; without it the spin loop would
deadlock (headless and CI runs would hang on any Nucleus I/O). The
no-Kit fallback uses asyncio.run for Python 3.12+ compatibility.
@dengyuchenkit dengyuchenkit force-pushed the ydeng/fix-omniclient-blocking branch from bb17e09 to 3838492 Compare May 8, 2026 22:24
@dengyuchenkit dengyuchenkit changed the title Use async omni.client APIs in assets utilities Switch assets.py omni.client calls to async May 8, 2026
@dengyuchenkit dengyuchenkit marked this pull request as ready for review May 11, 2026 18:19
The try/except around omni.kit.async_engine in _drive_kit_async would
schedule the coroutine onto asyncio.get_event_loop() if the import or
run_coroutine call raised. That loop is never pumped by app.update(),
so the spin loop would silently hang forever.

All six bundled apps/*.kit files declare the dependency, so the
fallback isn't reachable from IsaacLab itself. But a stripped or
forked downstream .kit could drop it, turning every Nucleus I/O into
an unrecoverable freeze with no traceback.

Let the import raise instead. Custom configs missing the dependency
now fail immediately with a clear ImportError pointing at the cause.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working isaac-lab Related to Isaac Lab team isaac-sim Related to Isaac Sim team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant