Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dtpthread.h
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011, 2014 johannes hanika.
4 Copyright (C) 2011, 2014, 2016-2017, 2020 Tobias Ellinghaus.
5 Copyright (C) 2012 Jérémy Rosen.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2013 Stuart Henderson.
8 Copyright (C) 2014-2017 Roman Lebedev.
9 Copyright (C) 2017 Christian Tellefsen.
10 Copyright (C) 2020 Pascal Obry.
11 Copyright (C) 2020 Ralf Brown.
12 Copyright (C) 2022 Martin Bařinka.
13 Copyright (C) 2023-2025 Aurélien PIERRE.
14
15 darktable is free software: you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 3 of the License, or
18 (at your option) any later version.
19
20 darktable is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with darktable. If not, see <http://www.gnu.org/licenses/>.
27*/
28
29#ifndef DT_SYSTEM_DTPTHREAD_H
30#define DT_SYSTEM_DTPTHREAD_H
31
32#include "external/ThreadSafetyAnalysis.h"
33#include <assert.h>
34#include <errno.h>
35#include <float.h>
36#include <glib.h>
37#include <pthread.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <time.h>
42
43/* One implementation, always.
44 *
45 * There used to be a second one behind #ifdef _DEBUG: ~320 lines carrying per-lock names,
46 * acquisition timing, a top-3 contention table and printf warnings. It is gone, for the
47 * reason its own neighbours documented. caches/cache.c still carries the epitaph of the
48 * last time this file had two arms:
49 *
50 * "There used to be an #ifdef _DEBUG split here ... the non-_DEBUG arm stopped compiling
51 * -- and nobody found out, because this whole block only exists under AddressSanitizer
52 * and nothing in CI builds with it."
53 *
54 * A second implementation that no test exercises does not verify anything; it only gives
55 * the analysers a second, unreachable body to reason about. All nine BLOCKER findings
56 * SonarCloud reported against this file were in that arm, and none of them described code
57 * that ships.
58 *
59 * What is kept is what earns its place at compile time or at run time:
60 *
61 * - the CAPABILITY/ACQUIRE/RELEASE annotations, which drive clang's -Wthread-safety
62 * (enabled in cmake/compiler-warnings.cmake). A bare pthread_mutex_t cannot carry
63 * them: the wrapper struct is what makes lock discipline checkable at all.
64 * - dt_pthread_rwlock_t's same-thread recursive-writer tracking, which is not
65 * instrumentation but a deadlock fix -- see the comment on the type below.
66 */
67
68
69typedef struct CAPABILITY("mutex") dt_pthread_mutex_t
70{
71 pthread_mutex_t mutex;
72} CAPABILITY("mutex") dt_pthread_mutex_t;
73
74// *please* do use these;
104static inline int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
105{
106 if(mutexattr) return pthread_mutex_init(&mutex->mutex, mutexattr);
107
108 pthread_mutexattr_t recursive;
109 int res = pthread_mutexattr_init(&recursive);
110 if(res) return res;
111 res = pthread_mutexattr_settype(&recursive, PTHREAD_MUTEX_RECURSIVE);
112 if(!res) res = pthread_mutex_init(&mutex->mutex, &recursive);
113 pthread_mutexattr_destroy(&recursive);
114 return res;
115};
116
118{
119 return pthread_mutex_lock(&mutex->mutex);
120};
121
122static inline int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0, mutex)
123{
124 return pthread_mutex_trylock(&mutex->mutex);
125};
126
127static inline int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
128{
129 return pthread_mutex_unlock(&mutex->mutex);
130};
131
132static inline int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
133{
134 return pthread_mutex_destroy(&mutex->mutex);
135};
136
149static inline int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
150{
151 return pthread_cond_wait(cond, &mutex->mutex);
152};
153
154// Same-thread recursive write-lock tracking.
155/* Annotated as a clang CAPABILITY so -Wthread-safety can check it. The point of doing so is
156 * GUARDED_BY on the DATA these locks protect: clang's analysis is declarative -- it proves
157 * that every access to an annotated field happens while the named lock is held -- which is a
158 * different and stronger thing than symbolic execution guessing at lock state. It also does
159 * not care that our writers are recursive, because it is not counting.
160 *
161 * Each accessor carries NO_THREAD_SAFETY_ANALYSIS on its own body: the body deliberately
162 * acquires or releases without a matching partner, which is exactly what the attribute is
163 * for. The ACQUIRE/RELEASE annotation is what callers are checked against.
164 */
165// A thread that already holds the write lock cannot race itself: no other thread can be
166// touching the protected data while the write lock is held, so letting that same thread
167// re-enter (as reader or writer) is safe for data validity. Without this, glibc's default
168// PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP policy self-deadlocks such a thread as soon as
169// a second thread is queued waiting for the write lock (new readers, including from the
170// writer's own thread, are blocked once a writer is waiting).
171typedef struct CAPABILITY("rwlock") dt_pthread_rwlock_t
172{
173 pthread_rwlock_t lock;
174 pthread_t writer;
175 int writer_depth;
176 // Temporary diagnostic hook (find_history_mutex_blocker): NULL for every lock except the
177 // ones explicitly named via dt_pthread_rwlock_set_name(). Zero overhead for unnamed locks
178 // (one pointer compare). last_holder_tid is best-effort/racy by design -- it's a debug hint
179 // for "who was probably holding this right before I blocked", not a correctness primitive.
180 const char *name;
181 long last_holder_tid;
182 gboolean last_holder_was_writer;
183 // Tracks the reader holding this lock the longest, to tell apart "the writer is queued
184 // behind a reader that grabbed it a moment ago" from "some reader has been sitting on this
185 // for the whole wait" -- the single last_holder_tid above can't distinguish those, since it
186 // reflects whoever *most recently acquired*, not whoever has been holding it longest.
187 int active_reader_count;
188 double oldest_active_reader_since;
189 long oldest_active_reader_tid;
190} CAPABILITY("rwlock") dt_pthread_rwlock_t;
191
192static inline int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
193{
194 lock->writer = 0;
195 lock->writer_depth = 0;
196 lock->name = NULL;
197 lock->last_holder_tid = 0;
198 lock->last_holder_was_writer = FALSE;
199 lock->active_reader_count = 0;
200 lock->oldest_active_reader_since = 0.0;
201 lock->oldest_active_reader_tid = 0;
202 return pthread_rwlock_init(&lock->lock, attr);
203}
204
205// Opt-in: call once after dt_pthread_rwlock_init() to enable wait-time diagnostics on this
206// specific lock instance. Temporary, for find_history_mutex_blocker -- remove once resolved.
207static inline void dt_pthread_rwlock_set_name(dt_pthread_rwlock_t *lock, const char *name)
208{
209 lock->name = name;
210}
211
212static inline int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
213{
214 return pthread_rwlock_destroy(&lock->lock);
215}
216
217static inline int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
218{
219 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth > 1)
220 {
221 rwlock->writer_depth--;
222 return 0;
223 }
224 const gboolean writer_was_self = pthread_equal(rwlock->writer, pthread_self());
225 if(rwlock->name && !writer_was_self)
226 {
227 // A reader unlocking (writer_was_self is only true for a thread that took the write lock;
228 // recursive-writer-as-reader re-entry already returned above via writer_depth).
229 if(__sync_fetch_and_sub(&rwlock->active_reader_count, 1) == 1)
230 {
231 rwlock->oldest_active_reader_since = 0.0;
232 rwlock->oldest_active_reader_tid = 0;
233 }
234 }
235 const int res = pthread_rwlock_unlock(&rwlock->lock);
236 if(writer_was_self)
237 {
238 rwlock->writer_depth = 0;
239 __sync_bool_compare_and_swap(&(rwlock->writer), pthread_self(), 0);
240 }
241 return res;
242}
243
244// Diagnostic-only. This header is included too early in the chain (darktable.h includes it
245// before declaring dt_debug_thread_t/darktable_t/dt_print) for dt_print() to be callable
246// directly from the inline functions below -- so the actual logging is delegated to these two
247// non-inline helpers, implemented in dtpthread.c (which, being a .c file and not part of the
248// header cycle, can include darktable.h and call dt_print(DT_DEBUG_HISTORY, ...) there).
249// This makes the traces respect `-d history` like every other diagnostic instead of always
250// firing via a raw fprintf. Only ever called for locks opted in via dt_pthread_rwlock_set_name();
251// zero-cost (one pointer compare) for every other lock. Temporary, for
252// find_history_mutex_blocker -- remove once resolved.
253void _dt_pthread_rwlock_diag_log_rdlock(const char *name, unsigned long tid, double wait_ms,
254 unsigned long prev_holder, gboolean prev_was_writer);
255void _dt_pthread_rwlock_diag_log_wrlock(const char *name, unsigned long tid, double wait_ms,
256 unsigned long prev_holder, gboolean prev_was_writer,
257 int active_readers, unsigned long oldest_reader_tid,
258 double oldest_reader_age_ms);
259
260static inline double _dt_pthread_rwlock_diag_now(void)
261{
262 struct timespec ts;
263 clock_gettime(CLOCK_MONOTONIC, &ts);
264 return ts.tv_sec + ts.tv_nsec / 1e9;
265}
266
267static inline int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
268{
269 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
270 {
271 rwlock->writer_depth++;
272 return 0;
273 }
274 if(rwlock->name)
275 {
276 const double _start = _dt_pthread_rwlock_diag_now();
277 const long _prev_holder = rwlock->last_holder_tid;
278 const gboolean _prev_was_writer = rwlock->last_holder_was_writer;
279 const int res = pthread_rwlock_rdlock(&rwlock->lock);
280 const double _wait_ms = (_dt_pthread_rwlock_diag_now() - _start) * 1000.0;
281 if(_wait_ms > 1.0)
282 _dt_pthread_rwlock_diag_log_rdlock(rwlock->name, (unsigned long)pthread_self(), _wait_ms,
283 (unsigned long)_prev_holder, _prev_was_writer);
284 if(!res)
285 {
286 rwlock->last_holder_tid = (long)pthread_self();
287 rwlock->last_holder_was_writer = FALSE;
288 if(__sync_fetch_and_add(&rwlock->active_reader_count, 1) == 0)
289 {
290 rwlock->oldest_active_reader_since = _dt_pthread_rwlock_diag_now();
291 rwlock->oldest_active_reader_tid = (long)pthread_self();
292 }
293 }
294 return res;
295 }
296 return pthread_rwlock_rdlock(&rwlock->lock);
297}
298
300{
301 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
302 {
303 rwlock->writer_depth++;
304 return 0;
305 }
306 if(rwlock->name)
307 {
308 const double _start = _dt_pthread_rwlock_diag_now();
309 const long _prev_holder = rwlock->last_holder_tid;
310 const gboolean _prev_was_writer = rwlock->last_holder_was_writer;
311 const int _readers_now = rwlock->active_reader_count;
312 const long _oldest_reader_tid = rwlock->oldest_active_reader_tid;
313 const double _oldest_reader_age_ms
314 = _readers_now > 0 ? (_start - rwlock->oldest_active_reader_since) * 1000.0 : 0.0;
315 const int res = pthread_rwlock_wrlock(&rwlock->lock);
316 const double _wait_ms = (_dt_pthread_rwlock_diag_now() - _start) * 1000.0;
317 if(_wait_ms > 1.0)
318 _dt_pthread_rwlock_diag_log_wrlock(rwlock->name, (unsigned long)pthread_self(), _wait_ms,
319 (unsigned long)_prev_holder, _prev_was_writer, _readers_now,
320 (unsigned long)_oldest_reader_tid, _oldest_reader_age_ms);
321 if(!res)
322 {
323 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
324 rwlock->writer_depth = 1;
325 rwlock->last_holder_tid = (long)pthread_self();
326 rwlock->last_holder_was_writer = TRUE;
327 }
328 return res;
329 }
330 const int res = pthread_rwlock_wrlock(&rwlock->lock);
331 if(!res)
332 {
333 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
334 rwlock->writer_depth = 1;
335 }
336 return res;
337}
338
339static inline int dt_pthread_rwlock_tryrdlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE_SHARED(0, rwlock) NO_THREAD_SAFETY_ANALYSIS
340{
341 // Keep try* locks honest as "is it locked by anyone?" probes: do NOT report success just
342 // because the current thread already holds the write lock (see dt_pthread_rwlock_rdlock
343 // above for the blocking-call recursion, which is the case this is not).
344 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
345 return pthread_rwlock_tryrdlock(&rwlock->lock);
346}
347
348static inline int dt_pthread_rwlock_trywrlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE(0, rwlock) NO_THREAD_SAFETY_ANALYSIS
349{
350 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
351 const int res = pthread_rwlock_trywrlock(&rwlock->lock);
352 if(!res)
353 {
354 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
355 rwlock->writer_depth = 1;
356 }
357 return res;
358}
359
360#define dt_pthread_rwlock_rdlock_with_caller(A,B,C) dt_pthread_rwlock_rdlock(A)
361#define dt_pthread_rwlock_wrlock_with_caller(A,B,C) dt_pthread_rwlock_wrlock(A)
362#define dt_pthread_rwlock_tryrdlock_with_caller(A,B,C) dt_pthread_rwlock_tryrdlock(A)
363#define dt_pthread_rwlock_trywrlock_with_caller(A,B,C) dt_pthread_rwlock_trywrlock(A)
364
365
366// if at all possible, do NOT use.
367static inline int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
368{
369 return pthread_mutex_lock(&mutex->mutex);
370};
371
372static inline int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
373{
374 return pthread_mutex_trylock(&mutex->mutex);
375};
376
377static inline int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
378{
379 return pthread_mutex_unlock(&mutex->mutex);
380};
381
382int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime);
383
384void dt_pthread_setname(const char *name);
385
386#endif // DT_SYSTEM_DTPTHREAD_H
387
388// clang-format off
389// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
390// vim: shiftwidth=2 expandtab tabstop=2 cindent
391// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
392// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ ACQUIRE
static int rwlock NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:340
static int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:367
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:52
struct CAPABILITY("mutex") dt_pthread_mutex_t
Definition dtpthread.h:69
void _dt_pthread_rwlock_diag_log_rdlock(const char *name, unsigned long tid, double wait_ms, unsigned long prev_holder, gboolean prev_was_writer)
Definition dtpthread.c:128
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
return pthread_rwlock_tryrdlock & rwlock
Definition dtpthread.h:345
static int dt_pthread_rwlock_trywrlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE(0
static int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:372
static double _dt_pthread_rwlock_diag_now(void)
Definition dtpthread.h:260
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:377
static void dt_pthread_rwlock_set_name(dt_pthread_rwlock_t *lock, const char *name)
Definition dtpthread.h:207
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
void _dt_pthread_rwlock_diag_log_wrlock(const char *name, unsigned long tid, double wait_ms, unsigned long prev_holder, gboolean prev_was_writer, int active_readers, unsigned long oldest_reader_tid, double oldest_reader_age_ms)
Definition dtpthread.c:135
static int mutex
Definition dtpthread.h:123
static int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
Definition dtpthread.h:212
static int dt_pthread_rwlock_tryrdlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE_SHARED(0
static int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
void dt_pthread_setname(const char *name)
Definition dtpthread.c:147
static int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
Definition dtpthread.h:192
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Wait on cond, releasing mutex for the duration.
Definition dtpthread.h:149
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
const char * name
Definition pdf.h:90
dt_pthread_mutex_t lock
Definition supervisor.c:123