Skip to content

Track cuMemAllocFromPoolAsync allocations and free untracked async pointers#217

Open
imlach wants to merge 2 commits into
Project-HAMi:mainfrom
imlach:imlach/fix-cumemallocfrompoolasync-tracking
Open

Track cuMemAllocFromPoolAsync allocations and free untracked async pointers#217
imlach wants to merge 2 commits into
Project-HAMi:mainfrom
imlach:imlach/fix-cumemallocfrompoolasync-tracking

Conversation

@imlach

@imlach imlach commented Jul 7, 2026

Copy link
Copy Markdown

Fixes #93. Relates to #67.

The bug

libvgpu tracks stream-ordered allocations so it can enforce the memory limit and
free them correctly. It tracks the ones from cuMemAllocAsync, but
cuMemAllocFromPoolAsync was left as a bare pass-through, so a pool allocation
was never registered.

When that pointer is freed, remove_chunk_async walks the tracking list, fails
to find it, and returns -1 without calling the real cuMemFreeAsync. The caller
gets -1, which isn't a valid CUresult and shows up as "unrecognized error
code" (issue #93, e.g. a RAPIDS RMM coredump), and the memory leaks.

We ran into this getting NVIDIA Dynamo's snapshot feature working on a
HAMi-managed GPU, and reduced it to the minimal reproducer in the test.

The fix

cuMemAllocFromPoolAsync now registers its allocation the same way
cuMemAllocAsync does, so the matching free finds and releases it. It is
accounted against the caller's pool (the one it allocates from) rather than the
device default pool; a per-pool limit would be the alternative if you'd prefer
it. The shared registration and accounting logic is factored into one helper so
the two paths can't drift.

remove_chunk_async also stops returning -1 for pointers it doesn't recognise:
it now frees them for real and returns the driver's own result. Tracked pointers
are unaffected, since they return earlier in the match loop, so there is no
double-free.

Two smaller changes fell out of this: both async alloc paths now return
CUDA_ERROR_OUT_OF_MEMORY instead of -1 when over the limit (RMM keys its
cache-release-and-retry on that code), and the shared limit counter can no
longer underflow when a pool reports no reserved high-water.

Known limitations

  • Under a mix of default-pool and explicit-pool allocations, the single shared
    limit counter can under-account a pool allocation (it may register as zero), so
    enforcement is approximate for multi-pool workloads. Still better than before,
    when pool allocations were not tracked at all; per-pool accounting is the
    follow-up.
  • A pointer allocated synchronously (cuMemAlloc) but freed with cuMemFreeAsync
    is now freed correctly, but its synchronous tracking entry is not reclaimed, so
    it stays accounted. This is unusual, and a proper fix needs the synchronous
    free path, which this PR leaves alone.
  • Accounting uses the current device, so an explicit pool living on another
    device is charged to the wrong limit. Benign in single-GPU containers and when
    the caller selects the device first (as RMM does).

Testing

Adds test/test_alloc_pool_async.c, picked up automatically by the test CMake
glob. It exercises two paths on one stream: a default-pool control
(cuMemAllocAsync) and the pool-explicit case. On stock CUDA both free cleanly;
under libvgpu the pool-explicit free fails before this change and succeeds after.

Built this branch's libvgpu.so and ran it on an Ampere GPU under a HAMi v2.9.0
deployment: the pool-explicit free goes from -1 ("unrecognized error code") to
CUDA_SUCCESS, with the control path unchanged.

Summary by CodeRabbit

  • New Features
    • Added asynchronous allocation support from a specified device memory pool.
  • Bug Fixes
    • Improved async allocation/free bookkeeping to be memory-pool aware.
    • Async frees now correctly handle previously untracked pointers and better respect pool high-water limits.
  • Tests
    • Added a standalone reproducer verifying cuMemFreeAsync behavior for both default-pool and explicit pool async allocations.

@hami-robot hami-robot Bot requested a review from archlitchi July 7, 2026 15:08
@hami-robot hami-robot Bot requested a review from chaunceyjiang July 7, 2026 15:08
@hami-robot

hami-robot Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: imlach
Once this PR has been reviewed and has the lgtm label, please assign archlitchi for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@hami-robot

