From 58e6aaf95986b6d6638765013786cd925bee634d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Tue, 8 Sep 2026 00:37:56 -0700 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=90=9B=20Fix=20lost-wakeup=20deadlock?= =?UTF-8?q?=20in=20remeshing=5Fim=20optimizer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimizer::optimizeOrientations() and optimizePositions() set the mOptimizeOrientations / mOptimizePositions predicate flags without holding mRes.mutex(), while the optimizer worker reads those same flags under that mutex in Optimizer::run(). This opens a lost-wakeup window. The worker evaluates its wait predicate while holding mRes.mutex(), then calls mCond.wait(), which only acquires the condition variable's internal mutex a few instructions later. If the main thread sets a flag and calls notify_all() inside that gap, the notification is delivered before the worker registers as a waiter and is lost. The worker then sleeps forever, and the main thread's Optimizer::wait() sleeps waiting for a flag the worker will never clear. Setting the flags while holding mRes.mutex() closes the window, since the worker holds that same mutex continuously from its predicate check until cv_any::wait() releases it. ThreadSanitizer reports the race between Optimizer::run() and Optimizer::optimizeOrientations() on an unfixed build and reports none after this change. ordered_lock is a non-recursive ticket lock and the flag setters take no lock themselves, so holding the mutex across these calls introduces no self-deadlock. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- modules/remeshing_im/src/remesh.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/remeshing_im/src/remesh.cpp b/modules/remeshing_im/src/remesh.cpp index edb3ed7c..79bcbc5b 100644 --- a/modules/remeshing_im/src/remesh.cpp +++ b/modules/remeshing_im/src/remesh.cpp @@ -38,6 +38,7 @@ #include #include +#include #include namespace lagrange::remeshing_im { @@ -193,14 +194,22 @@ SurfaceMesh remesh(SurfaceMesh& mesh, const Remesh optimizer.setPoSy(posy); optimizer.setExtrinsic(options.extrinsic); - optimizer.optimizeOrientations(-1); + // The optimizer worker waits on a condition variable guarded by the hierarchy mutex, but its + // flag setters are unsynchronized. Setting them under the mutex avoids a lost-wakeup deadlock. + { + std::lock_guard lock(mRes.mutex()); + optimizer.optimizeOrientations(-1); + } optimizer.notify(); optimizer.wait(); std::map sing; compute_orientation_singularities(mRes, sing, options.extrinsic, rosy); - optimizer.optimizePositions(-1); + { + std::lock_guard lock(mRes.mutex()); + optimizer.optimizePositions(-1); + } optimizer.notify(); optimizer.wait(); From 08f50be2d35ef69c0267c6a6ae31ff6ac6012319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Fri, 11 Sep 2026 13:44:06 -0700 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=8C=20Use=20upstream=20instant-mes?= =?UTF-8?q?hes=20deadlock=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cmake/recipes/external/instant-meshes-core.cmake | 2 +- modules/remeshing_im/src/remesh.cpp | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/cmake/recipes/external/instant-meshes-core.cmake b/cmake/recipes/external/instant-meshes-core.cmake index 3b651718..a0502a43 100644 --- a/cmake/recipes/external/instant-meshes-core.cmake +++ b/cmake/recipes/external/instant-meshes-core.cmake @@ -19,7 +19,7 @@ include(CPM) CPMAddPackage( NAME instant-meshes-core GITHUB_REPOSITORY qnzhou/instant-meshes-core - GIT_TAG 8c87f12bec4b98ce29febcf5dd63ebb90e957104 + GIT_TAG 632605af06eae75c9aaa6a61a0e551c64de02b71 ) add_library(instant-meshes-core::instant-meshes-core ALIAS instant-meshes-core) diff --git a/modules/remeshing_im/src/remesh.cpp b/modules/remeshing_im/src/remesh.cpp index 79bcbc5b..edb3ed7c 100644 --- a/modules/remeshing_im/src/remesh.cpp +++ b/modules/remeshing_im/src/remesh.cpp @@ -38,7 +38,6 @@ #include #include -#include #include namespace lagrange::remeshing_im { @@ -194,22 +193,14 @@ SurfaceMesh remesh(SurfaceMesh& mesh, const Remesh optimizer.setPoSy(posy); optimizer.setExtrinsic(options.extrinsic); - // The optimizer worker waits on a condition variable guarded by the hierarchy mutex, but its - // flag setters are unsynchronized. Setting them under the mutex avoids a lost-wakeup deadlock. - { - std::lock_guard lock(mRes.mutex()); - optimizer.optimizeOrientations(-1); - } + optimizer.optimizeOrientations(-1); optimizer.notify(); optimizer.wait(); std::map sing; compute_orientation_singularities(mRes, sing, options.extrinsic, rosy); - { - std::lock_guard lock(mRes.mutex()); - optimizer.optimizePositions(-1); - } + optimizer.optimizePositions(-1); optimizer.notify(); optimizer.wait();