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
17 changes: 11 additions & 6 deletions c/src/core/c_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,19 +347,24 @@ extern "C" cuvsError_t cuvsRMMMemoryResourceReset()
});
}

thread_local std::unique_ptr<rmm::mr::pinned_host_memory_resource> pinned_mr;
namespace {
// One process-wide resource: pinned_host_memory_resource is stateless, so a buffer allocated on
// one thread can be freed on another (a thread_local resource left it null on other threads).
rmm::mr::pinned_host_memory_resource& pinned_mr()
{
static rmm::mr::pinned_host_memory_resource mr;
return mr;
}
} // namespace

extern "C" cuvsError_t cuvsRMMHostAlloc(void** ptr, size_t bytes)
{
return cuvs::core::translate_exceptions([=] {
if (pinned_mr == nullptr) { pinned_mr = std::make_unique<rmm::mr::pinned_host_memory_resource>(); }
*ptr = pinned_mr->allocate_sync(bytes);
});
return cuvs::core::translate_exceptions([=] { *ptr = pinned_mr().allocate_sync(bytes); });
}

extern "C" cuvsError_t cuvsRMMHostFree(void* ptr, size_t bytes)
{
return cuvs::core::translate_exceptions([=] { pinned_mr->deallocate_sync(ptr, bytes); });
return cuvs::core::translate_exceptions([=] { pinned_mr().deallocate_sync(ptr, bytes); });
}

thread_local std::string last_error_text = "";
Expand Down
1 change: 1 addition & 0 deletions c/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ ConfigureTest(NAME cuvs_c_headers PATH core/headers.c)
ConfigureTest(NAME cuvs_c_test PATH core/c_api.c)
ConfigureTest(NAME cuvs_c_resource_pool_test PATH core/resource_pool.cpp)
ConfigureTest(NAME cuvs_c_resource_stream_pool_test PATH core/resource_stream_pool.cpp)
ConfigureTest(NAME cuvs_c_host_alloc_test PATH core/host_alloc.cpp)
target_link_libraries(cuvs_c_test PRIVATE CUDA::cudart_static)
target_link_libraries(cuvs_c_resource_pool_test PRIVATE CUDA::cudart_static)
target_link_libraries(cuvs_c_resource_stream_pool_test PRIVATE CUDA::cudart_static)
Expand Down
66 changes: 66 additions & 0 deletions c/tests/core/host_alloc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

#include <cuvs/core/c_api.h>

#include <gtest/gtest.h>

#include <atomic>
#include <cstddef>
#include <thread>
#include <vector>

// Pinned host buffers may be freed on a different thread than the one that allocated them, e.g.
// when a binding moves the owning object across threads.
TEST(RMMHostAlloc, FreeOnAnotherThread)
{
constexpr std::size_t kBytes = 1024;
void* ptr = nullptr;
cuvsError_t alloc_status = CUVS_ERROR;
std::thread([&] { alloc_status = cuvsRMMHostAlloc(&ptr, kBytes); }).join();
ASSERT_EQ(alloc_status, CUVS_SUCCESS);
ASSERT_NE(ptr, nullptr);

cuvsError_t free_status = CUVS_ERROR;
std::thread([&] { free_status = cuvsRMMHostFree(ptr, kBytes); }).join();
EXPECT_EQ(free_status, CUVS_SUCCESS);
}

TEST(RMMHostAlloc, ConcurrentAllocAndFree)
{
constexpr int kThreads = 8;
constexpr int kIters = 50;
constexpr std::size_t kBytes = 4096;
std::vector<void*> ptrs(kThreads * kIters, nullptr);
std::atomic<int> failures{0};

std::vector<std::thread> threads;
for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&, t] {
for (int i = 0; i < kIters; ++i) {
if (cuvsRMMHostAlloc(&ptrs[t * kIters + i], kBytes) != CUVS_SUCCESS) { failures++; }
}
});
}
for (auto& th : threads) {
th.join();
}
threads.clear();
ASSERT_EQ(failures.load(), 0);

// Free every buffer from a different thread than the one that allocated it.
for (int t = 0; t < kThreads; ++t) {
threads.emplace_back([&, t] {
int const owner = (t + 1) % kThreads;
for (int i = 0; i < kIters; ++i) {
if (cuvsRMMHostFree(ptrs[owner * kIters + i], kBytes) != CUVS_SUCCESS) { failures++; }
}
});
}
for (auto& th : threads) {
th.join();
}
EXPECT_EQ(failures.load(), 0);
}
Loading