From c27530a32dca915d9472657ed3e69a08f8c064fe Mon Sep 17 00:00:00 2001 From: Xinjie Yao Date: Wed, 27 May 2026 16:08:18 -0700 Subject: [PATCH 1/2] retry/timeout --- isaaclab_arena/assets/background_library.py | 11 +++- isaaclab_arena/assets/lightwheel_utils.py | 58 ++++++++++++++++++ isaaclab_arena/assets/object_library.py | 55 +++++++++++++---- isaaclab_arena/tests/test_lightwheel.py | 67 +++++++++++++++++++++ 4 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 isaaclab_arena/assets/lightwheel_utils.py create mode 100644 isaaclab_arena/tests/test_lightwheel.py diff --git a/isaaclab_arena/assets/background_library.py b/isaaclab_arena/assets/background_library.py index 379567fb0..decceadd6 100644 --- a/isaaclab_arena/assets/background_library.py +++ b/isaaclab_arena/assets/background_library.py @@ -8,6 +8,7 @@ from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, ISAACLAB_NUCLEUS_DIR from isaaclab_arena.assets.background import Background +from isaaclab_arena.assets.lightwheel_utils import acquire_lightwheel_asset from isaaclab_arena.assets.register import register_asset from isaaclab_arena.utils.pose import Pose @@ -155,8 +156,14 @@ def __init__(self, layout_id: int = 1, style_id: int = 1): # Lazily download the USD self.usd_path = str( - floorplan_loader.get_usd( - scene="robocasakitchen", layout_id=layout_id, style_id=style_id, backend="robocasa" + acquire_lightwheel_asset( + floorplan_loader, + floorplan_loader.get_usd, + description=f"{self.name} background layout={layout_id} style={style_id}", + scene="robocasakitchen", + layout_id=layout_id, + style_id=style_id, + backend="robocasa", )[0] ) super().__init__() diff --git a/isaaclab_arena/assets/lightwheel_utils.py b/isaaclab_arena/assets/lightwheel_utils.py new file mode 100644 index 000000000..f3f2ff555 --- /dev/null +++ b/isaaclab_arena/assets/lightwheel_utils.py @@ -0,0 +1,58 @@ +# Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +import time +from collections.abc import Callable +from typing import Any + +_MISSING = object() + + +def acquire_lightwheel_asset( + loader: Any, + acquire_fn: Callable, + description: str, + attempts: int = 3, + timeout_sec: int | None = 60, + delay_sec: float = 2.0, + **kwargs, +): + """Acquire a Lightwheel asset with scoped timeout and retry handling.""" + + assert attempts > 0, "attempts must be positive" + assert delay_sec >= 0, "delay_sec must be non-negative" + + client = getattr(loader, "client", None) + old_timeout = getattr(client, "base_timeout", _MISSING) + for attempt in range(1, attempts + 1): + try: + if timeout_sec is not None and old_timeout is not _MISSING: + client.base_timeout = timeout_sec + return acquire_fn(**kwargs) + except Exception as exc: + if not _looks_like_timeout(exc) or attempt == attempts: + raise + print( + f"[isaaclab-arena] {description} timed out; retrying {attempt + 1}/{attempts} " + f"in {delay_sec:g}s." + ) + if delay_sec > 0: + time.sleep(delay_sec) + finally: + if old_timeout is not _MISSING: + client.base_timeout = old_timeout + + raise AssertionError("unreachable") + + +def _looks_like_timeout(exc: BaseException) -> bool: + """Return whether an exception is a timeout.""" + current: BaseException | None = exc + while current is not None: + text = f"{type(current).__name__}: {current}".lower() + if "timeout" in text or "timed out" in text: + return True + current = current.__cause__ or current.__context__ + return False diff --git a/isaaclab_arena/assets/object_library.py b/isaaclab_arena/assets/object_library.py index 609b86b6d..d7ad7a941 100644 --- a/isaaclab_arena/assets/object_library.py +++ b/isaaclab_arena/assets/object_library.py @@ -18,6 +18,7 @@ from isaaclab_arena.affordances.placeable import Placeable from isaaclab_arena.affordances.pressable import Pressable from isaaclab_arena.affordances.turnable import Turnable +from isaaclab_arena.assets.lightwheel_utils import acquire_lightwheel_asset from isaaclab_arena.assets.object import Object from isaaclab_arena.assets.object_base import ObjectType from isaaclab_arena.assets.object_utils import ( @@ -176,8 +177,13 @@ class Microwave(LibraryObject, Openable): name = "microwave" tags = ["object", "openable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="fixtures", file_name="Microwave039", file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="microwave asset", + registry_type="fixtures", + file_name="Microwave039", + file_type="USD", ) usd_path = file_path object_type = ObjectType.ARTICULATION @@ -209,8 +215,13 @@ class CoffeeMachine(LibraryObject, Pressable): name = "coffee_machine" tags = ["object", "pressable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="fixtures", file_name="CoffeeMachine108", file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="coffee_machine asset", + registry_type="fixtures", + file_name="CoffeeMachine108", + file_type="USD", ) usd_path = file_path object_type = ObjectType.ARTICULATION @@ -677,8 +688,13 @@ class Broccoli(LibraryObject): name = "broccoli" tags = ["object", "vegetable", "graspable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="objects", registry_name=["broccoli"], file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="broccoli asset", + registry_type="objects", + registry_name=["broccoli"], + file_type="USD", ) usd_path = file_path object_type = ObjectType.RIGID @@ -704,8 +720,13 @@ class SweetPotato(LibraryObject): name = "sweet_potato" tags = ["object", "vegetable", "graspable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="objects", file_name="SweetPotato005", file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="sweet_potato asset", + registry_type="objects", + file_name="SweetPotato005", + file_type="USD", ) usd_path = file_path object_type = ObjectType.RIGID @@ -732,8 +753,13 @@ class Jug(LibraryObject): name = "jug" tags = ["object", "graspable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="objects", file_name="Jug005", file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="jug asset", + registry_type="objects", + file_name="Jug005", + file_type="USD", ) usd_path = file_path object_type = ObjectType.RIGID @@ -760,8 +786,13 @@ class BeerBottle(LibraryObject): name = "beer_bottle" tags = ["object", "graspable"] - file_path, object_name, metadata = object_loader.acquire_by_registry( - registry_type="objects", file_name="beer016", file_type="USD" + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + description="beer_bottle asset", + registry_type="objects", + file_name="beer016", + file_type="USD", ) usd_path = file_path object_type = ObjectType.RIGID diff --git a/isaaclab_arena/tests/test_lightwheel.py b/isaaclab_arena/tests/test_lightwheel.py new file mode 100644 index 000000000..71ec4459d --- /dev/null +++ b/isaaclab_arena/tests/test_lightwheel.py @@ -0,0 +1,67 @@ +# Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +import pytest + +from isaaclab_arena.assets.lightwheel_utils import acquire_lightwheel_asset + + +def test_acquire_lightwheel_asset_fetches_object(): + loader_module = pytest.importorskip("lightwheel_sdk.loader") + object_loader = loader_module.object_loader + old_timeout = object_loader.client.base_timeout + + file_path, object_name, metadata = acquire_lightwheel_asset( + object_loader, + object_loader.acquire_by_registry, + "microwave asset", + attempts=2, + delay_sec=0, + registry_type="fixtures", + file_name="Microwave039", + file_type="USD", + ) + + assert Path(file_path).exists() + assert object_name == "Microwave039" + assert metadata["fileName"].startswith("Microwave039") + assert object_loader.client.base_timeout == old_timeout + + +def test_acquire_lightwheel_asset_retries_timeout_failure(): + class Client: + base_timeout = 10 + + class Loader: + def __init__(self): + self.client = Client() + self.calls = 0 + self.timeouts_seen = [] + + def acquire(self, **kwargs): + self.calls += 1 + self.timeouts_seen.append(self.client.base_timeout) + if self.calls == 1: + raise TimeoutError("read timed out") + return kwargs["asset_name"] + + loader = Loader() + + result = acquire_lightwheel_asset( + loader, + loader.acquire, + "test asset", + attempts=2, + timeout_sec=45, + delay_sec=0, + asset_name="foo", + ) + + assert result == "foo" + assert loader.calls == 2 + assert loader.timeouts_seen == [45, 45] + assert loader.client.base_timeout == 10 From 5175954f278f47ec81a3389f5ae4ccad30f17b8a Mon Sep 17 00:00:00 2001 From: Xinjie Yao Date: Wed, 27 May 2026 17:23:53 -0700 Subject: [PATCH 2/2] lint --- isaaclab_arena/assets/lightwheel_utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/isaaclab_arena/assets/lightwheel_utils.py b/isaaclab_arena/assets/lightwheel_utils.py index f3f2ff555..a7e561a6f 100644 --- a/isaaclab_arena/assets/lightwheel_utils.py +++ b/isaaclab_arena/assets/lightwheel_utils.py @@ -34,10 +34,7 @@ def acquire_lightwheel_asset( except Exception as exc: if not _looks_like_timeout(exc) or attempt == attempts: raise - print( - f"[isaaclab-arena] {description} timed out; retrying {attempt + 1}/{attempts} " - f"in {delay_sec:g}s." - ) + print(f"[isaaclab-arena] {description} timed out; retrying {attempt + 1}/{attempts} in {delay_sec:g}s.") if delay_sec > 0: time.sleep(delay_sec) finally: