From c8dde0985f7c5166c5fc07b95f640e3753b8e18c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 29 Mar 2026 21:36:06 -0700 Subject: [PATCH] fix: detached normal-map thread could deadlock shutdown on exception If generateNormalHeightMapCPU threw (e.g., bad_alloc), the pending counter was never decremented, causing shutdown() to block forever waiting for a count that would never reach zero. Added try-catch to guarantee the decrement. Also strengthened the increment from relaxed to acq_rel so shutdown()'s acquire load sees the count before the thread body begins executing. --- src/rendering/character_renderer.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index b0b0602a..aac98830 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -726,13 +726,23 @@ VkTexture* CharacterRenderer::loadTexture(const std::string& path) { uint32_t w = blpImage.width, h = blpImage.height; std::string ck = key; std::vector px(blpImage.data.begin(), blpImage.data.end()); - pendingNormalMapCount_.fetch_add(1, std::memory_order_relaxed); + // Use acq_rel so the increment is visible to shutdown()'s acquire load + // before the thread body begins (relaxed could delay visibility and cause + // shutdown() to see 0 and proceed while a thread is still running). + pendingNormalMapCount_.fetch_add(1, std::memory_order_acq_rel); auto* self = this; std::thread([self, ck = std::move(ck), px = std::move(px), w, h]() mutable { - auto result = generateNormalHeightMapCPU(std::move(ck), std::move(px), w, h); - { - std::lock_guard lock(self->normalMapResultsMutex_); - self->completedNormalMaps_.push_back(std::move(result)); + // try-catch guarantees the counter is decremented even if the compute + // throws (e.g., bad_alloc). Without this, shutdown() would deadlock + // waiting for a count that never reaches zero. + try { + auto result = generateNormalHeightMapCPU(std::move(ck), std::move(px), w, h); + { + std::lock_guard lock(self->normalMapResultsMutex_); + self->completedNormalMaps_.push_back(std::move(result)); + } + } catch (const std::exception& e) { + LOG_ERROR("Normal map generation failed: ", e.what()); } if (self->pendingNormalMapCount_.fetch_sub(1, std::memory_order_release) == 1) { self->normalMapDoneCV_.notify_one();