Skip to content

fix: reuse visited lists across sub-pools#2435

Open
jac0626 wants to merge 2 commits into
antgroup:mainfrom
jac0626:codex/reuse-visited-list-subpools
Open

fix: reuse visited lists across sub-pools#2435
jac0626 wants to merge 2 commits into
antgroup:mainfrom
jac0626:codex/reuse-visited-list-subpools

Conversation

@jac0626

@jac0626 jac0626 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Change Type

  • Improvement/Refactor

Linked Issue

What Changed

  • Scan all visited-list sub-pools for an idle object before allocating a new one.
  • Preserve the existing thread-local sub-pool preference and non-blocking lock strategy.
  • Add regression coverage for reusing an object from another sub-pool.

Test Evidence

  • Other (described below)

Test details:

clang-format 15.0.7 --dry-run --Werror (changed files): passed
make test CASE='[ut][VisitedListPool]':
All tests passed (10001 assertions in 2 test cases)

Compatibility Impact

  • API/ABI compatibility: none
  • Behavior changes: pool allocation occurs only after all sub-pools are observed empty

Performance and Concurrency Impact

  • Performance impact: reduces unnecessary large visited-list allocations
  • Concurrency/thread-safety impact: retains the existing per-sub-pool mutexes and try_lock behavior

Documentation Impact

  • No docs update needed

Risk and Rollback

  • Risk level: low
  • Rollback plan: revert the commit to restore the previous allocation policy

Checklist

  • I have linked the relevant issue
  • I have added/updated tests for the changed behavior
  • I have considered API compatibility impact
  • My commit messages follow project conventions

Drafted with AI assistant: Codex:gpt-5

Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
Copilot AI review requested due to automatic review settings July 13, 2026 03:07
@vsag-bot

vsag-bot commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

/label S-waiting-on-review
/waiting-on reviewer
/request-review @jiaweizone
/request-review @wxyucs
/request-review @inabao
/request-review @LHT129

@jac0626 jac0626 added kind/improvement Optimizations, UX polish, or minor improvements 性能优化、体验打磨或细节改良 module/index version/1.0 labels Jul 13, 2026
@mergify

mergify Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 2 merge protections satisfied — ready to merge.

Show 2 satisfied protections

🟢 Require kind label

  • label~=^kind/

🟢 Require version label

  • label~=^version/

@jac0626
jac0626 marked this pull request as ready for review July 13, 2026 03:08

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request updates the object retrieval logic in ResourceObjectPool to prioritize reusing existing objects across all sub-pools before constructing new ones, and adds a unit test to verify this behavior. However, the current implementation introduces a critical concurrency issue: if sub-pools are locked by other threads, try_lock() fails, causing the outer while (true) loop to busy-spin and consume 100% CPU. It is recommended to replace this loop with a single-pass check over the sub-pools, falling back to constructing a new object if no existing objects can be safely retrieved.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 74 to 100
while (true) {
if (sub_pool_mutexes_[pool_id].try_lock()) {
prefer_pool_id_ = pool_id;
if (pool_[pool_id]->empty()) {
sub_pool_mutexes_[pool_id].unlock();
auto obj = this->constructor_();
obj->source_pool_id_ = pool_id;
return obj;
uint64_t empty_pool_count = 0;
for (uint64_t i = 0; i < kSubPoolCount; ++i) {
if (sub_pool_mutexes_[pool_id].try_lock()) {
if (pool_[pool_id]->empty()) {
sub_pool_mutexes_[pool_id].unlock();
++empty_pool_count;
} else {
std::shared_ptr<T> obj = pool_[pool_id]->front();
pool_[pool_id]->pop_front();
sub_pool_mutexes_[pool_id].unlock();
prefer_pool_id_ = pool_id;
obj->source_pool_id_ = pool_id;
obj->Reset();
return obj;
}
}
std::shared_ptr<T> obj = pool_[pool_id]->front();
pool_[pool_id]->pop_front();
sub_pool_mutexes_[pool_id].unlock();
obj->source_pool_id_ = pool_id;
obj->Reset();

++pool_id;
pool_id %= kSubPoolCount;
}
if (empty_pool_count == kSubPoolCount) {
auto obj = this->constructor_();
obj->source_pool_id_ = prefer_pool_id_;
return obj;
}
++pool_id;
pool_id %= kSubPoolCount;
}

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.

critical

Potential Livelock / High CPU Usage Under Concurrency

In the current implementation, if any sub-pool is locked by another thread (which is highly likely under concurrent access), try_lock() will fail, and empty_pool_count will not reach kSubPoolCount. This causes the outer while (true) loop to busy-spin indefinitely or for a very long time, consuming 100% CPU.

Solution

Instead of a busy-spin while (true) loop, we can perform a single pass over all sub-pools. If we find an available object in any sub-pool we can lock, we reuse it. If we finish the pass and find no available objects (either because they are empty or because they are locked), we safely allocate a new object. This completely avoids busy-spinning while still satisfying the goal of reusing objects across sub-pools before allocating new ones.

        for (uint64_t i = 0; i < kSubPoolCount; ++i) {
            if (sub_pool_mutexes_[pool_id].try_lock()) {
                if (!pool_[pool_id]->empty()) {
                    std::shared_ptr<T> obj = pool_[pool_id]->front();
                    pool_[pool_id]->pop_front();
                    sub_pool_mutexes_[pool_id].unlock();
                    prefer_pool_id_ = pool_id;
                    obj->source_pool_id_ = pool_id;
                    obj->Reset();
                    return obj;
                }
                sub_pool_mutexes_[pool_id].unlock();
            }
            ++pool_id;
            pool_id %= kSubPoolCount;
        }
        auto obj = this->constructor_();
        obj->source_pool_id_ = prefer_pool_id_;
        return obj;

Copilot AI left a comment

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.

Pull request overview

This PR improves ResourceObjectPool’s allocation policy so that when a thread’s preferred sub-pool is empty, the pool will first attempt to reuse an idle object from other sub-pools before allocating a new VisitedList. This reduces peak scratch-memory growth in HGraph search workloads without changing the public API/ABI.

Changes:

  • Update ResourceObjectPool::TakeOne() to scan all sub-pools for an available object before constructing a new one.
  • Add a regression test that fails under the old “allocate immediately when preferred sub-pool is empty” behavior and passes with cross-sub-pool reuse.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/utils/resource_object_pool.h Implements cross-sub-pool reuse by scanning sub-pools before allocating a new resource object.
src/utils/visited_list_test.cpp Adds a regression test validating that no additional VisitedList allocations occur when other sub-pools still have idle objects.

Signed-off-by: jac0626 <jac0626@users.noreply.github.com>
Assisted-by: Codex:gpt-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/improvement Optimizations, UX polish, or minor improvements 性能优化、体验打磨或细节改良 module/index size/M version/1.1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[improve](hgraph): reuse idle visited lists across sub-pools before allocating

4 participants