Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions source/isaaclab/isaaclab/envs/manager_based_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,11 @@ def close(self):
# Stop simulation first to allow physics to clean up properly
self.sim.stop()

# Drop cached observation tensors so they don't survive close via
# gymnasium's wrapper chain.
if isinstance(getattr(self, "obs_buf", None), dict):
self.obs_buf.clear()

# destructor is order-sensitive
del self.viewport_camera_controller
del self.action_manager
Expand Down
6 changes: 6 additions & 0 deletions source/isaaclab/isaaclab/envs/manager_based_rl_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ def close(self):
del self.reward_manager
del self.termination_manager
del self.curriculum_manager
# Release gym.spaces.Box bounds arrays set in _configure_gym_env_spaces;
# without this they survive close via gymnasium's wrapper chain.
self.single_observation_space = None
self.single_action_space = None
self.observation_space = None
self.action_space = None
# call the parent class to close the environment
super().close()

Expand Down
50 changes: 50 additions & 0 deletions source/isaaclab/test/envs/test_manager_based_env_close_leak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Regression test for the ``ManagerBasedEnv.close`` memory leak fixed in this PR."""

"""Launch Isaac Sim Simulator first."""

from isaaclab.app import AppLauncher

simulation_app = AppLauncher(headless=True).app

"""Rest everything follows."""

import gymnasium as gym
import pytest
import torch

import isaaclab_tasks # noqa: F401 -- registers Isaac-* env IDs with gymnasium
from isaaclab_tasks.utils.parse_cfg import parse_env_cfg


@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
def test_close_clears_obs_buf_and_releases_spaces(device):
"""``ManagerBasedEnv.close`` should release the cached observation buffer and the
gym space attributes attached to the env.

Without the release, gymnasium's wrapper chain (e.g. ``OrderEnforcing`` plus the
``make()`` registry) keeps the env reachable past ``close``, so ``self.obs_buf``
(the dict of cached observation tensors — tens of MB per image sensor) and the
``observation_space`` / ``action_space`` ``gym.spaces.Box`` attributes (~110 MB
of numpy bounds-array memory per image-observation Box at ``(num_envs, H, W, C)``
float32) survive the call and accumulate on each construct/teardown cycle.
"""
env_cfg = parse_env_cfg("Isaac-Cartpole-v0", device=device, num_envs=2)
env = gym.make("Isaac-Cartpole-v0", cfg=env_cfg)
env.reset()
# Step once so the ObservationManager populates ``obs_buf``.
action = torch.zeros((2, env.action_space.shape[-1]), device=env.unwrapped.device)
env.step(action)
assert env.unwrapped.obs_buf, "precondition: obs_buf should be populated after step"
assert env.unwrapped.observation_space is not None, "precondition: observation_space should be set"
assert env.unwrapped.action_space is not None, "precondition: action_space should be set"

env.close()

assert not env.unwrapped.obs_buf, "obs_buf was not cleared on close"
assert env.unwrapped.observation_space is None, "observation_space was not released on close"
assert env.unwrapped.action_space is None, "action_space was not released on close"
Comment on lines +44 to +50
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.

P2 Missing assertions for single_observation_space / single_action_space

The fix in ManagerBasedRLEnv.close() nulls all four space attributes (single_observation_space, single_action_space, observation_space, action_space), but the test only asserts the last two. The single_* variants are the per-environment gym.spaces.Box objects described in the PR description as holding ~110 MB of bounds arrays for image observations — they are exactly the attributes whose memory the fix aims to release. Leaving them unchecked means a regression that stops nulling single_observation_space would go undetected.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Loading