-
Notifications
You must be signed in to change notification settings - Fork 170
Track cuMemAllocFromPoolAsync allocations and free untracked async pointers #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,9 +237,6 @@ int free_raw(CUdeviceptr dptr) { | |
| int remove_chunk_async( | ||
| allocated_list *a_list, CUdeviceptr dptr, CUstream hStream) { | ||
| size_t t_size; | ||
| if (a_list->length == 0) { | ||
| return -1; | ||
| } | ||
| allocated_list_entry *val; | ||
| for (val = a_list->head; val != NULL; val = val->next) { | ||
| if (val->entry->address == dptr) { | ||
|
|
@@ -253,7 +250,9 @@ int remove_chunk_async( | |
| return 0; | ||
| } | ||
| } | ||
| return -1; | ||
| /* Not tracked by libvgpu: free it for real instead of returning -1, which | ||
| * leaked and surfaced as an "unrecognized error code". */ | ||
| return CUDA_OVERRIDE_CALL(cuda_library_entry, cuMemFreeAsync, dptr, hStream); | ||
| } | ||
|
|
||
| int free_raw_async(CUdeviceptr dptr, CUstream hStream) { | ||
|
|
@@ -263,14 +262,40 @@ int free_raw_async(CUdeviceptr dptr, CUstream hStream) { | |
| return tmp; | ||
| } | ||
|
|
||
| /* 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; | ||
| size_t addr = 0; | ||
| CUresult res = CUDA_SUCCESS; | ||
| CUdevice dev; | ||
| cuCtxGetDevice(&dev); | ||
| if (oom_check(dev,size)) | ||
| return -1; | ||
| if (oom_check(dev, size)) | ||
| return CUDA_ERROR_OUT_OF_MEMORY; | ||
|
Comment on lines
+265
to
+298
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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/*.hRepository: 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.hRepository: Project-HAMi/HAMi-core Length of output: 19457 Track async memory per pool, not through a shared counter. 🧰 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 |
||
|
|
||
| allocated_list_entry *e; | ||
| INIT_ALLOCATED_LIST_ENTRY(e, addr, size, dev); | ||
|
|
@@ -280,31 +305,34 @@ int add_chunk_async(CUdeviceptr *address, size_t size, CUstream hStream) { | |
| return res; | ||
| } | ||
| *address = e->entry->address; | ||
| /* 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; | ||
|
Comment on lines
+308
to
313
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 🧰 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 |
||
| } | ||
| size_t poollimit; | ||
| res = CUDA_OVERRIDE_CALL(cuda_library_entry,cuMemPoolGetAttribute,pool,CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH,&poollimit); | ||
| return account_async_chunk(e, size, pool); | ||
| } | ||
|
|
||
| int add_chunk_from_pool_async(CUdeviceptr *address, size_t size, CUmemoryPool pool, CUstream hStream) { | ||
| size_t addr = 0; | ||
| CUresult res = CUDA_SUCCESS; | ||
| CUdevice dev; | ||
| cuCtxGetDevice(&dev); | ||
| if (oom_check(dev, size)) | ||
| return CUDA_ERROR_OUT_OF_MEMORY; | ||
|
|
||
| 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; | ||
|
Comment on lines
+326
to
331
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" srcRepository: 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 -nRepository: Project-HAMi/HAMi-core Length of output: 10365 Release the temporary entry on 🧰 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 AgentsSource: Linters/SAST tools |
||
| } | ||
| if (poollimit != 0) { | ||
| if (poollimit> device_allocasync->limit) { | ||
| allocsize = (poollimit-device_allocasync->limit < size)? poollimit-device_allocasync->limit : size; | ||
| cuCtxGetDevice(&dev); | ||
| add_gpu_device_memory_usage(getpid(), dev, allocsize, 2); | ||
| device_allocasync->limit=device_allocasync->limit+allocsize; | ||
| e->entry->length=allocsize; | ||
| }else{ | ||
| e->entry->length=0; | ||
| } | ||
| } | ||
| LIST_ADD(device_allocasync,e); | ||
| return 0; | ||
| *address = e->entry->address; | ||
| /* Account against the caller's pool, not the default. */ | ||
| return account_async_chunk(e, size, pool); | ||
| } | ||
|
|
||
| int allocate_async_raw(CUdeviceptr *dptr, size_t size, CUstream hStream) { | ||
|
|
@@ -314,3 +342,11 @@ int allocate_async_raw(CUdeviceptr *dptr, size_t size, CUstream hStream) { | |
| pthread_mutex_unlock(&mutex); | ||
| return tmp; | ||
| } | ||
|
|
||
| int allocate_from_pool_async_raw(CUdeviceptr *dptr, size_t size, CUmemoryPool pool, CUstream hStream) { | ||
| int tmp; | ||
| pthread_mutex_lock(&mutex); | ||
| tmp = add_chunk_from_pool_async(dptr, size, pool, hStream); | ||
| pthread_mutex_unlock(&mutex); | ||
| return tmp; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // Reproducer for Project-HAMi/HAMi-core issue #93 (the same untracked-free leak | ||
| // class reported in #67): cuMemAllocFromPoolAsync allocations are invisible to | ||
| // libvgpu, so the matching cuMemFreeAsync is rejected. | ||
| // | ||
| // Mechanism (src/cuda/memory.c + src/allocator/allocator.c on main): | ||
| // cuMemFreeAsync() -> free_raw_async() -> remove_chunk_async() walks the | ||
| // device_allocasync list for the pointer. cuMemAllocAsync() registers there | ||
| // (via add_chunk_async); cuMemAllocFromPoolAsync() is a bare pass-through and | ||
| // never does. For a pool-explicit allocation the pointer is not found, so | ||
| // remove_chunk_async() returns -1 WITHOUT calling the real cuMemFreeAsync: | ||
| // (a) -1 surfaces as a CUresult -> "unrecognized error code", and | ||
| // (b) the real free is skipped -> the device allocation leaks. | ||
| // | ||
| // Path [A] (default pool) is the control: it IS tracked, so it still frees | ||
| // cleanly under libvgpu. Path [B] (explicit pool) is the bug. On stock CUDA | ||
| // (no LD_PRELOAD) both paths return CUDA_SUCCESS. | ||
| // | ||
| // Build (standalone, no HAMi headers needed): | ||
| // gcc test_alloc_pool_async.c -o test_alloc_pool_async \ | ||
| // -I/usr/local/cuda/include -L/usr/local/cuda/lib64/stubs -lcuda | ||
| // Or drop into HAMi-core/test/ and `make build-in-docker` (auto-discovered). | ||
| // | ||
| // Run baseline (no libvgpu, expect PASS): | ||
| // ./test_alloc_pool_async | ||
| // Run under libvgpu (expect FAIL until fixed): | ||
| // LD_PRELOAD=/path/to/libvgpu.so CUDA_DEVICE_MEMORY_LIMIT=2g \ | ||
| // LIBCUDA_LOG_LEVEL=1 ./test_alloc_pool_async | ||
|
|
||
| #include <cuda.h> | ||
| #include <stdio.h> | ||
| #include <string.h> | ||
|
|
||
| #ifndef TEST_DEVICE_ID | ||
| #define TEST_DEVICE_ID 0 | ||
| #endif | ||
|
|
||
| #define ALLOC_BYTES (32ull * 1024 * 1024) /* 32 MiB */ | ||
|
|
||
| /* Abort-on-error for setup calls that must succeed for the test to be valid. */ | ||
| #define CHECK_DRV(call) \ | ||
| do { \ | ||
| CUresult _e = (call); \ | ||
| if (_e != CUDA_SUCCESS) { \ | ||
| const char *_n = NULL; \ | ||
| cuGetErrorName(_e, &_n); \ | ||
| fprintf(stderr, "FATAL %s:%d: %s -> %d (%s)\n", __FILE__, \ | ||
| __LINE__, #call, (int)_e, _n ? _n : "?"); \ | ||
| return 2; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Print a CUresult, flagging codes the driver itself does not recognise | ||
| (exactly what a caller sees as "unrecognized error code"). */ | ||
| static void describe(const char *label, CUresult res) { | ||
| const char *name = NULL; | ||
| CUresult q = cuGetErrorName(res, &name); | ||
| if (q != CUDA_SUCCESS || name == NULL) | ||
| printf(" %-34s -> %d <unrecognized error code>\n", label, (int)res); | ||
| else | ||
| printf(" %-34s -> %d (%s)\n", label, (int)res, name); | ||
| } | ||
|
|
||
| int main(void) { | ||
| CHECK_DRV(cuInit(0)); | ||
|
|
||
| CUdevice dev; | ||
| CHECK_DRV(cuDeviceGet(&dev, TEST_DEVICE_ID)); | ||
|
|
||
| CUcontext ctx; | ||
| #if CUDA_VERSION >= 13000 | ||
| CHECK_DRV(cuCtxCreate(&ctx, NULL, 0, dev)); | ||
| #else | ||
| CHECK_DRV(cuCtxCreate(&ctx, 0, dev)); | ||
| #endif | ||
|
|
||
| CUstream stream; | ||
| CHECK_DRV(cuStreamCreate(&stream, CU_STREAM_NON_BLOCKING)); | ||
|
|
||
| int failures = 0; | ||
|
|
||
| /* [A] Control: default-pool async path. libvgpu tracks this one. */ | ||
| printf("[A] cuMemAllocAsync + cuMemFreeAsync (default pool)\n"); | ||
| { | ||
| CUdeviceptr d = 0; | ||
| CHECK_DRV(cuMemAllocAsync(&d, ALLOC_BYTES, stream)); | ||
| CUresult f = cuMemFreeAsync(d, stream); | ||
| describe("cuMemFreeAsync", f); | ||
| CHECK_DRV(cuStreamSynchronize(stream)); | ||
| if (f != CUDA_SUCCESS) | ||
| failures++; | ||
| } | ||
|
|
||
| /* [B] Bug: explicit-pool async path. libvgpu never tracks this one. */ | ||
| printf("[B] cuMemAllocFromPoolAsync + cuMemFreeAsync (explicit pool)\n"); | ||
| { | ||
| CUmemPoolProps props; | ||
| memset(&props, 0, sizeof(props)); | ||
| props.allocType = CU_MEM_ALLOCATION_TYPE_PINNED; | ||
| props.location.type = CU_MEM_LOCATION_TYPE_DEVICE; | ||
| props.location.id = dev; | ||
|
|
||
| CUmemoryPool pool; | ||
| CHECK_DRV(cuMemPoolCreate(&pool, &props)); | ||
|
|
||
| CUdeviceptr d = 0; | ||
| CHECK_DRV(cuMemAllocFromPoolAsync(&d, ALLOC_BYTES, pool, stream)); | ||
| CUresult f = cuMemFreeAsync(d, stream); | ||
| describe("cuMemFreeAsync", f); | ||
| if (f != CUDA_SUCCESS) | ||
| failures++; | ||
|
|
||
| /* Best-effort teardown; a rejected free leaves the allocation live. */ | ||
| cuStreamSynchronize(stream); | ||
| cuMemPoolDestroy(pool); | ||
| } | ||
|
|
||
| printf("\nResult: %s\n", | ||
| failures ? "FAIL - pool-async free rejected (issue #93 reproduced)" | ||
| : "PASS - both async free paths accepted"); | ||
|
|
||
| cuStreamDestroy(stream); | ||
| cuCtxDestroy(ctx); | ||
| return failures ? 1 : 0; | ||
| } |
There was a problem hiding this comment.
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
cuMemPoolGetAttributefails.By the time
account_async_chunkis called, the real device allocation (cuMemAllocAsync/cuMemAllocFromPoolAsync) has already succeeded. IfcuMemPoolGetAttributefails, the function returns early withoutLIST_ADD-ingeintodevice_allocasyncand without freeingeor 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'snullPointerOutOfResources/leak signals around this region.🐛 Proposed fix — release what was already allocated on this failure path
Callers need the extra
hStreamargument:(applies to both call sites in
add_chunk_asyncandadd_chunk_from_pool_async)📝 Committable suggestion
🧰 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
Source: Linters/SAST tools