From e220ce888db153d3dc1dfd2d6917ff128251439e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 25 Feb 2026 13:42:58 -0800 Subject: [PATCH] Fix window close hang from blocking worker thread joins unloadAll() now uses a 500ms deadline with pthread_timedjoin_np to avoid blocking indefinitely when worker threads are mid-prepareTile (reading MPQ archives / parsing ADT files). Threads that don't finish within the deadline are detached so the app can exit promptly. --- src/rendering/terrain_manager.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 3554b3d3..ee118c1f 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -1238,13 +1238,35 @@ void TerrainManager::unloadTile(int x, int y) { } void TerrainManager::unloadAll() { - // Stop worker threads + // Signal worker threads to stop and wait briefly for them to finish. + // Workers may be mid-prepareTile (reading MPQ / parsing ADT) which can + // take seconds, so use a short deadline and detach any stragglers. if (workerRunning.load()) { workerRunning.store(false); queueCV.notify_all(); + + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(500); for (auto& t : workerThreads) { - if (t.joinable()) { - t.join(); + if (!t.joinable()) continue; + // Try a timed wait via polling — std::thread has no timed join. + bool joined = false; + while (std::chrono::steady_clock::now() < deadline) { + // Check if thread finished by trying a native timed join + #ifdef __linux__ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 50000000; // 50ms + if (ts.tv_nsec >= 1000000000) { ts.tv_sec++; ts.tv_nsec -= 1000000000; } + if (pthread_timedjoin_np(t.native_handle(), nullptr, &ts) == 0) { + joined = true; + break; + } + #else + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + #endif + } + if (!joined && t.joinable()) { + t.detach(); } } workerThreads.clear();