From a699662bb02446b97d36c28f085cf3aba7daddf8 Mon Sep 17 00:00:00 2001 From: Rafal Rudnicki Date: Wed, 18 Jun 2025 08:25:25 +0000 Subject: [PATCH] x --- include/umf/memory_pool.h | 9 ++++ include/umf/memory_pool_ops.h | 10 ++++ src/libumf.def | 2 + src/libumf.map | 4 ++ src/memory_pool.c | 26 ++++++++-- src/pool/pool_disjoint.c | 42 +++++++++++++++ src/pool/pool_jemalloc.c | 18 +++++++ src/pool/pool_proxy.c | 12 ++++- src/pool/pool_scalable.c | 15 +++++- src/provider/provider_tracking.c | 1 + src/utils/utils_posix_concurrency.c | 5 ++ test/common/pool.hpp | 32 +++++++----- test/common/pool_trace.c | 9 ++++ test/common/provider.hpp | 41 +++++---------- test/memoryPoolAPI.cpp | 13 +++++ test/poolFixtures.hpp | 30 +++++++++++ test/pools/disjoint_pool.cpp | 79 +++++++++++++++++++++++++++++ test/utils/cpp_helpers.hpp | 12 +++-- 18 files changed, 308 insertions(+), 52 deletions(-) diff --git a/include/umf/memory_pool.h b/include/umf/memory_pool.h index c405e6f61..3866c4ab8 100644 --- a/include/umf/memory_pool.h +++ b/include/umf/memory_pool.h @@ -196,6 +196,15 @@ umf_result_t umfPoolSetTag(umf_memory_pool_handle_t hPool, void *tag, /// @return UMF_RESULT_SUCCESS on success. umf_result_t umfPoolGetTag(umf_memory_pool_handle_t hPool, void **tag); +/// +/// @brief Trims memory pool to keep at least \p minBytesToKeep bytes of memory +/// if possible. +/// @param hPool specified memory pool +/// @param minBytesToKeep minimum number of bytes to keep in the pool +/// @return UMF_RESULT_SUCCESS on success or appropriate error code on failure. +umf_result_t umfPoolTrimMemory(umf_memory_pool_handle_t hPool, + size_t minBytesToKeep); + #ifdef __cplusplus } #endif diff --git a/include/umf/memory_pool_ops.h b/include/umf/memory_pool_ops.h index e6d6736fe..ff5bde879 100644 --- a/include/umf/memory_pool_ops.h +++ b/include/umf/memory_pool_ops.h @@ -168,6 +168,16 @@ typedef struct umf_memory_pool_ops_t { const char *name, void *arg, size_t size, umf_ctl_query_type_t queryType, va_list args); + /// + /// @brief Trims memory of the pool, removing resources that are not needed + /// to keep the pool operational. + /// @param pool pointer to the memory pool + /// @param minBytesToKeep minimum number of bytes to keep in the pool if + /// possible. + /// @return UMF_RESULT_SUCCESS on success or appropriate error code on + /// failure. + /// + umf_result_t (*ext_trim_memory)(void *pool, size_t minBytesToKeep); } umf_memory_pool_ops_t; #ifdef __cplusplus diff --git a/src/libumf.def b/src/libumf.def index 0159ddbe2..665a52f4b 100644 --- a/src/libumf.def +++ b/src/libumf.def @@ -144,3 +144,5 @@ EXPORTS umfJemallocPoolParamsDestroy umfJemallocPoolParamsSetNumArenas umfPoolGetName +; Added in UMF_1.1 + umfPoolTrimMemory diff --git a/src/libumf.map b/src/libumf.map index 348675ff0..88fd6047a 100644 --- a/src/libumf.map +++ b/src/libumf.map @@ -141,3 +141,7 @@ UMF_1.0 { local: *; }; + +UMF_1.1 { + umfPoolTrimMemory; +} UMF_1.0; diff --git a/src/memory_pool.c b/src/memory_pool.c index 004f42d9e..c76f6d386 100644 --- a/src/memory_pool.c +++ b/src/memory_pool.c @@ -167,6 +167,13 @@ umfDefaultCtlPoolHandle(void *hPool, umf_ctl_query_source_t operationType, return UMF_RESULT_ERROR_NOT_SUPPORTED; } +static umf_result_t umfDefaultTrimMemory(void *provider, + size_t minBytesToKeep) { + (void)provider; + (void)minBytesToKeep; + return UMF_RESULT_ERROR_NOT_SUPPORTED; +} + // logical sum (OR) of all umf_pool_create_flags_t flags static const umf_pool_create_flags_t UMF_POOL_CREATE_FLAG_ALL = UMF_POOL_CREATE_FLAG_OWN_PROVIDER | UMF_POOL_CREATE_FLAG_DISABLE_TRACKING; @@ -189,9 +196,9 @@ static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, const void *params, umf_pool_create_flags_t flags, umf_memory_pool_handle_t *hPool) { - if (!ops || !provider || !hPool) { - return UMF_RESULT_ERROR_INVALID_ARGUMENT; - } + UMF_CHECK((ops != NULL), UMF_RESULT_ERROR_INVALID_ARGUMENT); + UMF_CHECK((provider != NULL), UMF_RESULT_ERROR_INVALID_ARGUMENT); + UMF_CHECK((hPool != NULL), UMF_RESULT_ERROR_INVALID_ARGUMENT); // validate flags if (flags & ~UMF_POOL_CREATE_FLAG_ALL) { @@ -234,6 +241,10 @@ static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, pool->ops.ext_ctl = umfDefaultCtlPoolHandle; } + if (NULL == pool->ops.ext_trim_memory) { + pool->ops.ext_trim_memory = umfDefaultTrimMemory; + } + if (NULL == utils_mutex_init(&pool->lock)) { LOG_ERR("Failed to initialize mutex for pool"); ret = UMF_RESULT_ERROR_UNKNOWN; @@ -277,6 +288,8 @@ static umf_result_t umfPoolCreateInternal(const umf_memory_pool_ops_t *ops, } umf_result_t umfPoolDestroy(umf_memory_pool_handle_t hPool) { + UMF_CHECK((hPool != NULL), UMF_RESULT_ERROR_INVALID_ARGUMENT); + if (umf_ba_global_is_destroyed()) { return UMF_RESULT_ERROR_UNKNOWN; } @@ -454,3 +467,10 @@ umf_result_t umfPoolGetTag(umf_memory_pool_handle_t hPool, void **tag) { utils_mutex_unlock(&hPool->lock); return UMF_RESULT_SUCCESS; } + +umf_result_t umfPoolTrimMemory(umf_memory_pool_handle_t hPool, + size_t minBytesToKeep) { + UMF_CHECK((hPool != NULL), UMF_RESULT_ERROR_INVALID_ARGUMENT); + + return hPool->ops.ext_trim_memory(hPool->pool_priv, minBytesToKeep); +} diff --git a/src/pool/pool_disjoint.c b/src/pool/pool_disjoint.c index e5339376e..e309f97c6 100644 --- a/src/pool/pool_disjoint.c +++ b/src/pool/pool_disjoint.c @@ -1133,6 +1133,47 @@ static umf_result_t disjoint_pool_get_name(void *pool, const char **name) { return UMF_RESULT_SUCCESS; } +umf_result_t disjoint_pool_trim_memory(void *pool, size_t minBytesToKeep) { + disjoint_pool_t *hPool = (disjoint_pool_t *)pool; + if (hPool == NULL) { + return UMF_RESULT_ERROR_INVALID_ARGUMENT; + } + + for (size_t i = 0; i < hPool->buckets_num; i++) { + bucket_t *bucket = hPool->buckets[i]; + utils_mutex_lock(&bucket->bucket_lock); + + int skip = (int)minBytesToKeep; + + // remove empty slabs from the pool + slab_list_item_t *it = NULL, *tmp = NULL; + LL_FOREACH_SAFE(bucket->available_slabs, it, tmp) { + slab_t *slab = it->val; + if (slab->num_chunks_allocated == 0) { + // skip first minBytesToKeep bytes from each bucket + if (skip > 0) { + skip -= (int)slab->slab_size; + continue; + } + + // remove slab + pool_unregister_slab(hPool, slab); + DL_DELETE(bucket->available_slabs, it); + assert(bucket->available_slabs_num > 0); + bucket->available_slabs_num--; + destroy_slab(slab); + + // update stats + bucket_update_stats(bucket, 0, -1); + } + } + + utils_mutex_unlock(&bucket->bucket_lock); + } + + return UMF_RESULT_SUCCESS; +} + static umf_memory_pool_ops_t UMF_DISJOINT_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = disjoint_pool_initialize, @@ -1146,6 +1187,7 @@ static umf_memory_pool_ops_t UMF_DISJOINT_POOL_OPS = { .get_last_allocation_error = disjoint_pool_get_last_allocation_error, .get_name = disjoint_pool_get_name, .ext_ctl = disjoint_pool_ctl, + .ext_trim_memory = disjoint_pool_trim_memory, }; const umf_memory_pool_ops_t *umfDisjointPoolOps(void) { diff --git a/src/pool/pool_jemalloc.c b/src/pool/pool_jemalloc.c index abbf50d2b..6ae8a025e 100644 --- a/src/pool/pool_jemalloc.c +++ b/src/pool/pool_jemalloc.c @@ -563,6 +563,23 @@ static umf_result_t op_get_name(void *pool, const char **name) { return UMF_RESULT_SUCCESS; } +static umf_result_t op_trim_memory(void *pool, size_t minBytesToKeep) { + (void)minBytesToKeep; // unused - TODO? + + jemalloc_memory_pool_t *je_pool = (jemalloc_memory_pool_t *)pool; + for (size_t i = 0; i < je_pool->n_arenas; i++) { + char cmd[64]; + unsigned arena = je_pool->arena_index[i]; + snprintf(cmd, sizeof(cmd), "arena.%u.purge", arena); + if (je_mallctl(cmd, NULL, NULL, NULL, 0)) { + LOG_ERR("Could not purge jemalloc arena %u", arena); + return UMF_RESULT_ERROR_UNKNOWN; + } + } + + return UMF_RESULT_SUCCESS; +} + static umf_memory_pool_ops_t UMF_JEMALLOC_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = op_initialize, @@ -575,6 +592,7 @@ static umf_memory_pool_ops_t UMF_JEMALLOC_POOL_OPS = { .free = op_free, .get_last_allocation_error = op_get_last_allocation_error, .get_name = op_get_name, + .ext_trim_memory = op_trim_memory, }; const umf_memory_pool_ops_t *umfJemallocPoolOps(void) { diff --git a/src/pool/pool_proxy.c b/src/pool/pool_proxy.c index c6bf74124..cecd178e8 100644 --- a/src/pool/pool_proxy.c +++ b/src/pool/pool_proxy.c @@ -136,6 +136,14 @@ static umf_result_t proxy_get_name(void *pool, const char **name) { return UMF_RESULT_SUCCESS; } +// TODO remove if na +static umf_result_t proxy_trim_memory(void *pool, size_t minBytesToKeep) { + (void)pool; + (void)minBytesToKeep; + + return UMF_RESULT_SUCCESS; +} + static umf_memory_pool_ops_t UMF_PROXY_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = proxy_pool_initialize, @@ -147,7 +155,9 @@ static umf_memory_pool_ops_t UMF_PROXY_POOL_OPS = { .malloc_usable_size = proxy_malloc_usable_size, .free = proxy_free, .get_last_allocation_error = proxy_get_last_allocation_error, - .get_name = proxy_get_name}; + .get_name = proxy_get_name, + .ext_trim_memory = proxy_trim_memory, +}; const umf_memory_pool_ops_t *umfProxyPoolOps(void) { return &UMF_PROXY_POOL_OPS; diff --git a/src/pool/pool_scalable.c b/src/pool/pool_scalable.c index 982a3408d..234c57d54 100644 --- a/src/pool/pool_scalable.c +++ b/src/pool/pool_scalable.c @@ -60,6 +60,7 @@ typedef struct tbb_callbacks_t { bool (*pool_destroy)(void *); void *(*pool_identify)(void *object); size_t (*pool_msize)(void *, void *); + int (*pool_allocation_command)(int, void *); #ifdef _WIN32 HMODULE lib_handle; #else @@ -422,12 +423,14 @@ static umf_result_t tbb_get_last_allocation_error(void *pool) { return TLS_last_allocation_error; } +static void initialize_pool_ctl(void) {} + static umf_result_t pool_ctl(void *hPool, umf_ctl_query_source_t operationType, const char *name, void *arg, size_t size, umf_ctl_query_type_t query_type, va_list args) { (void)operationType; // unused umf_memory_pool_handle_t pool_provider = (umf_memory_pool_handle_t)hPool; - utils_init_once(&ctl_initialized, NULL); + utils_init_once(&ctl_initialized, initialize_pool_ctl); return ctl_query(&pool_scallable_ctl_root, pool_provider->pool_priv, CTL_QUERY_PROGRAMMATIC, name, query_type, arg, size, args); } @@ -438,6 +441,15 @@ static umf_result_t scalable_get_name(void *pool, const char **name) { return UMF_RESULT_SUCCESS; } +// TODO remove if na +static umf_result_t scalable_trim_memory(void *pool, size_t minBytesToKeep) { + (void)pool; // unused + (void)minBytesToKeep; // unused + + //scalable_allocation_command? + return UMF_RESULT_SUCCESS; +} + static umf_memory_pool_ops_t UMF_SCALABLE_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = tbb_pool_initialize, @@ -451,6 +463,7 @@ static umf_memory_pool_ops_t UMF_SCALABLE_POOL_OPS = { .get_last_allocation_error = tbb_get_last_allocation_error, .ext_ctl = pool_ctl, .get_name = scalable_get_name, + .ext_trim_memory = scalable_trim_memory, }; const umf_memory_pool_ops_t *umfScalablePoolOps(void) { diff --git a/src/provider/provider_tracking.c b/src/provider/provider_tracking.c index 386eef0ba..40b5fadc3 100644 --- a/src/provider/provider_tracking.c +++ b/src/provider/provider_tracking.c @@ -472,6 +472,7 @@ umf_result_t umfMemoryTrackerGetAllocInfo(const void *ptr, umf_alloc_info_t *pAllocInfo) { assert(pAllocInfo); + // TODO unlikely if (ptr == NULL) { return UMF_RESULT_ERROR_INVALID_ARGUMENT; } diff --git a/src/utils/utils_posix_concurrency.c b/src/utils/utils_posix_concurrency.c index 44a317361..c6f273bed 100644 --- a/src/utils/utils_posix_concurrency.c +++ b/src/utils/utils_posix_concurrency.c @@ -38,6 +38,11 @@ int utils_mutex_unlock(utils_mutex_t *m) { } void utils_init_once(UTIL_ONCE_FLAG *flag, void (*oneCb)(void)) { + if (oneCb == NULL) { + LOG_FATAL("utils_init_once: callback is NULL"); + return; + } + pthread_once(flag, oneCb); } diff --git a/test/common/pool.hpp b/test/common/pool.hpp index 5cae85411..41582741a 100644 --- a/test/common/pool.hpp +++ b/test/common/pool.hpp @@ -108,25 +108,26 @@ typedef struct pool_base_t { umf_result_t initialize(umf_memory_provider_handle_t) noexcept { return UMF_RESULT_SUCCESS; }; - void *malloc([[maybe_unused]] size_t size) noexcept { return nullptr; } + void *malloc(size_t) noexcept { return nullptr; } void *calloc(size_t, size_t) noexcept { return nullptr; } void *realloc(void *, size_t) noexcept { return nullptr; } void *aligned_malloc(size_t, size_t) noexcept { return nullptr; } - umf_result_t malloc_usable_size(const void *, size_t *size) noexcept { - if (size) { - *size = 0; - } - return UMF_RESULT_SUCCESS; + umf_result_t malloc_usable_size(const void *, size_t *) noexcept { + return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t free(void *) noexcept { return UMF_RESULT_SUCCESS; } + umf_result_t free(void *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } umf_result_t get_last_allocation_error() noexcept { - return UMF_RESULT_SUCCESS; + return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t get_name(const char **name) noexcept { - if (name) { - *name = "pool_base"; - } - return UMF_RESULT_SUCCESS; + umf_result_t get_name(const char **) noexcept { + return UMF_RESULT_ERROR_UNKNOWN; + } + umf_result_t ext_ctl(umf_ctl_query_source_t, const char *, void *, size_t, + umf_ctl_query_type_t, va_list) noexcept { + return UMF_RESULT_ERROR_UNKNOWN; + } + umf_result_t ext_trim_memory(size_t) noexcept { + return UMF_RESULT_ERROR_UNKNOWN; } } pool_base_t; @@ -177,6 +178,11 @@ struct malloc_pool : public pool_base_t { } return UMF_RESULT_SUCCESS; } + + umf_result_t ext_trim_memory(size_t) noexcept { + // malloc_pool frees all memory immediately, so we have nothing to trim + return UMF_RESULT_SUCCESS; + } }; umf_memory_pool_ops_t MALLOC_POOL_OPS = diff --git a/test/common/pool_trace.c b/test/common/pool_trace.c index ce944479f..c05a16d32 100644 --- a/test/common/pool_trace.c +++ b/test/common/pool_trace.c @@ -99,6 +99,14 @@ static umf_result_t traceGetName(void *pool, const char **name) { return UMF_RESULT_SUCCESS; } +static umf_result_t traceTrimMemory(void *pool, size_t minBytesToKeep) { + trace_pool_t *trace_pool = (trace_pool_t *)pool; + + trace_pool->params.trace_handler(trace_pool->params.trace_context, + "trim_memory"); + return umfPoolTrimMemory(trace_pool->params.hUpstreamPool, minBytesToKeep); +} + umf_memory_pool_ops_t UMF_TRACE_POOL_OPS = { .version = UMF_POOL_OPS_VERSION_CURRENT, .initialize = traceInitialize, @@ -111,4 +119,5 @@ umf_memory_pool_ops_t UMF_TRACE_POOL_OPS = { .free = traceFree, .get_last_allocation_error = traceGetLastStatus, .get_name = traceGetName, + .ext_trim_memory = traceTrimMemory, }; diff --git a/test/common/provider.hpp b/test/common/provider.hpp index 6d42c59f2..45b0dae10 100644 --- a/test/common/provider.hpp +++ b/test/common/provider.hpp @@ -38,66 +38,49 @@ typedef struct provider_base_t { umf_result_t alloc(size_t, size_t, void **) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t free([[maybe_unused]] void *ptr, - [[maybe_unused]] size_t size) noexcept { + umf_result_t free(void *, size_t) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } umf_result_t get_last_native_error(const char **, int32_t *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t - get_recommended_page_size([[maybe_unused]] size_t size, - [[maybe_unused]] size_t *pageSize) noexcept { + umf_result_t get_recommended_page_size(size_t, size_t *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t get_min_page_size([[maybe_unused]] const void *ptr, - [[maybe_unused]] size_t *pageSize) noexcept { + umf_result_t get_min_page_size(const void *, size_t *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } umf_result_t get_name(const char **name) noexcept { *name = "base"; return UMF_RESULT_SUCCESS; } - umf_result_t ext_purge_lazy([[maybe_unused]] void *ptr, - [[maybe_unused]] size_t size) noexcept { + umf_result_t ext_purge_lazy(void *, size_t) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t ext_purge_force([[maybe_unused]] void *ptr, - [[maybe_unused]] size_t size) noexcept { + umf_result_t ext_purge_force(void *, size_t) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t ext_allocation_merge([[maybe_unused]] void *lowPtr, - [[maybe_unused]] void *highPtr, - [[maybe_unused]] size_t totalSize) { + umf_result_t ext_allocation_merge(void *, void *, size_t) { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t ext_allocation_split([[maybe_unused]] void *ptr, - [[maybe_unused]] size_t totalSize, - [[maybe_unused]] size_t firstSize) { + umf_result_t ext_allocation_split(void *, size_t, size_t) { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t - ext_get_ipc_handle_size([[maybe_unused]] size_t *size) noexcept { + umf_result_t ext_get_ipc_handle_size(size_t *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t - ext_get_ipc_handle([[maybe_unused]] const void *ptr, - [[maybe_unused]] size_t size, - [[maybe_unused]] void *providerIpcData) noexcept { + umf_result_t ext_get_ipc_handle(const void *, size_t, void *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t - ext_put_ipc_handle([[maybe_unused]] void *providerIpcData) noexcept { + umf_result_t ext_put_ipc_handle(void *) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t ext_open_ipc_handle([[maybe_unused]] void *providerIpcData, - [[maybe_unused]] void **ptr) noexcept { + umf_result_t ext_open_ipc_handle(void *, void **) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } - umf_result_t ext_close_ipc_handle([[maybe_unused]] void *ptr, - [[maybe_unused]] size_t size) noexcept { + umf_result_t ext_close_ipc_handle(void *, size_t) noexcept { return UMF_RESULT_ERROR_UNKNOWN; } virtual ~provider_base_t() = default; diff --git a/test/memoryPoolAPI.cpp b/test/memoryPoolAPI.cpp index 16d7afd58..842daf430 100644 --- a/test/memoryPoolAPI.cpp +++ b/test/memoryPoolAPI.cpp @@ -13,7 +13,9 @@ #include #include +#include #include +#include #ifdef UMF_PROXY_LIB_ENABLED #include @@ -126,6 +128,12 @@ TEST_P(umfPoolWithCreateFlagsTest, memoryPoolWithCustomProvider) { EXPECT_NE_NOEXCEPT(provider, nullptr); return UMF_RESULT_SUCCESS; } + umf_result_t get_name(const char **name) noexcept { + if (name) { + *name = "pool"; + } + return UMF_RESULT_SUCCESS; + } }; umf_memory_pool_ops_t pool_ops = umf_test::poolMakeCOps(); @@ -309,7 +317,12 @@ INSTANTIATE_TEST_SUITE_P( &BA_GLOBAL_PROVIDER_OPS, nullptr, nullptr}, poolCreateExtParams{umfDisjointPoolOps(), defaultDisjointPoolConfig, defaultDisjointPoolConfigDestroy, + &BA_GLOBAL_PROVIDER_OPS, nullptr, nullptr}, + poolCreateExtParams{umfScalablePoolOps(), nullptr, nullptr, &BA_GLOBAL_PROVIDER_OPS, nullptr, nullptr})); +// TODO enable jemalloc pool tests +//poolCreateExtParams{umfJemallocPoolOps(), nullptr, nullptr, +// &BA_GLOBAL_PROVIDER_OPS, nullptr, nullptr})); INSTANTIATE_TEST_SUITE_P(mallocMultiPoolTest, umfMultiPoolTest, ::testing::Values(poolCreateExtParams{ diff --git a/test/poolFixtures.hpp b/test/poolFixtures.hpp index 98778cd56..d83ded7b5 100644 --- a/test/poolFixtures.hpp +++ b/test/poolFixtures.hpp @@ -403,6 +403,36 @@ TEST_P(umfPoolTest, multiThreadedMallocFreeRandomSizes) { } } +TEST_P(umfPoolTest, trimMemory) { + constexpr size_t size = 1024; + + umf_memory_pool_handle_t hPool = pool.get(); + void *ptr = umfPoolMalloc(hPool, size); + ASSERT_NE(ptr, nullptr); + + umf_result_t ret = umfPoolFree(hPool, ptr); + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + + // use CTL to get the current memory pool size (if supported) + size_t reserved_memory1 = 0; + ret = umfCtlGet("umf.pool.by_handle.{}.stats.reserved_memory", + &reserved_memory1, sizeof(size_t), hPool); + if (ret == UMF_RESULT_SUCCESS) { + ASSERT_GE(reserved_memory1, 0); + } + + // Call to umfPoolTrimMemory should purge the whole memory pool + ret = umfPoolTrimMemory(hPool, 0); + ASSERT_EQ(ret, UMF_RESULT_SUCCESS); + + size_t reserved_memory2 = 0; + ret = umfCtlGet("umf.pool.by_handle.{}.stats.reserved_memory", + &reserved_memory2, sizeof(size_t), hPool); + if (ret == UMF_RESULT_SUCCESS) { + ASSERT_EQ(reserved_memory2, 0); + } +} + TEST_P(umfMemTest, outOfMem) { static constexpr size_t allocSize = 4096; auto hPool = pool.get(); diff --git a/test/pools/disjoint_pool.cpp b/test/pools/disjoint_pool.cpp index 92ccd0410..12eaac960 100644 --- a/test/pools/disjoint_pool.cpp +++ b/test/pools/disjoint_pool.cpp @@ -274,6 +274,85 @@ TEST_F(test, sharedLimits) { EXPECT_EQ(MaxSize / SlabMinSize * 2, numFrees); } +TEST_F(test, disjointPoolTrim) { + struct memory_provider : public umf_test::provider_base_t { + umf_result_t alloc(size_t size, size_t alignment, void **ptr) noexcept { + *ptr = umf_ba_global_aligned_alloc(size, alignment); + return UMF_RESULT_SUCCESS; + } + + umf_result_t free(void *ptr, [[maybe_unused]] size_t size) noexcept { + umf_ba_global_free(ptr); + return UMF_RESULT_SUCCESS; + } + }; + + umf_memory_provider_ops_t provider_ops = + umf_test::providerMakeCOps(); + + auto providerUnique = + wrapProviderUnique(createProviderChecked(&provider_ops, nullptr)); + + umf_memory_provider_handle_t provider_handle; + provider_handle = providerUnique.get(); + + umf_disjoint_pool_params_handle_t params = + (umf_disjoint_pool_params_handle_t)defaultDisjointPoolConfig(); + params->pool_trace = 3; + // Set the slab min size to 64 so allocating 64 bytes will use the whole + // slab. + params->slab_min_size = 64; + params->capacity = 4; + + // in "internals" test we use ops interface to directly manipulate the pool + // structure + const umf_memory_pool_ops_t *ops = umfDisjointPoolOps(); + EXPECT_NE(ops, nullptr); + + disjoint_pool_t *pool; + umf_result_t res = ops->initialize(provider_handle, params, (void **)&pool); + EXPECT_EQ(res, UMF_RESULT_SUCCESS); + EXPECT_NE(pool, nullptr); + + // do 4 allocs, then free all of them + size_t size = 64; + void *ptrs[4] = {0}; + ptrs[0] = ops->malloc(pool, size); + EXPECT_NE(ptrs[0], nullptr); + ptrs[1] = ops->malloc(pool, size); + EXPECT_NE(ptrs[1], nullptr); + ptrs[2] = ops->malloc(pool, size); + EXPECT_NE(ptrs[2], nullptr); + ptrs[3] = ops->malloc(pool, size); + EXPECT_NE(ptrs[3], nullptr); + + ops->free(pool, ptrs[0]); + ops->free(pool, ptrs[1]); + ops->free(pool, ptrs[2]); + ops->free(pool, ptrs[3]); + + // Because we set the slab min size to 64, each allocation should go to the + // separate slab. Additionally, because we set the capacity to 4, all slabs + // should still be in the pool available for new allocations. + EXPECT_EQ(pool->buckets[0]->available_slabs_num, 4); + EXPECT_EQ(pool->buckets[0]->curr_slabs_in_use, 0); + EXPECT_EQ(pool->buckets[0]->curr_slabs_in_pool, 4); + + // Trim memory - leave only one slab + ops->ext_trim_memory(pool, pool->buckets[0]->size); + EXPECT_EQ(pool->buckets[0]->available_slabs_num, 1); + EXPECT_EQ(pool->buckets[0]->curr_slabs_in_pool, 1); + + // Trim the rest of memory + ops->ext_trim_memory(pool, 0); + EXPECT_EQ(pool->buckets[0]->available_slabs_num, 0); + EXPECT_EQ(pool->buckets[0]->curr_slabs_in_pool, 0); + + ops->finalize(pool); + res = umfDisjointPoolParamsDestroy(params); + EXPECT_EQ(res, UMF_RESULT_SUCCESS); +} + TEST_F(test, disjointPoolNullParams) { umf_result_t res = umfDisjointPoolParamsCreate(nullptr); EXPECT_EQ(res, UMF_RESULT_ERROR_INVALID_ARGUMENT); diff --git a/test/utils/cpp_helpers.hpp b/test/utils/cpp_helpers.hpp index ca1940e16..963d7d07c 100644 --- a/test/utils/cpp_helpers.hpp +++ b/test/utils/cpp_helpers.hpp @@ -10,11 +10,6 @@ #ifndef UMF_TEST_HELPERS_HPP #define UMF_TEST_HELPERS_HPP 1 -#include -#include -#include -#include - #include #include #include @@ -22,6 +17,11 @@ #include #include +#include +#include +#include +#include + namespace umf_test { using pool_unique_handle_t = @@ -86,6 +86,8 @@ template umf_memory_pool_ops_t poolOpsBase() { UMF_ASSIGN_OP(ops, T, malloc_usable_size, UMF_RESULT_ERROR_UNKNOWN); UMF_ASSIGN_OP(ops, T, free, UMF_RESULT_ERROR_UNKNOWN); UMF_ASSIGN_OP(ops, T, get_last_allocation_error, UMF_RESULT_ERROR_UNKNOWN); + UMF_ASSIGN_OP(ops, T, ext_ctl, UMF_RESULT_ERROR_UNKNOWN); + UMF_ASSIGN_OP(ops, T, ext_trim_memory, UMF_RESULT_ERROR_UNKNOWN); return ops; }