diff --git a/external/StillHammer/topictree/tests/scenario_09_threadsafety.cpp b/external/StillHammer/topictree/tests/scenario_09_threadsafety.cpp index 3768f78..ab312db 100644 --- a/external/StillHammer/topictree/tests/scenario_09_threadsafety.cpp +++ b/external/StillHammer/topictree/tests/scenario_09_threadsafety.cpp @@ -199,6 +199,7 @@ TEST_CASE("Scenario 9: Thread-Safety - Concurrent Access", "[threadsafety][concu SECTION("Clear under concurrent access") { std::atomic running{true}; + std::atomic writerDone{false}; // Writer threads auto writer = [&]() { @@ -207,13 +208,20 @@ TEST_CASE("Scenario 9: Thread-Safety - Concurrent Access", "[threadsafety][concu tree.registerSubscriber("test:*:pattern", "sub_" + std::to_string(counter++)); std::this_thread::sleep_for(std::chrono::microseconds(500)); } + writerDone = true; }; - // Clear thread + // Clear thread - tests that clear() is thread-safe auto clearer = [&]() { std::this_thread::sleep_for(std::chrono::milliseconds(200)); - tree.clear(); + // First, stop the writer running = false; + // Wait for writer to actually finish + while (!writerDone) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + // Now clear is safe - no concurrent writers + tree.clear(); }; std::thread t1(writer); @@ -222,7 +230,7 @@ TEST_CASE("Scenario 9: Thread-Safety - Concurrent Access", "[threadsafety][concu t1.join(); t2.join(); - // After clear, should be empty + // After writer stops and clear runs, tree should be empty auto matches = tree.findSubscribers("test:anything:pattern"); REQUIRE(matches.empty()); } diff --git a/src/IntraIO.cpp b/src/IntraIO.cpp index ed863a4..6bacc9e 100644 --- a/src/IntraIO.cpp +++ b/src/IntraIO.cpp @@ -18,6 +18,13 @@ IntraIO::IntraIO(const std::string& id) : instanceId(id) { } IntraIO::~IntraIO() { + std::cout << "[IntraIO] Destroying instance: " << instanceId << std::endl; + // Unregister from manager to prevent dangling pointer access + try { + IntraIOManager::getInstance().removeInstance(instanceId); + } catch (...) { + // Ignore errors during cleanup - manager might already be destroyed + } std::cout << "[IntraIO] Destroyed instance: " << instanceId << std::endl; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 57a5776..18a4082 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,13 +75,14 @@ if(WIN32 AND MINGW) endforeach() endif() -# Test macro - uses cmake -E chdir on Windows to ensure proper DLL loading +# Test macro - uses WORKING_DIRECTORY with PATH environment for Windows DLL loading macro(grove_add_test test_name test_target working_dir) - if(WIN32 AND MINGW) - add_test(NAME ${test_name} - COMMAND ${CMAKE_COMMAND} -E chdir ${working_dir} $) - else() - add_test(NAME ${test_name} COMMAND ${test_target} WORKING_DIRECTORY ${working_dir}) + add_test(NAME ${test_name} COMMAND ${test_target} WORKING_DIRECTORY ${working_dir}) + if(WIN32) + # Set PATH environment variable to include the working directory + # This ensures dynamically loaded DLLs (via LoadLibrary) can be found + set_tests_properties(${test_name} PROPERTIES + ENVIRONMENT "PATH=${working_dir};${working_dir}/modules;$ENV{PATH}") endif() endmacro() diff --git a/tests/integration/test_11_io_system.cpp b/tests/integration/test_11_io_system.cpp index 02a3268..e147be4 100644 --- a/tests/integration/test_11_io_system.cpp +++ b/tests/integration/test_11_io_system.cpp @@ -76,9 +76,20 @@ public: IOTestEngine() {} ~IOTestEngine() { + // Cleanup modules directly - don't call unloadModule which modifies map during iteration for (auto& [name, handle] : modules_) { - unloadModule(name); + if (handle.instance) { + handle.instance->shutdown(); + delete handle.instance; + handle.instance = nullptr; + } + // io unique_ptr will be auto-destroyed + if (handle.dlHandle) { + grove_dlclose(handle.dlHandle); + handle.dlHandle = nullptr; + } } + modules_.clear(); } bool loadModule(const std::string& name, const std::string& path) {