hami-robot Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Welcome @imlach! It looks like this is your first PR to Project-HAMi/HAMi-core 🎉

@hami-robot hami-robot Bot added the size/L label Jul 7, 2026
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 65a03b4d-d9c7-4795-a4a1-979594e10614

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1b0c3 and 18d3d16.

📒 Files selected for processing (2)
  • src/allocator/allocator.c
  • src/cuda/memory.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cuda/memory.c

📝 Walkthrough

Walkthrough

This PR updates async CUDA allocation and free handling for default-pool and explicit-pool allocations. Untracked async frees now fall through to cuMemFreeAsync, pool-aware accounting and allocation helpers were added, cuMemAllocFromPoolAsync now uses the internal allocator path, and a reproducer test covers both async allocation flows.

Changes

Pool-aware async allocation and free tracking

Layer / File(s) Summary
Fix untracked async chunk free behavior
src/allocator/allocator.c
remove_chunk_async removes the empty-list early return and now calls real cuMemFreeAsync for chunks not found in tracking.
Pool-aware accounting and allocation helpers
src/allocator/allocator.c, src/allocator/allocator.h
Adds account_async_chunk, updates add_chunk_async to fetch the device default pool, introduces add_chunk_from_pool_async for explicit pools, and adds the allocate_from_pool_async_raw allocator entry point.
Wire cuMemAllocFromPoolAsync override
src/cuda/memory.c
The cuMemAllocFromPoolAsync override now logs allocation size and calls allocate_from_pool_async_raw instead of the driver passthrough.
Async pool free reproducer test
test/test_alloc_pool_async.c
Adds a standalone test that runs default-pool and explicit-pool async alloc/free flows and reports pass or fail from CUresult outcomes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant allocator as allocate_from_pool_async_raw
  participant pool_add as add_chunk_from_pool_async
  participant accounting as account_async_chunk

  Caller->>allocator: allocate_from_pool_async_raw(dptr, size, pool, hStream)
  allocator->>pool_add: cuMemAllocFromPoolAsync(...)
  pool_add->>accounting: account_async_chunk(pool, entry)
  accounting-->>pool_add: adjusted tracked length
  pool_add-->>allocator: allocation result
  allocator-->>Caller: CUresult
Loading

Poem

A bunny saw a pool at night,
And set the async frees aright.
Untracked chunks now drift no more,
They hop through cuMemFreeAsync to shore. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: tracking cuMemAllocFromPoolAsync allocations and handling untracked async frees.
Linked Issues check ✅ Passed The changes add tracking for cuMemAllocFromPoolAsync and make cuMemFreeAsync succeed on those allocations, matching cuMemAllocAsync behavior for #93.
Out of Scope Changes check ✅ Passed The test and allocator/memory updates all support the async pool tracking fix; no unrelated code changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

…inters

cuMemAllocFromPoolAsync was a bare pass-through, so its pointer was never
registered; the matching cuMemFreeAsync then returned -1 without freeing
(issue Project-HAMi#93, e.g. RAPIDS RMM) - an invalid CUresult ("unrecognized error
code") plus a leak.

It now registers its allocation like cuMemAllocAsync, accounting against the
caller's pool, and remove_chunk_async frees any pointer it doesn't recognise
for real instead of returning -1. Both async alloc paths also return
CUDA_ERROR_OUT_OF_MEMORY rather than -1 on the limit. Adds
test/test_alloc_pool_async.c.

Fixes Project-HAMi#93
Relates to Project-HAMi#67

Signed-off-by: Ross Imlach <ross@imla.ch>
@imlach imlach force-pushed the imlach/fix-cumemallocfrompoolasync-tracking branch from 4f92197 to 9e1b0c3 Compare July 7, 2026 15:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/allocator/allocator.c (1)

290-334: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Same allocate-then-leak-on-failure pattern in the new pool-aware paths.

Both add_chunk_async (new cuDeviceGetMemPool failure branch, lines 306-312) and add_chunk_from_pool_async (cuMemAllocFromPoolAsync failure branch, lines 327-330) write *address and/or leak e before all failure paths are handled:

  • In add_chunk_async, *address = e->entry->address; (line 305) runs before cuDeviceGetMemPool. If that call fails, the function returns an error but the real device allocation from the preceding cuMemAllocAsync call is already live and now untracked/unfreeable, and e is leaked.
  • In add_chunk_from_pool_async, if cuMemAllocFromPoolAsync fails, e is leaked (matches cppcheck's memory leak flag at line 325) — no device leak here since the allocation itself failed, but the host-side allocated_list_entry is never freed.
🐛 Proposed fix
     res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemAllocFromPoolAsync,&e->entry->address,size,pool,hStream);
     if (res != CUDA_SUCCESS) {
         LOG_ERROR("cuMemAllocFromPoolAsync failed res=%d",res);
+        free(e);
         return res;
     }
     res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuDeviceGetMemPool,&pool,dev);
     if (res != CUDA_SUCCESS) {
         LOG_ERROR("cuDeviceGetMemPool failed res=%d",res);
+        CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemFreeAsync,e->entry->address,hStream);
+        free(e);
         return res;
     }

