From 91791c00e714d2e1bded2e93a405e47a2e110011 Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Sat, 22 Aug 2026 01:26:53 -0700 Subject: [PATCH 1/3] ABLASTR: Warn Manager State is Per AMReX Cycle `WarnManager::GetInstance()` held the singleton in a function-local `static`, so its state lived for the whole process instead of for one AMReX initialize/finalize cycle. A process that runs more than one simulation - a parameter scan in Python, or the pytest unit tests - saw the second simulation start with the warnings of the first one still in the list, and with `always_warn_immediately` and `abort_on_warning_threshold` still set to whatever the first inputs deck had chosen. The instance now lives in a `std::unique_ptr` that is created on first use and released from an `amrex::ExecOnFinalize` hook, so every cycle starts with a clean warning list, with the default warning settings and with a freshly sampled `m_rank`. Tying the reset to `amrex::Finalize()` rather than to `WarpX::ResetInstance()` keeps ABLASTR independent of WarpX, and covers the warnings that are recorded outside the lifetime of a WarpX instance, such as the ones from `check_mpi_thread_level()`. The teardown must not itself record a warning. The mutex is not recursive, so the instance is moved out under the lock and destroyed after the lock is released; and `amrex::Finalize()` calls `top()()` before `pop()`, so a teardown hook that is registered while the stack drains is silently discarded and the state would leak into the next cycle anyway. Neither is reachable today - `~WarnManager` is defaulted, and `initialize_warning_manager()` registers the teardown ahead of every other finalize hook - so the second one is only documented in the code. This follows https://github.com/BLAST-WarpX/warpx/pull/7142 and https://github.com/BLAST-WarpX/warpx/pull/7143, which removed the equivalent process-lifetime state from the WarpX singleton and from the Python input buckets. --- Source/ablastr/warn_manager/WarnManager.H | 10 ++++++ Source/ablastr/warn_manager/WarnManager.cpp | 38 +++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Source/ablastr/warn_manager/WarnManager.H b/Source/ablastr/warn_manager/WarnManager.H index 56ff1bf1a1a..9bcc0d8fd68 100644 --- a/Source/ablastr/warn_manager/WarnManager.H +++ b/Source/ablastr/warn_manager/WarnManager.H @@ -74,6 +74,15 @@ namespace ablastr::warn_manager */ ~WarnManager() = default; + /** + * \brief Returns the WarnManager instance of the current AMReX + * initialize/finalize cycle, creating it on first use. + * + * The instance is destroyed by the next amrex::Finalize, so the + * returned reference must not be stored beyond it. + * + * @return the instance of the WarnManager class + */ static WarnManager& GetInstance(); /** @@ -211,6 +220,7 @@ namespace ablastr::warn_manager /** * \brief Helper function to abbreviate the call to get a WarnManager instance + * (the returned reference must not be stored beyond the next amrex::Finalize) * * @return the instance of the WarnManager class */ diff --git a/Source/ablastr/warn_manager/WarnManager.cpp b/Source/ablastr/warn_manager/WarnManager.cpp index e7544c7a3a4..b7d0dbba366 100644 --- a/Source/ablastr/warn_manager/WarnManager.cpp +++ b/Source/ablastr/warn_manager/WarnManager.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include @@ -25,6 +27,14 @@ using namespace ablastr::warn_manager; namespace { + //! the WarnManager instance of the current AMReX initialize/finalize cycle + //! (with nested amrex::Initialize, the innermost amrex::Finalize already ends it) + std::unique_ptr warn_manager_instance; + + //! guards creation and teardown of warn_manager_instance - the pointer, not the + //! reference GetInstance returns; a namespace-scope pointer has no thread-safe init + std::mutex warn_manager_instance_mutex; + WarnPriority MapPriorityToWarnPriority ( const abl_msg_logger::Priority& priority) { @@ -45,8 +55,32 @@ namespace } WarnManager& WarnManager::GetInstance() { - static auto warn_manager = WarnManager{}; - return warn_manager; + const std::lock_guard lock{warn_manager_instance_mutex}; + + if (!warn_manager_instance) + { + // The state of the warn manager (recorded warnings, the + // warn-immediately flag, the abort threshold and the MPI rank) belongs + // to one AMReX initialize/finalize cycle: a process that runs more than + // one simulation, e.g., a parameter scan in Python, starts each of them + // with a clean warning list and with the default warning settings. + // std::make_unique is not an option here, because the constructor is + // private. + warn_manager_instance = std::unique_ptr{new WarnManager}; + + // Do not record a warning from a finalize hook: amrex::Finalize calls top()() before + // pop(), so a teardown pushed from there is dropped and leaks into the next cycle. + amrex::ExecOnFinalize([](){ + // destroyed outside the lock: the non-recursive mutex deadlocks on a warning + std::unique_ptr expiring; + { + const std::lock_guard finalize_lock{warn_manager_instance_mutex}; + expiring = std::move(warn_manager_instance); + } + }); + } + + return *warn_manager_instance; } WarnManager::WarnManager(): From a270e0289958e1a4aba742fcd118fd1f1f1a796f Mon Sep 17 00:00:00 2001 From: David Grote Date: Wed, 26 Aug 2026 12:57:55 -0700 Subject: [PATCH 2/3] Implement clearing of the warning messages --- Source/WarpX.cpp | 3 ++ Source/ablastr/utils/msg_logger/MsgLogger.H | 5 +++ Source/ablastr/warn_manager/WarnManager.H | 10 +++++ Source/ablastr/warn_manager/WarnManager.cpp | 48 ++++++--------------- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/Source/WarpX.cpp b/Source/WarpX.cpp index b2d3fa67c19..b3709127d1c 100644 --- a/Source/WarpX.cpp +++ b/Source/WarpX.cpp @@ -329,6 +329,9 @@ void WarpX::Finalize() { WarpX::ResetInstance(); + + // Clear all of the warning messages + ablastr::warn_manager::WMClear(); } WarpX::WarpX () diff --git a/Source/ablastr/utils/msg_logger/MsgLogger.H b/Source/ablastr/utils/msg_logger/MsgLogger.H index 088a613bc87..2e1ce7365a2 100644 --- a/Source/ablastr/utils/msg_logger/MsgLogger.H +++ b/Source/ablastr/utils/msg_logger/MsgLogger.H @@ -224,6 +224,11 @@ namespace ablastr::utils::msg_logger [[nodiscard]] std::vector collective_gather_msgs_with_counter_and_ranks() const; + /** + * \brief Clear all messages + */ + void clear() { m_messages.clear(); } + private: /** diff --git a/Source/ablastr/warn_manager/WarnManager.H b/Source/ablastr/warn_manager/WarnManager.H index 9bcc0d8fd68..60f839b04f1 100644 --- a/Source/ablastr/warn_manager/WarnManager.H +++ b/Source/ablastr/warn_manager/WarnManager.H @@ -154,6 +154,11 @@ namespace ablastr::warn_manager */ void debug_read_warnings_from_input(const amrex::ParmParse& params); + /** + * \brief Clear all warning messages + */ + void Clear(); + static const int warn_line_size = 80 /*! Maximum line length to be used in formatting warning list*/; static const int warn_tab_size = 5 /*! Tabulation size to be used in formatting warning list*/; @@ -238,6 +243,11 @@ namespace ablastr::warn_manager const std::string& topic, const std::string& text, const WarnPriority& priority = WarnPriority::medium); + + /** + * \brief Clear all warning messages + */ + void WMClear(); } #endif //ABLASTR_WARN_MANAGER_H_ diff --git a/Source/ablastr/warn_manager/WarnManager.cpp b/Source/ablastr/warn_manager/WarnManager.cpp index b7d0dbba366..76a272ff83f 100644 --- a/Source/ablastr/warn_manager/WarnManager.cpp +++ b/Source/ablastr/warn_manager/WarnManager.cpp @@ -17,8 +17,6 @@ #include #include -#include -#include #include #include @@ -27,14 +25,6 @@ using namespace ablastr::warn_manager; namespace { - //! the WarnManager instance of the current AMReX initialize/finalize cycle - //! (with nested amrex::Initialize, the innermost amrex::Finalize already ends it) - std::unique_ptr warn_manager_instance; - - //! guards creation and teardown of warn_manager_instance - the pointer, not the - //! reference GetInstance returns; a namespace-scope pointer has no thread-safe init - std::mutex warn_manager_instance_mutex; - WarnPriority MapPriorityToWarnPriority ( const abl_msg_logger::Priority& priority) { @@ -55,32 +45,8 @@ namespace } WarnManager& WarnManager::GetInstance() { - const std::lock_guard lock{warn_manager_instance_mutex}; - - if (!warn_manager_instance) - { - // The state of the warn manager (recorded warnings, the - // warn-immediately flag, the abort threshold and the MPI rank) belongs - // to one AMReX initialize/finalize cycle: a process that runs more than - // one simulation, e.g., a parameter scan in Python, starts each of them - // with a clean warning list and with the default warning settings. - // std::make_unique is not an option here, because the constructor is - // private. - warn_manager_instance = std::unique_ptr{new WarnManager}; - - // Do not record a warning from a finalize hook: amrex::Finalize calls top()() before - // pop(), so a teardown pushed from there is dropped and leaks into the next cycle. - amrex::ExecOnFinalize([](){ - // destroyed outside the lock: the non-recursive mutex deadlocks on a warning - std::unique_ptr expiring; - { - const std::lock_guard finalize_lock{warn_manager_instance_mutex}; - expiring = std::move(warn_manager_instance); - } - }); - } - - return *warn_manager_instance; + static auto warn_manager = WarnManager{}; + return warn_manager; } WarnManager::WarnManager(): @@ -346,6 +312,11 @@ WarnManager::MsgFormatter( return ss_out.str(); } +void WarnManager::Clear() +{ + m_p_logger->clear(); +} + WarnManager& ablastr::warn_manager::GetWMInstance() { return WarnManager::GetInstance(); @@ -359,3 +330,8 @@ void ablastr::warn_manager::WMRecordWarning( WarnManager::GetInstance().RecordWarning( topic, text, priority); } + +void ablastr::warn_manager::WMClear() +{ + WarnManager::GetInstance().Clear(); +} From c123ec0c89908afd632180388da54aa841f58d41 Mon Sep 17 00:00:00 2001 From: David Grote Date: Wed, 26 Aug 2026 13:03:26 -0700 Subject: [PATCH 3/3] Fix comments --- Source/ablastr/warn_manager/WarnManager.H | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Source/ablastr/warn_manager/WarnManager.H b/Source/ablastr/warn_manager/WarnManager.H index 60f839b04f1..c9f0eedcbd1 100644 --- a/Source/ablastr/warn_manager/WarnManager.H +++ b/Source/ablastr/warn_manager/WarnManager.H @@ -75,14 +75,8 @@ namespace ablastr::warn_manager ~WarnManager() = default; /** - * \brief Returns the WarnManager instance of the current AMReX - * initialize/finalize cycle, creating it on first use. - * - * The instance is destroyed by the next amrex::Finalize, so the - * returned reference must not be stored beyond it. - * - * @return the instance of the WarnManager class - */ + * \brief Return the static WarnManager singleton + */ static WarnManager& GetInstance(); /** @@ -225,7 +219,6 @@ namespace ablastr::warn_manager /** * \brief Helper function to abbreviate the call to get a WarnManager instance - * (the returned reference must not be stored beyond the next amrex::Finalize) * * @return the instance of the WarnManager class */