From 61c41a765bbd90fdaf6d7a292771ed947b9eae43 Mon Sep 17 00:00:00 2001 From: Philipp Lutz Date: Tue, 7 Apr 2026 11:15:29 +0200 Subject: [PATCH] Fix startup race condition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix rare starup race condition between OpenCL background job and `dt_wb_presets_init`. In darktable.c:1878-1887, the GUI startup path queues OpenCL detection as a background job on the system worker pool and then immediately continues to call `dt_wb_presets_init(NULL)`. `dt_opencl_init` (called by a worker thread) loads OpenCL vendor libraries (ICD loader, driver). These vendor libraries are known to call `setlocale()` internally. `setlocale()` in glibc: * Acquires `__libc_setlocale_lock` * Frees and replaces internal locale data structures (including cached locale strings) Meanwhile, the main thread is inside `dcigettext` → `guess_category_value` → `getenv("LANGUAGE")`. The glibc `dcigettext` code is not re-entrant with `setlocale()`: it caches locale state that can be freed mid-read when `setlocale()` runs concurrently, leading to a SIGSEGV. This does not happen in the non-GUI/CLI path (`init_gui=0`) because there `dt_opencl_init` is called synchronously before `dt_wb_presets_init`. Potential fixes: * Move `dt_wb_presets_init` (and `dt_noiseprofile_init`) to before the OpenCL background job is enqueued, so the JSON/gettext code finishes before any worker thread starts running the OCL job. In darktable.c around line 1876. The fix is confirmed clean for the non-GUI path since it already calls `dt_opencl_init` synchronously — the proposed reorder makes both paths consistent. * Alternatively, a more targeted fix is to call `setlocale(LC_ALL, "")` once and freeze it (or save/restore it around `dt_opencl_init`) before JSON parsing begins, but reordering the init sequence is cleaner and less invasive. --- src/common/darktable.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/common/darktable.c b/src/common/darktable.c index 12fba89b069b..3e1e3cd162db 100644 --- a/src/common/darktable.c +++ b/src/common/darktable.c @@ -1873,23 +1873,25 @@ int dt_init(int argc, heif_init(NULL); #endif + dt_splash_screen_set_progress(_("initializing WB presets")); + dt_wb_presets_init(NULL); + + // Do locale-sensitive init BEFORE starting any background worker jobs + dt_splash_screen_set_progress(_("loading noise profiles")); + darktable.noiseprofile_parser = dt_noiseprofile_init(noiseprofiles_from_command); + dt_splash_screen_set_progress(_("starting OpenCL")); darktable.opencl = (dt_opencl_t *)calloc(1, sizeof(dt_opencl_t)); + darktable.points = (dt_points_t *)calloc(1, sizeof(dt_points_t)); + dt_points_init(darktable.points, dt_get_num_threads()); + + // Only then kick off the OpenCL background job if(init_gui) dt_control_add_job(DT_JOB_QUEUE_SYSTEM_BG, _detect_opencl_job_create(exclude_opencl)); else dt_opencl_init(darktable.opencl, exclude_opencl, print_statistics); - darktable.points = (dt_points_t *)calloc(1, sizeof(dt_points_t)); - dt_points_init(darktable.points, dt_get_num_threads()); - - dt_wb_presets_init(NULL); - - dt_splash_screen_set_progress(_("loading noise profiles")); - darktable.noiseprofile_parser = dt_noiseprofile_init(noiseprofiles_from_command); - - // must come before mipmap_cache, because that one will need to access - // image dimensions stored in here: + // must come before mipmap_cache, because that one will need to access image dimensions stored in here: dt_image_cache_init(); dt_mipmap_cache_init();