Skip to content

Memory leak in exploration_node causes severe slowdown during full HM3D v2 benchmark (~14 GB RSS, 30+ min/episode after ~400 eps) #41

Open

Description

@WangJinCheng1998

While running the full HM3D v2 val benchmark (1000 episodes) with habitat_evaluation.py, I observed severe slowdown over time caused by what appears to be a memory leak in the C++ exploration_node. After ~420 episodes the planner's RSS grew to ~14 GB and per-episode time went from ~37s (episode 0) to 30+ minutes (episode ~420). Restarting only the ROS 2 planner (while keeping VLM servers and habitat_evaluation.py's continue.txt checkpoint) brought per-episode time back to ~60s and RSS down to ~500 MB.

Environment

  • OS: Ubuntu 22.04 (jammy) — note: ROS 2 Jazzy is Tier 3 here, source-built per official docs
  • ROS 2: Jazzy (built from source against the conda env's Python)
  • conda env: apexnav (Python 3.9, per apexnav_environment.yaml)
  • Branch: ros2-jazzy
  • GPU: NVIDIA RTX 4090, CUDA 12.8, PyTorch 2.8.0+cu128
  • VLM servers (default ports 12181-12184): all running fresh, never restarted during the run
  • Command:
    python habitat_evaluation.py --dataset hm3dv2 \
        habitat.dataset.split=val \
        need_video=true \
        test_epi_num=-1

Symptoms

Episode # Per-episode time exploration_node RSS
1 37 s ~500 MB (fresh)
100 ~70 s ~2 GB
300 ~150 s ~6 GB
420 ~30 min ~13.9 GB

After episode ~420 the system began swapping (60 GB total RAM, planner alone holding ~14 GB), turning the 60-second exploration loops into 30+ minutes due to OS-level page thrashing.

Reproduction

  1. Run the full HM3D v2 val split with test_epi_num=-1 (1000 episodes) and need_video=true.
  2. Periodically observe RSS of exploration_node via:
    ps -o pid,rss,etime,cmd -p $(pgrep -f exploration_node)
  3. Check the cumulative seconds spend in this task field in videos/test_hm3dv2_val/record.txt — the per-episode delta grows roughly monotonically from ~40s to thousands of seconds.

Workaround (works, but obviously not ideal)

I wrote a tmux watchdog that restarts only the ROS 2 planner every 200 episodes (or whenever its RSS exceeds 5 GB), while keeping the 4 VLM servers and habitat_evaluation.py running. Because continue.txt checkpoints per-episode, evaluation resumes seamlessly from where it left off, and per-episode time returns to ~30-60s after each restart.

Possible Causes

In exploration_fsm.cpp lines 14-20 the FSM code does fp_.reset(...), fd_.reset(...), expl_manager_.reset(...) etc. on each episode reset, which suggests inter-episode reset was intended. But something downstream — possibly:

  • a shared_ptr to point clouds / occupancy grids / SDF map is held by another component (visualization? mapping? planner cache?) and never released
  • PCL / Octomap-style data structures that grow but never shrink
  • accumulated frontiers / TF buffer / parameter listeners

—keeps the previous episode's allocations alive across resets.

Thanks for the great work.

Activity

  1. Zager-Zhang commented on Apr 27, 2026

    @Zager-Zhang
    Member

    Thank you for the detailed report and reproduction steps.

    I think this issue may be related to the ROS 2 branch. This branch was contributed by community users and may not be as mature or fully tested as the official version. In the official version, we have not received similar reports from users so far.

    Your observation does look like some resources are not being properly released across episodes, possibly due to differences in the ROS 2 port.

  2. zhu-hang131433 commented on May 22, 2026

    @zhu-hang131433

    Bug Fix Report: Episode Reset 内存泄漏

    问题现象

    exploration_node 进程在跨 episode 运行时内存持续增长(呈指数级),最终导致 OOM。

    根因分析

    发现两个独立的泄漏源:

    Bug 1: ObjectMap2D / ValueMap reset 后缓冲区大小归零(堆破坏)

    ObjectMap2D::reset() 和 ValueMap::reset() 中在 clear() + shrink_to_fit() 之后调用 assign(xxx.size(), val),此时 size() 已为 0,assign(0, val) 是空操作。Reset 后缓冲区大小为 0,后续代码通过 operator[] 以 voxel 索引直接访问,造成堆越界读写,破坏堆元数据。

    HABITAT_STATE::EPISODE_FINISH → init(node_) → sdf_map_->resetMap()
      → object_map2d_->reset()   ← Bug
      → value_map_->reset()      ← Bug
    

    Bug 2: Astar2D 每 episode 重新 init 导致 100 万 Node2D 泄漏(主因)

    Astar2D::init() 每 episode 通过 ExplorationManager::reset() 被重复调用,每次创建 1,000,000 个 new Node2D 对象并覆盖旧指针,旧对象永不释放。

    ExplorationManager::reset() → path_finder_->init(node_, sdf_map_)
      → Astar2D::init() → resize(1000000) + new Node2D × 1000000  ← 每 episode 泄漏 ~48MB
    

    Astar2D::reset() 已正确实现了搜索状态重置,只需调它而非 init()。

    修复内容

    Fix 1: src/planner/plan_env/src/object_map2d.cpp:67-74

    + int voxel_num = sdf_map_->getVoxelNum();
    - object_buffer_.assign(object_buffer_.size(), 0);
    + object_buffer_.assign(voxel_num, 0);
    - object_indexs_.assign(object_indexs_.size(), -1);
    + object_indexs_.assign(voxel_num, -1);

    Fix 2: src/planner/plan_env/src/value_map2d.cpp:27-34

    + int voxel_num = sdf_map_->getVoxelNum();
    - value_buffer_.assign(value_buffer_.size(), 0.0);
    + value_buffer_.assign(voxel_num, 0.0);
    - confidence_buffer_.assign(confidence_buffer_.size(), 0.0);
    + confidence_buffer_.assign(voxel_num, 0.0);

    Fix 3: src/planner/exploration_manager/src/exploration_manager.cpp:92

    - path_finder_->init(node_, sdf_map_);
    + path_finder_->reset();
  3. Zager-Zhang commented on May 27, 2026

    @Zager-Zhang
    Member

    @zhu-hang131433 Thanks for the report. I have fixed the confirmed issue in the ROS2 Jazzy branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions