fix: 安全关闭 MuJoCo 显示窗口

This commit is contained in:
2026-08-04 13:46:31 +08:00
parent 002484b610
commit 0dfe3d77dc
2 changed files with 36 additions and 0 deletions
@@ -1,9 +1,12 @@
import math import math
from pathlib import Path from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock
import pytest import pytest
import yaml import yaml
from xr_rm_mujoco import dual_arm_simulator as simulator_module
from xr_rm_mujoco.dual_arm_simulator import ( from xr_rm_mujoco.dual_arm_simulator import (
ARM_JOINT_NAMES, ARM_JOINT_NAMES,
DualArmKinematicModel, DualArmKinematicModel,
@@ -70,6 +73,34 @@ def test_mujoco_config_contains_only_render_parameters() -> None:
assert parameters == {"render_rate_hz": 60.0} assert parameters == {"render_rate_hz": 60.0}
def test_main_closes_viewer_cleanly_on_keyboard_interrupt(monkeypatch) -> None:
close_viewer = simulator_module.DualArmSimulator.close_viewer
viewer = Mock()
node = SimpleNamespace(_viewer=viewer, destroy_node=Mock())
node.close_viewer = lambda: close_viewer(node)
init = Mock()
spin = Mock(side_effect=KeyboardInterrupt)
sleep = Mock()
shutdown = Mock()
monkeypatch.setattr(simulator_module, "DualArmSimulator", lambda: node)
monkeypatch.setattr(simulator_module.rclpy, "init", init)
monkeypatch.setattr(simulator_module.rclpy, "spin", spin)
monkeypatch.setattr(simulator_module.rclpy, "ok", lambda: True)
monkeypatch.setattr(simulator_module.time, "sleep", sleep)
monkeypatch.setattr(simulator_module.rclpy, "shutdown", shutdown)
simulator_module.main(["--test"])
init.assert_called_once_with(args=["--test"])
spin.assert_called_once_with(node)
viewer.close.assert_called_once_with()
sleep.assert_called_once_with(0.1)
node.destroy_node.assert_called_once_with()
shutdown.assert_called_once_with()
assert node._viewer is None
@pytest.mark.parametrize( @pytest.mark.parametrize(
("names", "positions", "match"), ("names", "positions", "match"),
[ [
@@ -4,6 +4,7 @@ from __future__ import annotations
import math import math
from pathlib import Path from pathlib import Path
import time
from typing import Callable from typing import Callable
import mujoco import mujoco
@@ -179,6 +180,8 @@ class DualArmSimulator(Node):
def close_viewer(self) -> None: def close_viewer(self) -> None:
if self._viewer is not None: if self._viewer is not None:
self._viewer.close() self._viewer.close()
# MuJoCo 在后台 daemon 线程释放 GLX;立即退出解释器会触发段错误。
time.sleep(0.1)
self._viewer = None self._viewer = None
@@ -188,6 +191,8 @@ def main(args=None) -> None:
try: try:
node = DualArmSimulator() node = DualArmSimulator()
rclpy.spin(node) rclpy.spin(node)
except KeyboardInterrupt:
pass
finally: finally:
if node is not None: if node is not None:
node.close_viewer() node.close_viewer()