From c0403818a4ded1ead7fd61ad5d26cb946c724419 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Mon, 2 Nov 2026 14:35:59 -0600 Subject: [PATCH] Don't warn when deferring an on-demand unload With on-demand load/unload, unloading while instances are alive is expected: onPluginDeletion() unloads once the last instance is gone. Only warn in that mode if the ClassLoader itself is destroyed, since the deferred unload can no longer happen. Make the noWarningOnLazyLoad tests actually check for warnings. Fixes #146 Signed-off-by: Michael Carroll --- src/class_loader.cpp | 14 +++++++++++++- test/unique_ptr_test.cpp | 15 +++++++++++++++ test/utest.cpp | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/class_loader.cpp b/src/class_loader.cpp index 76785bc..f90acd0 100644 --- a/src/class_loader.cpp +++ b/src/class_loader.cpp @@ -72,6 +72,16 @@ ClassLoader::~ClassLoader() "%s", "class_loader.ClassLoader: Destroying class loader, " "unloading associated library...\n"); + { + // The deferred on-demand unload in onPluginDeletion() can't happen once this loader is gone. + std::lock_guard lock(plugin_ref_count_mutex_); + if (plugin_ref_count_ > 0 && isOnDemandLoadUnloadEnabled()) { + CONSOLE_BRIDGE_logWarn( + "%s", + "class_loader.ClassLoader: SEVERE WARNING!!! " + "Destroying ClassLoader while objects created by it exist in the heap!"); + } + } unloadLibrary(); // TODO(mikaelarguedas): while(unloadLibrary() > 0){} ?? } @@ -123,7 +133,9 @@ int ClassLoader::unloadLibraryInternal(bool lock_plugin_ref_count) std::lock_guard load_ref_lock(load_ref_count_mutex_); try { - if (plugin_ref_count_ > 0) { + if (plugin_ref_count_ > 0 && isOnDemandLoadUnloadEnabled()) { + // Expected: onPluginDeletion() unloads the library once the last object is destroyed. + } else if (plugin_ref_count_ > 0) { CONSOLE_BRIDGE_logWarn( "%s", "class_loader.ClassLoader: SEVERE WARNING!!! " diff --git a/test/unique_ptr_test.cpp b/test/unique_ptr_test.cpp index 1aa4578..6910b2a 100644 --- a/test/unique_ptr_test.cpp +++ b/test/unique_ptr_test.cpp @@ -37,6 +37,7 @@ #include #include +#include "console_bridge/console.h" #include "gtest/gtest.h" #include "class_loader/class_loader.hpp" @@ -48,6 +49,18 @@ const std::string LIBRARY_1 = class_loader::systemLibraryFormat("class_loader_Te const std::string LIBRARY_2 = class_loader::systemLibraryFormat("class_loader_TestPlugins2"); // NOLINT const std::string LIBRARY_3 = class_loader::systemLibraryFormat("class_loader_TestPlugins3"); // NOLINT +// Collects console_bridge warnings and errors logged while in scope. +struct WarningCapture : console_bridge::OutputHandler +{ + WarningCapture() {console_bridge::useOutputHandler(this);} + ~WarningCapture() override {console_bridge::restorePreviousOutputHandler();} + void log(const std::string & text, console_bridge::LogLevel level, const char *, int) override + { + if (level >= console_bridge::CONSOLE_BRIDGE_LOG_WARN) {warnings += text;} + } + std::string warnings; +}; + using class_loader::ClassLoader; TEST(ClassLoaderUniquePtrTest, basicLoad) { @@ -297,6 +310,7 @@ TEST(MultiClassLoaderUniquePtrTest, nonLazyLoad) { } TEST(MultiClassLoaderUniquePtrTest, noWarningOnLazyLoad) { try { + WarningCapture capture; ClassLoader::UniquePtr cat, dog, rob; { class_loader::MultiLibraryClassLoader loader(true); @@ -310,6 +324,7 @@ TEST(MultiClassLoaderUniquePtrTest, noWarningOnLazyLoad) { cat->saySomething(); dog->saySomething(); rob->saySomething(); + EXPECT_EQ("", capture.warnings); } catch (class_loader::ClassLoaderException & e) { FAIL() << "ClassLoaderException: " << e.what() << "\n"; } diff --git a/test/utest.cpp b/test/utest.cpp index f4e13ee..1a9fd3b 100644 --- a/test/utest.cpp +++ b/test/utest.cpp @@ -44,6 +44,7 @@ #include "class_loader/class_loader.hpp" #include "class_loader/multi_library_class_loader.hpp" +#include "console_bridge/console.h" #include "gtest/gtest.h" #include "./base.hpp" @@ -57,6 +58,18 @@ const std::string LIBRARY_3 = class_loader::systemLibraryFormat("class_loader_Te const std::string GLOBAL_PLUGINS = // NOLINT class_loader::systemLibraryFormat("class_loader_TestGlobalPlugins"); +// Collects console_bridge warnings and errors logged while in scope. +struct WarningCapture : console_bridge::OutputHandler +{ + WarningCapture() {console_bridge::useOutputHandler(this);} + ~WarningCapture() override {console_bridge::restorePreviousOutputHandler();} + void log(const std::string & text, console_bridge::LogLevel level, const char *, int) override + { + if (level >= console_bridge::CONSOLE_BRIDGE_LOG_WARN) {warnings += text;} + } + std::string warnings; +}; + TEST(ClassLoaderTest, basicLoad) { try { class_loader::ClassLoader loader1(LIBRARY_1, false); @@ -419,6 +432,7 @@ TEST(MultiClassLoaderTest, nonLazyLoad) { } TEST(MultiClassLoaderTest, noWarningOnLazyLoad) { try { + WarningCapture capture; std::shared_ptr cat, dog, rob; { class_loader::MultiLibraryClassLoader loader(true); @@ -432,6 +446,7 @@ TEST(MultiClassLoaderTest, noWarningOnLazyLoad) { cat->saySomething(); dog->saySomething(); rob->saySomething(); + EXPECT_EQ("", capture.warnings); } catch (class_loader::ClassLoaderException & e) { FAIL() << "ClassLoaderException: " << e.what() << "\n"; }