Same root cause as the account_async_chunk leak flagged above.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/allocator/allocator.c` around lines 290 - 334, The new async allocation
paths still leak on failure: `add_chunk_async` assigns `*address` before
`cuDeviceGetMemPool`, so if that call fails the `cuMemAllocAsync` allocation and
`allocated_list_entry` are left untracked; `add_chunk_from_pool_async` also
leaks its `allocated_list_entry` when `cuMemAllocFromPoolAsync` fails. Fix both
functions by deferring `*address` until all setup succeeds, and by cleaning up
the allocated entry and any successful device allocation on every error path in
`add_chunk_async` and `add_chunk_from_pool_async`.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/allocator/allocator.c`:
- Around line 265-288: The early return in account_async_chunk leaves both the
tracking entry e and the already-created async device allocation unhandled when
cuMemPoolGetAttribute fails. Update account_async_chunk and its callers
(add_chunk_async and add_chunk_from_pool_async) so the failure path frees or
rolls back the allocated device memory, releases the host tracking entry, and
does not skip cleanup before returning the CUDA error.
- Around line 265-296: The async accounting in account_async_chunk() is using
the shared device_allocasync->limit for CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH, which
mixes different pools together. Update the accounting to track high-water usage
per CUmemoryPool (using the pool passed into account_async_chunk and the chunk’s
pool association) instead of a global counter, and make the corresponding
free/unaccount path subtract against the same per-pool state so allocations from
one pool do not suppress or erase accounting for another.

---

Outside diff comments:
In `@src/allocator/allocator.c`:
- Around line 290-334: The new async allocation paths still leak on failure:
`add_chunk_async` assigns `*address` before `cuDeviceGetMemPool`, so if that
call fails the `cuMemAllocAsync` allocation and `allocated_list_entry` are left
untracked; `add_chunk_from_pool_async` also leaks its `allocated_list_entry`
when `cuMemAllocFromPoolAsync` fails. Fix both functions by deferring `*address`
until all setup succeeds, and by cleaning up the allocated entry and any
successful device allocation on every error path in `add_chunk_async` and
`add_chunk_from_pool_async`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c263669-e3a4-4aa2-83a2-7af139c2a743

📥 Commits

Reviewing files that changed from the base of the PR and between 8f3a89c and 4f92197.

📒 Files selected for processing (4)
  • src/allocator/allocator.c
  • src/allocator/allocator.h
  • src/cuda/memory.c
  • test/test_alloc_pool_async.c

Comment thread src/allocator/allocator.c
Comment on lines +265 to +288
/* Track an allocated async chunk and account it against its pool's high-water
* limit. Call with mutex held. */
static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool) {
size_t poollimit;
CUresult res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
return res;
}
if (poollimit != 0) {
if (poollimit> device_allocasync->limit) {
size_t allocsize = (poollimit-device_allocasync->limit < size)? poollimit-device_allocasync->limit : size;
add_gpu_device_memory_usage(getpid(), e->entry->dev, allocsize, 2);
device_allocasync->limit=device_allocasync->limit+allocsize;
e->entry->length=allocsize;
}else{
e->entry->length=0;
}
}else{
e->entry->length=0;
}
LIST_ADD(device_allocasync,e);
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Leaks the driver allocation and the tracking entry when cuMemPoolGetAttribute fails.

