Skip to content

Commit 107e284

Browse files
fix(isaacsim): hard-exit camera assert harnesses past the Kit teardown deadlock
The 11 isaacsim camera tests each hung the full 700s subprocess timeout and were SIGKILL'd (-9) in the blocking `Gate: isaacsim tests` on Docker Build, turning it from ~12m into 2h41m and blocking :latest promotion. Root cause (proven by a native py-spy stack on the A10G): the tests were NOT hanging in rendering — rendering completes fine (get_camera_data returns a correct (1,64,64,3) uint8 frame). They hung on process EXIT. IsaacSim's atexit teardown deadlocks in carbOnPluginShutdown tearing down the omni.syntheticdata / OmniGraph render-product graph that a TiledCamera creates, so the subprocess never exits; run_harness's subprocess.run(700s) then times out and SIGKILLs it — turning a PASS (verdict already written to --result-file) into a spurious timeout failure. This is why the isaacsim tests that already os._exit (behavior_assert, scene_spawn_assert) passed while the camera harnesses using plain sys.exit(main()) hung; non-camera tests build no syntheticdata graph and tear down cleanly. Fix: each camera *_assert.py now flushes and os._exit(main()) after main() returns (the sentinel is already on disk), bypassing the deadlocking teardown — the pattern behavior_assert/scene_spawn_assert already use. Validated on the A10G CI runner: camera_geometry_assert.py renders, prints "geometry OK"/PASS, writes OK, and exits rc=0 with no teardown hang. Also adds a ruff per-file-ignore of PLC0415 for tests/simulators/*_assert.py (these standalone harnesses defer torch/isaaclab imports until after the SimApp is launched, inherent to IsaacSim), and fixes a pre-existing unused unpack (RUF059) in camera_follow_assert.py surfaced by linting the file.
1 parent b954d94 commit 107e284

10 files changed

