diff --git a/src/umpire/ResourceManager.cpp b/src/umpire/ResourceManager.cpp index 07f1cd5c6..1829e3aa7 100644 --- a/src/umpire/ResourceManager.cpp +++ b/src/umpire/ResourceManager.cpp @@ -326,6 +326,145 @@ void ResourceManager::removeAlias(const std::string& name, Allocator allocator) m_allocators_by_name.erase(a); } +bool ResourceManager::isBuiltinAllocator(strategy::AllocationStrategy* strategy) +{ + for (const auto& entry : m_memory_resources) { + if (entry.second == strategy) { + return true; + } + } + + std::string name = strategy->getName(); + if (name == "__umpire_internal_null" || name == "__umpire_internal_0_byte_pool") { + return true; + } + + return false; +} + +void ResourceManager::destroyAllocator(const std::string& name, bool free_allocations) +{ + std::lock_guard lock(m_mutex); + + UMPIRE_LOG(Debug, "(name=\"" << name << "\", free_allocations=" << free_allocations << ")"); + + auto it = m_allocators_by_name.find(name); + if (it == m_allocators_by_name.end()) { + UMPIRE_ERROR(runtime_error, fmt::format("Allocator \"{}\" not found", name)); + } + + strategy::AllocationStrategy* strategy = it->second; + int id = strategy->getId(); + + if (isBuiltinAllocator(strategy)) { + UMPIRE_ERROR(runtime_error, + fmt::format("Cannot destroy builtin allocator \"{}\"", name)); + } + + if (isStrictDestructionMode()) { + auto records = umpire::get_allocator_records(Allocator(strategy)); + if (!records.empty() && !free_allocations) { + UMPIRE_ERROR(runtime_error, + fmt::format("Allocator \"{}\" has {} active allocations. " + "Use free_allocations=true or deallocate them first.", + name, records.size())); + } + } else if (!free_allocations) { + UMPIRE_LOG(Warning, "Allocator \"" << name << "\" may have active allocations. " + << "Destroying anyway (non-strict mode)."); + } + + + if (isStrictDestructionMode()) { + std::vector child_names; + for (const auto& alloc : m_allocators) { + if (alloc.get() != strategy && alloc->getParent() == strategy) { + child_names.push_back(alloc->getName()); + } + } + + if (!child_names.empty()) { + std::string children_str; + for (size_t i = 0; i < child_names.size(); ++i) { + if (i > 0) children_str += ", "; + children_str += child_names[i]; + } + + UMPIRE_ERROR(runtime_error, + fmt::format("Allocator \"{}\" is a parent of other allocators: {}. " + "Destroy children first.", + name, children_str)); + } + } else { + UMPIRE_LOG(Warning, "Allocator \"" << name << "\" may be a parent of other allocators. " + << "Destroying anyway (non-strict mode)."); + } + + if (free_allocations) { + auto records = umpire::get_allocator_records(Allocator(strategy)); + UMPIRE_LOG(Debug, "Freeing " << records.size() << " allocations"); + for (const auto& record : records) { + strategy->deallocate_internal(record.ptr, record.size); + } + } + + std::vector names_to_remove; + for (const auto& entry : m_allocators_by_name) { + if (entry.second == strategy) { + names_to_remove.push_back(entry.first); + } + } + + for (const auto& n : names_to_remove) { + m_allocators_by_name.erase(n); + } + + m_allocators_by_id.erase(id); + + for (auto it_mem = m_memory_resources.begin(); it_mem != m_memory_resources.end();) { + if (it_mem->second == strategy) { + it_mem = m_memory_resources.erase(it_mem); + } else { + ++it_mem; + } + } + + auto shared_it = std::find(m_shared_allocator_names.begin(), m_shared_allocator_names.end(), name); + if (shared_it != m_shared_allocator_names.end()) { + m_shared_allocator_names.erase(shared_it); + } + + for (auto it_alloc = m_allocators.begin(); it_alloc != m_allocators.end(); ++it_alloc) { + if (it_alloc->get() == strategy) { + m_allocators.erase(it_alloc); + break; + } + } + + umpire::event::record([&](auto& event) { + event.name("destroy_allocator") + .category(event::category::operation) + .arg("allocator_name", name) + .arg("allocator_id", id) + .arg("freed_allocations", free_allocations) + .tag("replay", "true"); + }); + + UMPIRE_LOG(Debug, "Allocator \"" << name << "\" destroyed successfully"); +} + +void ResourceManager::destroyAllocator(int id, bool free_allocations) +{ + UMPIRE_LOG(Debug, "(id=" << id << ", free_allocations=" << free_allocations << ")"); + + auto it = m_allocators_by_id.find(id); + if (it == m_allocators_by_id.end()) { + UMPIRE_ERROR(runtime_error, fmt::format("Allocator with id {} not found", id)); + } + + destroyAllocator(it->second->getName(), free_allocations); +} + Allocator ResourceManager::getAllocator(void* ptr) { UMPIRE_LOG(Debug, "(ptr=" << ptr << ")"); @@ -958,6 +1097,12 @@ std::shared_ptr ResourceManager::getOperation(const std::st return op_registry.find(operation_name, src_allocator.getAllocationStrategy(), dst_allocator.getAllocationStrategy()); } +bool ResourceManager::isStrictDestructionMode() const noexcept +{ + static const char* env_value = std::getenv("UMPIRE_STRICT_DESTRUCTION"); + return (env_value != nullptr); +} + int ResourceManager::getNumDevices() const { int device_count{0}; diff --git a/src/umpire/ResourceManager.hpp b/src/umpire/ResourceManager.hpp index 4cd72813f..345f686b5 100644 --- a/src/umpire/ResourceManager.hpp +++ b/src/umpire/ResourceManager.hpp @@ -161,6 +161,48 @@ class ResourceManager { */ void removeAlias(const std::string& name, Allocator allocator); + /*! + * \brief Destroy an allocator by name. + * + * Removes the allocator from the ResourceManager and frees associated + * resources. Core resource allocators (HOST, DEVICE, etc.) cannot be + * destroyed. + * + * Behavior is controlled by the UMPIRE_STRICT_DESTRUCTION environment variable: + * + * If UMPIRE_STRICT_DESTRUCTION is set (to any value): + * - Throws error if allocator has active allocations and free_allocations=false + * - Throws error if allocator is a parent of other allocators + * + * If UMPIRE_STRICT_DESTRUCTION is not set (default): + * - Logs warning but proceeds if allocator has active allocations + * - Logs warning but proceeds if allocator is a parent + * + * Example: + * export UMPIRE_STRICT_DESTRUCTION=1 // Enable strict mode + * unset UMPIRE_STRICT_DESTRUCTION // Disable strict mode (default) + * + * \param name Name of the allocator to destroy + * \param free_allocations If true, deallocates all active allocations + * before destroying. Defaults to false. + * + * \throw runtime_error if allocator is a core resource or not found + */ + void destroyAllocator(const std::string& name, bool free_allocations = false); + + /*! + * \brief Destroy an allocator by ID. + * + * See destroyAllocator(const std::string&, bool) for detailed behavior. + * + * \param id ID of the allocator to destroy + * \param free_allocations If true, deallocates all active allocations + * before destroying. Defaults to false. + * + * \throw runtime_error if allocator is a core resource or not found + */ + void destroyAllocator(int id, bool free_allocations = false); + /*! * \brief Get the Allocator used to allocate ptr. * @@ -328,6 +370,19 @@ class ResourceManager { strategy::AllocationStrategy* findAllocatorForId(int id); strategy::AllocationStrategy* getAllocationStrategy(const std::string& name); + bool isBuiltinAllocator(strategy::AllocationStrategy* strategy); + + /*! + * \brief Check if strict destruction mode is enabled via environment variable. + * + * Checks the UMPIRE_STRICT_DESTRUCTION environment variable. If set to any + * value, strict mode is enabled (errors thrown). If unset, non-strict mode + * is used (warnings logged). The check is cached on first call. + * + * \return true if UMPIRE_STRICT_DESTRUCTION is set, false otherwise + */ + bool isStrictDestructionMode() const noexcept; + int getNextId() noexcept; std::string getAllocatorInformation() const noexcept; diff --git a/src/umpire/ResourceManager.inl b/src/umpire/ResourceManager.inl index ffec72fc2..2c96be943 100644 --- a/src/umpire/ResourceManager.inl +++ b/src/umpire/ResourceManager.inl @@ -11,6 +11,7 @@ #include "camp/list.hpp" #include "umpire/ResourceManager.hpp" +#include "umpire/event/event.hpp" #include "umpire/util/Macros.hpp" #include "umpire/util/error.hpp" #include "umpire/util/make_unique.hpp" diff --git a/src/umpire/interface/c_fortran/wrapResourceManager.cpp b/src/umpire/interface/c_fortran/wrapResourceManager.cpp index b0c39f875..9b8f1254d 100644 --- a/src/umpire/interface/c_fortran/wrapResourceManager.cpp +++ b/src/umpire/interface/c_fortran/wrapResourceManager.cpp @@ -729,6 +729,96 @@ void umpire_resourcemanager_remove_alias_bufferify( // splicer end class.ResourceManager.method.remove_alias_bufferify } +/** + * \brief Destroy an allocator by name + * + */ +void umpire_resourcemanager_destroy_allocator_by_name( + umpire_resourcemanager * self, const char * name) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_name + const std::string SHCXX_name(name); + SH_this->destroyAllocator(SHCXX_name); + // splicer end class.ResourceManager.method.destroy_allocator_by_name +} + +/** + * \brief Destroy an allocator by name + * + */ +void umpire_resourcemanager_destroy_allocator_by_name_bufferify( + umpire_resourcemanager * self, const char * name, int Lname) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_name_bufferify + const std::string SHCXX_name(name, Lname); + SH_this->destroyAllocator(SHCXX_name); + // splicer end class.ResourceManager.method.destroy_allocator_by_name_bufferify +} + +/** + * \brief Destroy an allocator by name + * + */ +void umpire_resourcemanager_destroy_allocator_by_name_with_free( + umpire_resourcemanager * self, const char * name, + bool free_allocations) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_name_with_free + const std::string SHCXX_name(name); + SH_this->destroyAllocator(SHCXX_name, free_allocations); + // splicer end class.ResourceManager.method.destroy_allocator_by_name_with_free +} + +/** + * \brief Destroy an allocator by name + * + */ +void umpire_resourcemanager_destroy_allocator_by_name_with_free_bufferify( + umpire_resourcemanager * self, const char * name, int Lname, + bool free_allocations) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_name_with_free_bufferify + const std::string SHCXX_name(name, Lname); + SH_this->destroyAllocator(SHCXX_name, free_allocations); + // splicer end class.ResourceManager.method.destroy_allocator_by_name_with_free_bufferify +} + +/** + * \brief Destroy an allocator by ID + * + */ +void umpire_resourcemanager_destroy_allocator_by_id( + umpire_resourcemanager * self, int id) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_id + SH_this->destroyAllocator(id); + // splicer end class.ResourceManager.method.destroy_allocator_by_id +} + +/** + * \brief Destroy an allocator by ID + * + */ +void umpire_resourcemanager_destroy_allocator_by_id_with_free( + umpire_resourcemanager * self, int id, bool free_allocations) +{ + umpire::ResourceManager *SH_this = + static_cast(self->addr); + // splicer begin class.ResourceManager.method.destroy_allocator_by_id_with_free + SH_this->destroyAllocator(id, free_allocations); + // splicer end class.ResourceManager.method.destroy_allocator_by_id_with_free +} + umpire_allocator * umpire_resourcemanager_get_allocator_for_ptr( umpire_resourcemanager * self, void * ptr, umpire_allocator * SHC_rv) diff --git a/src/umpire/interface/c_fortran/wrapResourceManager.h b/src/umpire/interface/c_fortran/wrapResourceManager.h index fd4ebe729..8b2584ac5 100644 --- a/src/umpire/interface/c_fortran/wrapResourceManager.h +++ b/src/umpire/interface/c_fortran/wrapResourceManager.h @@ -200,6 +200,26 @@ void umpire_resourcemanager_remove_alias_bufferify( umpire_resourcemanager * self, const char * name, int Lname, umpire_allocator allocator); +void umpire_resourcemanager_destroy_allocator_by_name( + umpire_resourcemanager * self, const char * name); + +void umpire_resourcemanager_destroy_allocator_by_name_bufferify( + umpire_resourcemanager * self, const char * name, int Lname); + +void umpire_resourcemanager_destroy_allocator_by_name_with_free( + umpire_resourcemanager * self, const char * name, + bool free_allocations); + +void umpire_resourcemanager_destroy_allocator_by_name_with_free_bufferify( + umpire_resourcemanager * self, const char * name, int Lname, + bool free_allocations); + +void umpire_resourcemanager_destroy_allocator_by_id( + umpire_resourcemanager * self, int id); + +void umpire_resourcemanager_destroy_allocator_by_id_with_free( + umpire_resourcemanager * self, int id, bool free_allocations); + umpire_allocator * umpire_resourcemanager_get_allocator_for_ptr( umpire_resourcemanager * self, void * ptr, umpire_allocator * SHC_rv); diff --git a/src/umpire/interface/c_fortran/wrapfumpire.f b/src/umpire/interface/c_fortran/wrapfumpire.f index a09bd4ec6..1646a604f 100644 --- a/src/umpire/interface/c_fortran/wrapfumpire.f +++ b/src/umpire/interface/c_fortran/wrapfumpire.f @@ -244,6 +244,10 @@ module umpire_mod procedure :: make_allocator_prefetcher => resourcemanager_make_allocator_prefetcher procedure :: add_alias => resourcemanager_add_alias procedure :: remove_alias => resourcemanager_remove_alias + procedure :: destroy_allocator_by_name => resourcemanager_destroy_allocator_by_name + procedure :: destroy_allocator_by_name_with_free => resourcemanager_destroy_allocator_by_name_with_free + procedure :: destroy_allocator_by_id => resourcemanager_destroy_allocator_by_id + procedure :: destroy_allocator_by_id_with_free => resourcemanager_destroy_allocator_by_id_with_free procedure :: get_allocator_for_ptr => resourcemanager_get_allocator_for_ptr procedure :: is_allocator_name => resourcemanager_is_allocator_name procedure :: is_allocator_id => resourcemanager_is_allocator_id @@ -261,6 +265,9 @@ module umpire_mod procedure :: deregister_allocation => resourcemanager_deregister_allocation procedure :: associated => resourcemanager_associated generic :: copy => copy_all, copy_with_size + generic :: destroy_allocator => destroy_allocator_by_name, & + destroy_allocator_by_name_with_free, destroy_allocator_by_id & + , destroy_allocator_by_id_with_free generic :: get_allocator => get_allocator_by_name, & get_allocator_by_id, get_allocator_for_ptr generic :: is_allocator => is_allocator_name, is_allocator_id @@ -954,6 +961,70 @@ subroutine c_resourcemanager_remove_alias_bufferify(self, name, & type(umpire_SHROUD_allocator_capsule), intent(IN), value :: allocator end subroutine c_resourcemanager_remove_alias_bufferify + subroutine c_resourcemanager_destroy_allocator_by_name(self, & + name) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_name") + use iso_c_binding, only : C_CHAR + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + character(kind=C_CHAR), intent(IN) :: name(*) + end subroutine c_resourcemanager_destroy_allocator_by_name + + subroutine c_resourcemanager_destroy_allocator_by_name_bufferify( & + self, name, Lname) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_name_bufferify") + use iso_c_binding, only : C_CHAR, C_INT + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + character(kind=C_CHAR), intent(IN) :: name(*) + integer(C_INT), value, intent(IN) :: Lname + end subroutine c_resourcemanager_destroy_allocator_by_name_bufferify + + subroutine c_resourcemanager_destroy_allocator_by_name_with_free( & + self, name, free_allocations) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_name_with_free") + use iso_c_binding, only : C_BOOL, C_CHAR + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + character(kind=C_CHAR), intent(IN) :: name(*) + logical(C_BOOL), value, intent(IN) :: free_allocations + end subroutine c_resourcemanager_destroy_allocator_by_name_with_free + + subroutine c_resourcemanager_destroy_allocator_by_name_with_free_bufferify( & + self, name, Lname, free_allocations) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_name_with_free_bufferify") + use iso_c_binding, only : C_BOOL, C_CHAR, C_INT + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + character(kind=C_CHAR), intent(IN) :: name(*) + integer(C_INT), value, intent(IN) :: Lname + logical(C_BOOL), value, intent(IN) :: free_allocations + end subroutine c_resourcemanager_destroy_allocator_by_name_with_free_bufferify + + subroutine c_resourcemanager_destroy_allocator_by_id(self, id) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_id") + use iso_c_binding, only : C_INT + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + integer(C_INT), value, intent(IN) :: id + end subroutine c_resourcemanager_destroy_allocator_by_id + + subroutine c_resourcemanager_destroy_allocator_by_id_with_free( & + self, id, free_allocations) & + bind(C, name="umpire_resourcemanager_destroy_allocator_by_id_with_free") + use iso_c_binding, only : C_BOOL, C_INT + import :: umpire_SHROUD_resourcemanager_capsule + implicit none + type(umpire_SHROUD_resourcemanager_capsule), intent(IN) :: self + integer(C_INT), value, intent(IN) :: id + logical(C_BOOL), value, intent(IN) :: free_allocations + end subroutine c_resourcemanager_destroy_allocator_by_id_with_free + function c_resourcemanager_get_allocator_for_ptr(self, ptr, & SHT_crv) & result(SHT_rv) & @@ -3115,6 +3186,69 @@ subroutine resourcemanager_remove_alias(obj, name, allocator) ! splicer end class.ResourceManager.method.remove_alias end subroutine resourcemanager_remove_alias + !> + !! \brief Destroy an allocator by name + !! + !< + subroutine resourcemanager_destroy_allocator_by_name(obj, name) + use iso_c_binding, only : C_INT + class(UmpireResourceManager) :: obj + character(len=*), intent(IN) :: name + ! splicer begin class.ResourceManager.method.destroy_allocator_by_name + call c_resourcemanager_destroy_allocator_by_name_bufferify(obj%cxxmem, & + name, len_trim(name, kind=C_INT)) + ! splicer end class.ResourceManager.method.destroy_allocator_by_name + end subroutine resourcemanager_destroy_allocator_by_name + + !> + !! \brief Destroy an allocator by name + !! + !< + subroutine resourcemanager_destroy_allocator_by_name_with_free(obj, & + name, free_allocations) + use iso_c_binding, only : C_BOOL, C_INT + class(UmpireResourceManager) :: obj + character(len=*), intent(IN) :: name + logical, value, intent(IN) :: free_allocations + ! splicer begin class.ResourceManager.method.destroy_allocator_by_name_with_free + logical(C_BOOL) SH_free_allocations + SH_free_allocations = free_allocations ! coerce to C_BOOL + call c_resourcemanager_destroy_allocator_by_name_with_free_bufferify(obj%cxxmem, & + name, len_trim(name, kind=C_INT), SH_free_allocations) + ! splicer end class.ResourceManager.method.destroy_allocator_by_name_with_free + end subroutine resourcemanager_destroy_allocator_by_name_with_free + + !> + !! \brief Destroy an allocator by ID + !! + !< + subroutine resourcemanager_destroy_allocator_by_id(obj, id) + use iso_c_binding, only : C_INT + class(UmpireResourceManager) :: obj + integer(C_INT), value, intent(IN) :: id + ! splicer begin class.ResourceManager.method.destroy_allocator_by_id + call c_resourcemanager_destroy_allocator_by_id(obj%cxxmem, id) + ! splicer end class.ResourceManager.method.destroy_allocator_by_id + end subroutine resourcemanager_destroy_allocator_by_id + + !> + !! \brief Destroy an allocator by ID + !! + !< + subroutine resourcemanager_destroy_allocator_by_id_with_free(obj, & + id, free_allocations) + use iso_c_binding, only : C_BOOL, C_INT + class(UmpireResourceManager) :: obj + integer(C_INT), value, intent(IN) :: id + logical, value, intent(IN) :: free_allocations + ! splicer begin class.ResourceManager.method.destroy_allocator_by_id_with_free + logical(C_BOOL) SH_free_allocations + SH_free_allocations = free_allocations ! coerce to C_BOOL + call c_resourcemanager_destroy_allocator_by_id_with_free(obj%cxxmem, & + id, SH_free_allocations) + ! splicer end class.ResourceManager.method.destroy_allocator_by_id_with_free + end subroutine resourcemanager_destroy_allocator_by_id_with_free + function resourcemanager_get_allocator_for_ptr(obj, ptr) & result(SHT_rv) use iso_c_binding, only : C_PTR diff --git a/src/umpire/interface/umpire_shroud.yaml b/src/umpire/interface/umpire_shroud.yaml index 77bc05bab..602141467 100644 --- a/src/umpire/interface/umpire_shroud.yaml +++ b/src/umpire/interface/umpire_shroud.yaml @@ -264,6 +264,20 @@ declarations: - decl: void addAlias(const std::string& name, Allocator allocator) - decl: void removeAlias(const std::string& name, Allocator allocator) + - decl: void destroyAllocator(const std::string& name, bool free_allocations=false) + doxygen: + brief: Destroy an allocator by name + default_arg_suffix: + - _by_name + - _by_name_with_free + + - decl: void destroyAllocator(int id, bool free_allocations=false) + doxygen: + brief: Destroy an allocator by ID + default_arg_suffix: + - _by_id + - _by_id_with_free + - decl: Allocator getAllocator(void* ptr) format: function_suffix: _for_ptr diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 9c83a846f..4937e25bc 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -243,6 +243,37 @@ blt_add_test( NAME introspection_tests COMMAND introspection_tests) +blt_add_executable( + NAME destroy_allocator_tests + SOURCES destroy_allocator_tests.cpp + DEPENDS_ON ${integration_tests_depends}) + +target_include_directories( + destroy_allocator_tests + PRIVATE + ${PROJECT_BINARY_DIR}/include) + +blt_add_test( + NAME destroy_allocator_tests + COMMAND destroy_allocator_tests) + +blt_add_executable( + NAME destroy_allocator_strict_mode_tests + SOURCES destroy_allocator_strict_mode_tests.cpp + DEPENDS_ON ${integration_tests_depends}) + +target_include_directories( + destroy_allocator_strict_mode_tests + PRIVATE + ${PROJECT_BINARY_DIR}/include) + +blt_add_test( + NAME destroy_allocator_strict_mode_tests + COMMAND destroy_allocator_strict_mode_tests) + +set_property(TEST destroy_allocator_strict_mode_tests + PROPERTY ENVIRONMENT "UMPIRE_STRICT_DESTRUCTION=1") + if (UMPIRE_ENABLE_IPC_SHARED_MEMORY AND UMPIRE_ENABLE_MPI) blt_add_executable( NAME get_communicator_tests diff --git a/tests/integration/destroy_allocator_strict_mode_tests.cpp b/tests/integration/destroy_allocator_strict_mode_tests.cpp new file mode 100644 index 000000000..76ac98ec1 --- /dev/null +++ b/tests/integration/destroy_allocator_strict_mode_tests.cpp @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and Umpire +// project contributors. See the COPYRIGHT file for details. +// +// SPDX-License-Identifier: (MIT) +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include "umpire/Umpire.hpp" +#include "umpire/config.hpp" +#include "umpire/strategy/QuickPool.hpp" + +// These tests require UMPIRE_STRICT_DESTRUCTION=1 to be set in the environment +// before the process starts, since the mode is determined by a static variable. +// The CMakeLists.txt sets this environment variable when running this test suite. + +TEST(DestroyAllocatorStrictModeTest, ActiveAllocations) +{ + auto& rm = umpire::ResourceManager::getInstance(); + auto pool = rm.makeAllocator("test_pool_strict", rm.getAllocator("HOST")); + void* ptr = pool.allocate(100); + ASSERT_NE(nullptr, ptr); + + // Try to destroy with active allocations - should throw error in strict mode + ASSERT_THROW(rm.destroyAllocator("test_pool_strict", false), umpire::runtime_error); + + // Clean up + pool.deallocate(ptr); + rm.destroyAllocator("test_pool_strict"); +} + +TEST(DestroyAllocatorStrictModeTest, ParentChildWarning) +{ + auto& rm = umpire::ResourceManager::getInstance(); + auto parent = rm.makeAllocator("test_parent_strict", rm.getAllocator("HOST")); + auto child = rm.makeAllocator("test_child_strict", parent); + + UMPIRE_USE_VAR(child); + + // Try to destroy parent - should throw error in strict mode + ASSERT_THROW(rm.destroyAllocator("test_parent_strict"), umpire::runtime_error); + + // Clean up in correct order + rm.destroyAllocator("test_child_strict"); + rm.destroyAllocator("test_parent_strict"); +} diff --git a/tests/integration/destroy_allocator_tests.cpp b/tests/integration/destroy_allocator_tests.cpp new file mode 100644 index 000000000..7c1eeb4bc --- /dev/null +++ b/tests/integration/destroy_allocator_tests.cpp @@ -0,0 +1,232 @@ +////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2016-26, Lawrence Livermore National Security, LLC and Umpire +// project contributors. See the COPYRIGHT file for details. +// +// SPDX-License-Identifier: (MIT) +////////////////////////////////////////////////////////////////////////////// +#include "gtest/gtest.h" +#include "umpire/Allocator.hpp" +#include "umpire/ResourceManager.hpp" +#include "umpire/Umpire.hpp" +#include "umpire/config.hpp" +#include "umpire/strategy/QuickPool.hpp" + +TEST(DestroyAllocatorTest, DestroyBasicQuickPool) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + auto pool = rm.makeAllocator("test_pool_basic", rm.getAllocator("HOST")); + + // Allocate and deallocate to verify it works + void* ptr = pool.allocate(100); + ASSERT_NE(nullptr, ptr); + pool.deallocate(ptr); + + // Verify allocator exists + ASSERT_TRUE(rm.isAllocator("test_pool_basic")); + + // Destroy the allocator + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_basic")); + + // Verify allocator no longer exists + ASSERT_FALSE(rm.isAllocator("test_pool_basic")); + + // Verify getting the allocator throws an error + ASSERT_THROW(rm.getAllocator("test_pool_basic"), umpire::runtime_error); +} + +TEST(DestroyAllocatorTest, DestroyAllocatorById) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + auto pool = rm.makeAllocator("test_pool_by_id", rm.getAllocator("HOST")); + int id = pool.getId(); + + // Verify allocator exists + ASSERT_TRUE(rm.isAllocator(id)); + + // Destroy the allocator by ID + ASSERT_NO_THROW(rm.destroyAllocator(id)); + + // Verify allocator no longer exists + ASSERT_FALSE(rm.isAllocator(id)); + ASSERT_FALSE(rm.isAllocator("test_pool_by_id")); +} + +TEST(DestroyAllocatorTest, ErrorOnCoreResourceDestruction) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Attempt to destroy HOST allocator - should throw error + ASSERT_THROW(rm.destroyAllocator("HOST"), umpire::runtime_error); + + // Verify HOST still exists + ASSERT_TRUE(rm.isAllocator("HOST")); + +#ifdef UMPIRE_ENABLE_DEVICE + // Attempt to destroy DEVICE allocator - should throw error + ASSERT_THROW(rm.destroyAllocator("DEVICE"), umpire::runtime_error); + + // Verify DEVICE still exists + ASSERT_TRUE(rm.isAllocator("DEVICE")); +#endif +} + +TEST(DestroyAllocatorTest, ErrorOnInternalAllocators) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Attempt to destroy internal null allocator - should throw error + ASSERT_THROW(rm.destroyAllocator("__umpire_internal_null"), umpire::runtime_error); + + // Attempt to destroy internal 0-byte pool - should throw error + ASSERT_THROW(rm.destroyAllocator("__umpire_internal_0_byte_pool"), umpire::runtime_error); +} + +TEST(DestroyAllocatorTest, ActiveAllocationsNonStrictMode) +{ + auto& rm = umpire::ResourceManager::getInstance(); + auto pool = rm.makeAllocator("test_pool_nonstrict", rm.getAllocator("HOST")); + void* ptr = pool.allocate(100); + ASSERT_NE(nullptr, ptr); + + // Destroy with active allocations - should succeed with warning in non-strict mode + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_nonstrict", false)); + + // Verify allocator is destroyed + ASSERT_FALSE(rm.isAllocator("test_pool_nonstrict")); +} + +TEST(DestroyAllocatorTest, FreeAllocationsOnDestroy) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + auto pool = rm.makeAllocator("test_pool_free", rm.getAllocator("HOST")); + + // Allocate multiple chunks of memory + void* ptr1 = pool.allocate(100); + void* ptr2 = pool.allocate(200); + void* ptr3 = pool.allocate(300); + ASSERT_NE(nullptr, ptr1); + ASSERT_NE(nullptr, ptr2); + ASSERT_NE(nullptr, ptr3); + + // Destroy with free_allocations=true - should succeed and free all allocations + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_free", true)); + + // Verify allocator is destroyed + ASSERT_FALSE(rm.isAllocator("test_pool_free")); +} + +TEST(DestroyAllocatorTest, DestroyAllocatorWithAliases) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + auto pool = rm.makeAllocator("test_pool_alias", rm.getAllocator("HOST")); + + // Add aliases + rm.addAlias("alias1", pool); + rm.addAlias("alias2", pool); + + // Verify aliases exist + ASSERT_TRUE(rm.isAllocator("test_pool_alias")); + ASSERT_TRUE(rm.isAllocator("alias1")); + ASSERT_TRUE(rm.isAllocator("alias2")); + + // Destroy by original name + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_alias")); + + // Verify all aliases are removed + ASSERT_FALSE(rm.isAllocator("test_pool_alias")); + ASSERT_FALSE(rm.isAllocator("alias1")); + ASSERT_FALSE(rm.isAllocator("alias2")); +} + +TEST(DestroyAllocatorTest, ParentChildWarningNonStrictMode) +{ + auto& rm = umpire::ResourceManager::getInstance(); + auto parent = rm.makeAllocator("test_parent_nonstrict", rm.getAllocator("HOST")); + rm.makeAllocator("test_child_nonstrict", parent); + + // Destroy parent - should succeed with warning in non-strict mode + ASSERT_NO_THROW(rm.destroyAllocator("test_parent_nonstrict")); + + // Clean up child + rm.destroyAllocator("test_child_nonstrict"); +} + +#if defined(UMPIRE_ENABLE_IPC_SHARED_MEMORY) || defined(UMPIRE_ENABLE_MPI3_SHARED_MEMORY) +TEST(DestroyAllocatorTest, DestroySharedAllocator) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a SHARED allocator + auto shared_alloc = rm.makeResource("SHARED"); + std::string shared_name = shared_alloc.getName(); + + // Verify it's in the shared allocator names list + auto shared_names = rm.getSharedAllocatorNames(); + ASSERT_TRUE(std::find(shared_names.begin(), shared_names.end(), shared_name) != shared_names.end()); + + // Destroy the shared allocator + ASSERT_NO_THROW(rm.destroyAllocator(shared_name)); + + // Verify it's removed from shared allocator names + shared_names = rm.getSharedAllocatorNames(); + ASSERT_TRUE(std::find(shared_names.begin(), shared_names.end(), shared_name) == shared_names.end()); +} +#endif + +TEST(DestroyAllocatorTest, AllocatorNotFound) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Attempt to destroy non-existent allocator - should throw error + ASSERT_THROW(rm.destroyAllocator("non_existent_allocator"), umpire::runtime_error); + ASSERT_THROW(rm.destroyAllocator(99999), umpire::runtime_error); +} + +TEST(DestroyAllocatorTest, RepeatedDestroy) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + rm.makeAllocator("test_pool_repeated", rm.getAllocator("HOST")); + + // Destroy once - should succeed + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_repeated")); + + // Attempt to destroy again - should throw "not found" error + ASSERT_THROW(rm.destroyAllocator("test_pool_repeated"), umpire::runtime_error); +} + +TEST(DestroyAllocatorTest, DestroyAndRecreate) +{ + auto& rm = umpire::ResourceManager::getInstance(); + + // Create a QuickPool + auto pool1 = rm.makeAllocator("test_pool_recreate", rm.getAllocator("HOST")); + int id1 = pool1.getId(); + + // Destroy it + ASSERT_NO_THROW(rm.destroyAllocator("test_pool_recreate")); + + // Create a new allocator with the same name + auto pool2 = rm.makeAllocator("test_pool_recreate", rm.getAllocator("HOST")); + int id2 = pool2.getId(); + + // Should have different IDs + ASSERT_NE(id1, id2); + + // Verify new allocator works + void* ptr = pool2.allocate(100); + ASSERT_NE(nullptr, ptr); + pool2.deallocate(ptr); + + // Clean up + rm.destroyAllocator("test_pool_recreate"); +}