By the time account_async_chunk is called, the real device allocation (cuMemAllocAsync/cuMemAllocFromPoolAsync) has already succeeded. If cuMemPoolGetAttribute fails, the function returns early without LIST_ADD-ing e into device_allocasync and without freeing e or the underlying device pointer. The result: e (host memory) leaks, and the already-allocated device memory becomes permanently untracked and unfreeable — the exact class of leak this PR set out to fix, just relocated to a rarer failure path. This matches cppcheck's nullPointerOutOfResources/leak signals around this region.

🐛 Proposed fix — release what was already allocated on this failure path
-static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool) {
+static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool, CUstream hStream) {
     size_t poollimit;
     CUresult res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit);
     if (res != CUDA_SUCCESS) {
         LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
+        /* The real allocation already succeeded; don't strand it. */
+        CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemFreeAsync,e->entry->address,hStream);
+        free(e);
         return res;
     }

Callers need the extra hStream argument:

-    return account_async_chunk(e, size, pool);
+    return account_async_chunk(e, size, pool, hStream);

(applies to both call sites in add_chunk_async and add_chunk_from_pool_async)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Track an allocated async chunk and account it against its pool's high-water
* limit. Call with mutex held. */
static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool) {
size_t poollimit;
CUresult res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
return res;
}
if (poollimit != 0) {
if (poollimit> device_allocasync->limit) {
size_t allocsize = (poollimit-device_allocasync->limit < size)? poollimit-device_allocasync->limit : size;
add_gpu_device_memory_usage(getpid(), e->entry->dev, allocsize, 2);
device_allocasync->limit=device_allocasync->limit+allocsize;
e->entry->length=allocsize;
}else{
e->entry->length=0;
}
}else{
e->entry->length=0;
}
LIST_ADD(device_allocasync,e);
return 0;
}
/* Track an allocated async chunk and account it against its pool's high-water
* limit. Call with mutex held. */
static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool, CUstream hStream) {
size_t poollimit;
CUresult res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
/* The real allocation already succeeded; don't strand it. */
CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemFreeAsync,e->entry->address,hStream);
free(e);
return res;
}
if (poollimit != 0) {
if (poollimit> device_allocasync->limit) {
size_t allocsize = (poollimit-device_allocasync->limit < size)? poollimit-device_allocasync->limit : size;
add_gpu_device_memory_usage(getpid(), e->entry->dev, allocsize, 2);
device_allocasync->limit=device_allocasync->limit+allocsize;
e->entry->length=allocsize;
}else{
e->entry->length=0;
}
}else{
e->entry->length=0;
}
LIST_ADD(device_allocasync,e);
return 0;
}
🧰 Tools
🪛 Cppcheck (2.21.0)

[warning] 269-269: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)