Lines changed: 101 additions & 10 deletions

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ lint.ignore=[
4848
"src/holosoma/holosoma/utils/warp_utils.py" = ["UP018", "RUF046"]
4949
"src/holosoma/holosoma/simulator/isaacsim/**/*.py" = ["ALL"] # Disable until Jenkins has access to IsaacLab
5050
"src/holosoma/holosoma/train_agent.py" = ["PLC0415"] # Disable since imports after SimApp are inherent to IsaacSim
51+
"src/holosoma/tests/simulators/*_assert.py" = ["PLC0415"] # Standalone sim harnesses defer torch/isaaclab imports until after the SimApp is launched (inherent to IsaacSim)
5152
"src/holosoma/holosoma/utils/draw.py" = ["F401"] # Disable since unused imports are required for the adapters
5253
"src/holosoma/holosoma/utils/eval_utils.py" = ["PLC0415"] # Disable since imports after SimApp are inherent to IsaacSim
5354
"src/holosoma_inference/holosoma_inference/policies/base.py" = ["PLC0415"] # Deferred imports for optional heavy deps (ROS2, joystick, keyboard providers)

src/holosoma/tests/simulators/camera_actor_mount_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import argparse
1212
import dataclasses
13+
import os
1314
import sys
1415

1516
if sys.path and sys.path[0].endswith("simulators"):
@@ -102,4 +103,13 @@ def main() -> int:
102103

103104

104105
if __name__ == "__main__":
105-
sys.exit(main())
106+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
107+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
108+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
109+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
110+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
111+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
112+
_rc = main()
113+
sys.stdout.flush()
114+
sys.stderr.flush()
115+
os._exit(_rc)

src/holosoma/tests/simulators/camera_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import argparse
2323
import dataclasses
24+
import os
2425
import sys
2526

2627
# Pop this dir off sys.path[0] so tests/simulators/isaacsim/ can't shadow the real isaacsim pkg.
@@ -184,4 +185,13 @@ def main() -> int:
184185

185186

186187
if __name__ == "__main__":
187-
sys.exit(main())
188+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
189+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
190+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
191+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
192+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
193+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
194+
_rc = main()
195+
sys.stdout.flush()
196+
sys.stderr.flush()
197+
os._exit(_rc)

src/holosoma/tests/simulators/camera_follow_assert.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import argparse
1515
import dataclasses
16+
import os
1617
import sys
1718

1819
if sys.path and sys.path[0].endswith("simulators"):
@@ -124,7 +125,7 @@ def _place(x_offset: float, rot_xyzw) -> None:
124125
step(sim, max(2, steps_for_seconds(sim, 0.05)))
125126
sim.render_sensors()
126127

127-
cam_name, cam = next(iter(config.sensor.items()))
128+
cam_name, _cam = next(iter(config.sensor.items()))
128129
_place(0.0, base_rot)
129130
before = [
130131
_panel_median_depth(sim.get_camera_data(cam_name, "rgb")[e], sim.get_camera_data(cam_name, "depth")[e])
@@ -206,4 +207,13 @@ def _place(x_offset: float, rot_xyzw) -> None:
206207

207208

208209
if __name__ == "__main__":
209-
sys.exit(main())
210+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
211+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
212+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
213+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
214+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
215+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
216+
_rc = main()
217+
sys.stdout.flush()
218+
sys.stderr.flush()
219+
os._exit(_rc)

src/holosoma/tests/simulators/camera_geometry_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import argparse
2626
import dataclasses
2727
import math
28+
import os
2829
import sys
2930

3031
if sys.path and sys.path[0].endswith("simulators"):
@@ -205,4 +206,13 @@ def _pin_robot() -> None:
205206

206207

207208
if __name__ == "__main__":
208-
sys.exit(main())
209+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
210+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
211+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
212+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
213+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
214+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
215+
_rc = main()
216+
sys.stdout.flush()
217+
sys.stderr.flush()
218+
os._exit(_rc)

src/holosoma/tests/simulators/camera_multi_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import argparse
1111
import dataclasses
12+
import os
1213
import sys
1314

1415
if sys.path and sys.path[0].endswith("simulators"):
@@ -107,4 +108,13 @@ def main() -> int:
107108

108109

109110
if __name__ == "__main__":
110-
sys.exit(main())
111+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
112+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
113+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
114+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
115+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
116+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
117+
_rc = main()
118+
sys.stdout.flush()
119+
sys.stderr.flush()
120+
os._exit(_rc)

src/holosoma/tests/simulators/camera_obs_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import argparse
1818
import dataclasses
19+
import os
1920
import sys
2021

2122
if sys.path and sys.path[0].endswith("simulators"):
@@ -175,4 +176,13 @@ def _obs():
175176

176177

177178
if __name__ == "__main__":
178-
sys.exit(main())
179+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
180+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
181+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
182+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
183+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
184+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
185+
_rc = main()
186+
sys.stdout.flush()
187+
sys.stderr.flush()
188+
os._exit(_rc)

src/holosoma/tests/simulators/camera_orientation_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
import argparse
1717
import dataclasses
18+
import os
1819
import sys
1920

2021
if sys.path and sys.path[0].endswith("simulators"):
@@ -137,4 +138,13 @@ def main() -> int:
137138

138139

139140
if __name__ == "__main__":
140-
sys.exit(main())
141+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
142+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
143+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
144+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
145+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
146+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
147+
_rc = main()
148+
sys.stdout.flush()
149+
sys.stderr.flush()
150+
os._exit(_rc)

src/holosoma/tests/simulators/depth_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import argparse
1818
import dataclasses
1919
import math
20+
import os
2021
import sys
2122

2223
if sys.path and sys.path[0].endswith("simulators"):
@@ -184,4 +185,13 @@ def main() -> int:
184185

185186

186187
if __name__ == "__main__":
187-
sys.exit(main())
188+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
189+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
190+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
191+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
192+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
193+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
194+
_rc = main()
195+
sys.stdout.flush()
196+
sys.stderr.flush()
197+
os._exit(_rc)

src/holosoma/tests/simulators/world_camera_assert.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import argparse
2323
import dataclasses
24+
import os
2425
import sys
2526

2627
if sys.path and sys.path[0].endswith("simulators"):
@@ -227,4 +228,13 @@ def _reds():
227228

228229

229230
if __name__ == "__main__":
230-
sys.exit(main())
231+
# IsaacSim teardown deadlocks in carbOnPluginShutdown tearing down the
232+
# omni.syntheticdata/OmniGraph render-product graph a TiledCamera creates (native
233+
# py-spy stack), so a normal interpreter exit hangs until the parent's subprocess
234+
# timeout SIGKILLs it -- turning a PASS (verdict already written to --result-file) into
235+
# a spurious timeout failure. Hard-exit past the atexit teardown, mirroring
236+
# behavior_assert / scene_spawn_assert. Rendering itself is fine; only exit hangs.
237+
_rc = main()
238+
sys.stdout.flush()
239+
sys.stderr.flush()
240+
os._exit(_rc)

0 commit comments

Comments
 (0)