From 75a163926c4eb97215efd08440034575984cfabb Mon Sep 17 00:00:00 2001 From: Andy Bulka Date: Wed, 1 Apr 2026 13:34:53 +1100 Subject: [PATCH] fix: prevent gui->reset corruption from re-entrant GTK event processing During _dev_load_requested_image(), widget creation/destruction and pixelpipe rebuilding can trigger re-entrant GTK event processing, allowing other code paths to run their own ++/-- cycles on darktable.gui->reset. If these complete out of order, the final decrement takes gui->reset to -1, permanently disabling all IOP module GUI callbacks for the remainder of the session. Replace the relative ++/-- with a save/force-restore pattern so that gui->reset is always correctly restored regardless of re-entrant modifications. A diagnostic warning is logged when corruption is detected and corrected. Primarily observed on macOS (Quartz backend dispatches events more aggressively during widget operations) but the underlying code issue is platform-independent. --- src/views/darkroom.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/views/darkroom.c b/src/views/darkroom.c index 98bfb8fc9be6..3b66b56ea7fc 100644 --- a/src/views/darkroom.c +++ b/src/views/darkroom.c @@ -1221,7 +1221,16 @@ static gboolean _dev_load_requested_image(gpointer user_data) dt_dev_reload_image(dev, imgid); // make sure no signals propagate here: - ++darktable.gui->reset; + // IMPORTANT: We save and force-restore gui->reset rather than using + // ++/--. During the operations below (widget creation/destruction, + // pixelpipe rebuilding, etc.), GTK re-entrant event processing can + // cause other code paths to run their own ++/-- cycles on + // gui->reset. If any of those paths has an early-return between + // their ++ and --, or if they complete out of order, a simple -- + // at the end can result in gui->reset going negative, which + // permanently disables all IOP module GUI callbacks. + const int32_t saved_gui_reset = darktable.gui->reset; + darktable.gui->reset = 1; dt_pthread_mutex_lock(&dev->history_mutex); dt_dev_pixelpipe_cleanup_nodes(dev->full.pipe); @@ -1337,7 +1346,12 @@ static gboolean _dev_load_requested_image(gpointer user_data) are blocked due to implementation of dt_iop_request_focus so we do it now A double history entry is not generated. */ - --darktable.gui->reset; + if(darktable.gui->reset != 1) + dt_print(DT_DEBUG_ALWAYS, + "[darkroom] BUG AVERTED: gui->reset was %d (expected 1, saved was %d)" + " - re-entrant corruption detected and corrected, restoring to %d\n", + darktable.gui->reset, saved_gui_reset, saved_gui_reset); + darktable.gui->reset = saved_gui_reset; dt_dev_masks_list_change(dev);