[warning] 271-271: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/allocator/allocator.c` around lines 265 - 288, The early return in
account_async_chunk leaves both the tracking entry e and the already-created
async device allocation unhandled when cuMemPoolGetAttribute fails. Update
account_async_chunk and its callers (add_chunk_async and
add_chunk_from_pool_async) so the failure path frees or rolls back the allocated
device memory, releases the host tracking entry, and does not skip cleanup
before returning the CUDA error.

Source: Linters/SAST tools

Comment thread src/allocator/allocator.c
Comment on lines +265 to +296
/* Track an allocated async chunk and account it against its pool's high-water
* limit. Call with mutex held. */
static int account_async_chunk(allocated_list_entry *e, size_t size, CUmemoryPool pool) {
size_t poollimit;
CUresult res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
return res;
}
if (poollimit != 0) {
if (poollimit> device_allocasync->limit) {
size_t allocsize = (poollimit-device_allocasync->limit < size)? poollimit-device_allocasync->limit : size;
add_gpu_device_memory_usage(getpid(), e->entry->dev, allocsize, 2);
device_allocasync->limit=device_allocasync->limit+allocsize;
e->entry->length=allocsize;
}else{
e->entry->length=0;
}
}else{
e->entry->length=0;
}
LIST_ADD(device_allocasync,e);
return 0;
}

int add_chunk_async(CUdeviceptr *address, size_t size, CUstream hStream) {
size_t addr=0;
size_t allocsize;
CUresult res = CUDA_SUCCESS;
CUdevice dev;
cuCtxGetDevice(&dev);
if (oom_check(dev,size))
return -1;
return CUDA_ERROR_OUT_OF_MEMORY;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect other consumers of device_allocasync->limit / oom_check to confirm
# whether cross-pool undercounting can actually bypass the configured memory limit.
rg -n 'device_allocasync->limit|oom_check\(' src/allocator/allocator.c src/allocator/*.h

Repository: Project-HAMi/HAMi-core

Length of output: 1010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== allocator.c outline =="
ast-grep outline src/allocator/allocator.c --view expanded || true

echo
echo "== relevant sections =="
sed -n '1,220p' src/allocator/allocator.c
echo
sed -n '220,420p' src/allocator/allocator.c

echo
echo "== allocator.h relevant definitions =="
sed -n '1,260p' src/allocator/allocator.h

Repository: Project-HAMi/HAMi-core

Length of output: 19457


Track async memory per pool, not through a shared counter. CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH is per-pool, but account_async_chunk() compares it against the global device_allocasync->limit, so one pool can suppress accounting in another and later frees subtract 0. Multi-pool async allocations then disappear from the process GPU-memory limit.

🧰 Tools
🪛 Cppcheck (2.21.0)

[warning] 269-269: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)


[warning] 271-271: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/allocator/allocator.c` around lines 265 - 296, The async accounting in
account_async_chunk() is using the shared device_allocasync->limit for
CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH, which mixes different pools together. Update
the accounting to track high-water usage per CUmemoryPool (using the pool passed
into account_async_chunk and the chunk’s pool association) instead of a global
counter, and make the corresponding free/unaccount path subtract against the
same per-pool state so allocations from one pool do not suppress or erase
accounting for another.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/allocator/allocator.c`:
- Around line 324-329: The cuMemAllocFromPoolAsync failure path in allocator
logic leaks the temporary allocated_list_entry created by
INIT_ALLOCATED_LIST_ENTRY, along with its nested entry and allocHandle. Update
the failure branch in the allocation routine that calls CUDA_OVERRIDE_CALL on
cuMemAllocFromPoolAsync to use the same cleanup pattern as LIST_REMOVE before
returning, so e, e->entry, and e->entry->allocHandle are released on error.
- Around line 306-311: The default-pool lookup failure path in allocator.c leaks
the already-created driver allocation and tracking entry. In the cuMemAllocAsync
flow around cuDeviceGetMemPool, add cleanup before returning when res !=
CUDA_SUCCESS: release the allocation created earlier and undo the initialized
bookkeeping so the allocator state stays consistent. Use the existing
cuMemAllocAsync path and the surrounding allocation/tracking symbols in
allocator.c to locate the rollback point.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab946327-95e5-41ac-b4d1-a403e592584a

📥 Commits

Reviewing files that changed from the base of the PR and between 4f92197 and 9e1b0c3.

📒 Files selected for processing (4)
  • src/allocator/allocator.c
  • src/allocator/allocator.h
  • src/cuda/memory.c
  • test/test_alloc_pool_async.c
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/test_alloc_pool_async.c
  • src/cuda/memory.c
  • src/allocator/allocator.h

Comment thread src/allocator/allocator.c
Comment on lines +306 to 311
/* cuMemAllocAsync draws from the device's default pool. */
CUmemoryPool pool;
res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuDeviceGetMemPool,&pool,dev);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuDeviceGetMemPool failed res=%d",res);
return res;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Roll back the allocation on default-pool lookup failure.
Line 300 already created the driver allocation, so returning here leaks the device memory and the initialized tracking entry.

🧰 Tools
🪛 Cppcheck (2.21.0)

[warning] 308-308: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)


[warning] 310-310: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/allocator/allocator.c` around lines 306 - 311, The default-pool lookup
failure path in allocator.c leaks the already-created driver allocation and
tracking entry. In the cuMemAllocAsync flow around cuDeviceGetMemPool, add
cleanup before returning when res != CUDA_SUCCESS: release the allocation
created earlier and undo the initialized bookkeeping so the allocator state
stays consistent. Use the existing cuMemAllocAsync path and the surrounding
allocation/tracking symbols in allocator.c to locate the rollback point.

