darktable-org / darktable-org/darktable

`retouch`: four display fields sit unlocked beside ten critical sections that guard others

Open
#22,067 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

scope: threading scope: UI
Dominant language
C
Stars
13.1k
Forks
1.4k
Avg merge
22h 14m
Merged PRs (30d)
198

Description

Found by code analysis (Claude + Codex, cross-checked) during the gui_data threading
audit, 2026-08-19. No known crash.

Description

retouch has ten critical sections (lines 1196, 1689, 1715, 1727, 1759, 2131, 3988,
4002, 4820, 4836). They guard the preview_auto_levels handshake and
displayed_wavelet_scale; the one at 2131 just wraps rt_shape_selection_changed().
Four other gui_data fields that both the GTK thread and the pipe touch sit right beside
all of that, unprotected — including display_wavelet_scale, whose near-identical
neighbour displayed_wavelet_scale is locked.

This is the clearest illustration in the audit of why "does this module lock?" is the
wrong question to ask of a module.

The four fields

field direction sites
display_wavelet_scale GTK writes, pipe reads GTK gesture callbacks write (1682, 2060, 2082) and change_image() resets (2403-2405); process() reads (3916, 3934, 3951, 3958, 4019), process_cl() reads (4735, 4757, 4777, 4854)
mask_display same same sites
suppress_mask same same sites
first_scale_visible reverse: pipe writes, GTK reads process() / process_cl() write it (3975, 4806); the rt_wdbar_draw() GTK callback reads it (1396)

first_scale_visible running in the opposite direction is worth noting on its own: it is
the direction dev-doc/GUI.md already documents as requiring a critical section.

Impact

Wrong pixels on screen for a frame (the three display fields), or a wavelet-bar decoration
drawn from a stale flag (first_scale_visible). None of the four is a pointer and none is
used as an index, so nothing here is memory-unsafe — but they are formally data races, and
process() reads the display fields at several points within one run, so a change landing
mid-call can make one part of the frame disagree with another.

Suggested fix

Wrap the writes and the reads in dt_iop_gui_enter_critical_section() /
dt_iop_gui_leave_critical_section(), snapshotting into locals at the top of process()
and process_cl() rather than re-reading the fields at each of their four or five use
sites. The mutex is not recursive, so the new sections must not nest inside the ten that
already exist.

change_image()'s reset of the three display fields needs the lock too.

Note for whoever audits this module next

The earlier drafts of the bulk report described retouch as "appearing to lock its
pipe-side access" on the strength of a dt_iop_gui_enter_critical_section grep. That was
wrong, here and in colorreconstruction, and it is recorded rather than quietly deleted
because it is the mistake this codebase invites.

Related

#22064 covers the same defect shape in eight
modules that never lock at all; #22058 covers
the other module that locks in the wrong places.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read dev-doc/GUI.md first, then locate retouch's process(), process_cl(), change_image(), and rt_wdbar_draw() entry points. Trace the four listed gui_data fields alongside the existing critical sections, ensuring the new sections do not nest. Done means all GTK and pipe accesses are protected and process() and process_cl() use consistent snapshots.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
desktop
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.