Comment thread src/allocator/allocator.c
Comment on lines +324 to 329
allocated_list_entry *e;
INIT_ALLOCATED_LIST_ENTRY(e, addr, size, dev);
res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemAllocFromPoolAsync,&e->entry->address,size,pool,hStream);
if (res != CUDA_SUCCESS) {
LOG_ERROR("cuMemPoolGetAttribute failed res=%d",res);
LOG_ERROR("cuMemAllocFromPoolAsync failed res=%d",res);
return res;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/allocator/allocator.c around lines 280-360 ---'
sed -n '280,360p' src/allocator/allocator.c | cat -n

echo '--- search for INIT_ALLOCATED_LIST_ENTRY ---'
rg -n "INIT_ALLOCATED_LIST_ENTRY|allocated_list_entry|allocated_list" src

echo '--- search for destructor/free/cleanup related to allocated_list_entry ---'
rg -n "free.*allocated_list_entry|destroy.*allocated_list_entry|cleanup.*allocated_list_entry|INIT_ALLOCATED_LIST_ENTRY|TRACKING_ENTRY|allocated_list_entry.*free" src

Repository: Project-HAMi/HAMi-core

Length of output: 6294


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/allocator/allocator.h around INIT_ALLOCATED_LIST_ENTRY ---'
sed -n '80,150p' src/allocator/allocator.h | cat -n

echo '--- src/allocator/allocator.c remove/cleanup paths ---'
sed -n '150,280p' src/allocator/allocator.c | cat -n

Repository: Project-HAMi/HAMi-core

Length of output: 10365


Release the temporary entry on cuMemAllocFromPoolAsync failure. INIT_ALLOCATED_LIST_ENTRY allocates e before this call, so the early return leaks e, e->entry, and e->entry->allocHandle; free them with the same cleanup pattern used by LIST_REMOVE.

🧰 Tools
🪛 Cppcheck (2.21.0)

[error] 325-325: Memory leak

(memleak)


[warning] 325-325: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)


[warning] 326-326: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)


[warning] 328-328: If resource allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfResources)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/allocator/allocator.c` around lines 324 - 329, The
cuMemAllocFromPoolAsync failure path in allocator logic leaks the temporary
allocated_list_entry created by INIT_ALLOCATED_LIST_ENTRY, along with its nested
entry and allocHandle. Update the failure branch in the allocation routine that
calls CUDA_OVERRIDE_CALL on cuMemAllocFromPoolAsync to use the same cleanup
pattern as LIST_REMOVE before returning, so e, e->entry, and
e->entry->allocHandle are released on error.

Source: Linters/SAST tools

@imlach

imlach commented Jul 7, 2026

Copy link
Copy Markdown
Author

Per-pool accounting. This is already called out under "Known
limitations": the shared device_allocasync->limit is deliberately reused (as
the existing default-pool path does), so a pool allocation can under-account
under mixed default/explicit-pool workloads. Per-pool tracking is the follow-up.
This PR's scope is just making pool allocations tracked and freeable at all,
which they weren't before.

Leak-on-failure. Real, but a pre-existing, file-wide pattern rather than
something new here. Entries are only ever released via LIST_REMOVE, which frees
all three allocations (entry->allocHandle, entry, then the entry itself).
Every add_* path that returns before LIST_ADD leaks the same way today:
add_chunk_async on main already does, on the same cuMemAllocAsync /
cuDeviceGetMemPool / cuMemPoolGetAttribute failure branches, and the new
add_chunk_from_pool_async mirrors that convention.

Two notes on the suggested patch:

  • free(e) alone would still leak e->entry and e->entry->allocHandle, since
    INIT_ALLOCATED_LIST_ENTRY mallocs all three; the correct cleanup is the same
    triple-free LIST_REMOVE performs.
  • The cuDeviceGetMemPool device-leak branch in add_chunk_async is unchanged
    from main.

I'd rather fix these error paths consistently across the async allocator (the
correct triple-free on every early return, and deferring *address until setup
succeeds) in a focused follow-up than piecemeal in one function here. Happy to do
that; let me know if you'd prefer it land in this PR.

Signed-off-by: Ross Imlach <ross@imla.ch>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement similar logic to interface cuMemAllocAsync for interface cuMemAllocFromPoolAsync

1 participant