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#pragma once
30
31#include "external/ThreadSafetyAnalysis.h"
32#include <assert.h>
33#include <errno.h>
34#include <float.h>
35#include <glib.h>
36#include <pthread.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40
41#ifdef _DEBUG
42
43// copied from darktable.h so we don't need to include the header
44#include <sys/time.h>
45static inline double dt_pthread_get_wtime()
46{
47 struct timeval time;
48 gettimeofday(&time, NULL);
49 return time.tv_sec - 1290608000 + (1.0 / 1000000.0) * time.tv_usec;
50}
51
52
53#define TOPN 3
54typedef struct CAPABILITY("mutex") dt_pthread_mutex_t
55{
56 pthread_mutex_t mutex;
57 char name[256];
58 double time_locked;
59 double time_sum_wait;
60 double time_sum_locked;
61 char top_locked_name[TOPN][256];
62 double top_locked_sum[TOPN];
63 char top_wait_name[TOPN][256];
64 double top_wait_sum[TOPN];
65} CAPABILITY("mutex") dt_pthread_mutex_t;
66
67typedef struct dt_pthread_rwlock_t
68{
69 pthread_rwlock_t lock;
70 int cnt;
71 pthread_t writer;
72 int writer_depth;
73 char name[256];
75
76static inline int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
77{
78 const int ret = pthread_mutex_destroy(&(mutex->mutex));
79 assert(!ret);
80
81#if 0
82 printf("\n[mutex] stats for mutex `%s':\n", mutex->name);
83 printf("[mutex] total time locked: %.3f secs\n", mutex->time_sum_locked);
84 printf("[mutex] total wait time : %.3f secs\n", mutex->time_sum_wait);
85 printf("[mutex] top %d lockers :\n", TOPN);
86 for(int k=0; k<TOPN; k++) printf("[mutex] %.3f secs : `%s'\n", mutex->top_locked_sum[k],
87 mutex->top_locked_name[k]);
88 printf("[mutex] top %d waiters :\n", TOPN);
89 for(int k=0; k<TOPN; k++) printf("[mutex] %.3f secs : `%s'\n", mutex->top_wait_sum[k],
90 mutex->top_wait_name[k]);
91#endif
92
93 return ret;
94}
95
96#define dt_pthread_mutex_init(A, B) dt_pthread_mutex_init_with_caller(A, B, __FILE__, __LINE__, __FUNCTION__)
97static inline int dt_pthread_mutex_init_with_caller(dt_pthread_mutex_t *mutex,
98 const pthread_mutexattr_t *attr, const char *file,
99 const int line, const char *function)
100{
101 memset(mutex, 0x0, sizeof(dt_pthread_mutex_t));
102 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
103#if defined(__OpenBSD__)
104 if(attr == NULL)
105 {
106 pthread_mutexattr_t a;
107 pthread_mutexattr_init(&a);
108 pthread_mutexattr_settype(&a, PTHREAD_MUTEX_NORMAL);
109 const int ret = pthread_mutex_init(&(mutex->mutex), &a);
110 pthread_mutexattr_destroy(&a);
111 return ret;
112 }
113#endif
114 const int ret = pthread_mutex_init(&(mutex->mutex), attr);
115 assert(!ret);
116 return ret;
117}
118
119#define dt_pthread_mutex_lock(A) dt_pthread_mutex_lock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
120static inline int dt_pthread_mutex_lock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
121 const int line, const char *function)
122 ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
123{
124 const double t0 = dt_pthread_get_wtime();
125 const int ret = pthread_mutex_lock(&(mutex->mutex));
126 assert(!ret);
127 mutex->time_locked = dt_pthread_get_wtime();
128 double wait = mutex->time_locked - t0;
129 mutex->time_sum_wait += wait;
130 char *name = mutex->name;
131 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
132 // TODO: have a -d thread option
133 //fprintf(stdout, "Thread lock %s acquired\n", mutex->name);
134 int min_wait_slot = 0;
135 for(int k = 0; k < TOPN; k++)
136 {
137 if(mutex->top_wait_sum[k] < mutex->top_wait_sum[min_wait_slot]) min_wait_slot = k;
138 if(!strncmp(name, mutex->top_wait_name[k], 256))
139 {
140 mutex->top_wait_sum[k] += wait;
141 return ret;
142 }
143 }
144 g_strlcpy(mutex->top_wait_name[min_wait_slot], name, sizeof(mutex->top_wait_name[min_wait_slot]));
145 mutex->top_wait_sum[min_wait_slot] = wait;
146 return ret;
147}
148
149#define dt_pthread_mutex_trylock(A) dt_pthread_mutex_trylock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
150static inline int dt_pthread_mutex_trylock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
151 const int line, const char *function)
152 TRY_ACQUIRE(0, mutex)
153{
154 const double t0 = dt_pthread_get_wtime();
155 const int ret = pthread_mutex_trylock(&(mutex->mutex));
156 assert(!ret || (ret == EBUSY));
157 if(ret) return ret;
158 mutex->time_locked = dt_pthread_get_wtime();
159 double wait = mutex->time_locked - t0;
160 mutex->time_sum_wait += wait;
161 char *name = mutex->name;
162 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
163 int min_wait_slot = 0;
164 for(int k = 0; k < TOPN; k++)
165 {
166 if(mutex->top_wait_sum[k] < mutex->top_wait_sum[min_wait_slot]) min_wait_slot = k;
167 if(!strncmp(name, mutex->top_wait_name[k], 256))
168 {
169 mutex->top_wait_sum[k] += wait;
170 return ret;
171 }
172 }
173 g_strlcpy(mutex->top_wait_name[min_wait_slot], name, sizeof(mutex->top_wait_name[min_wait_slot]));
174 mutex->top_wait_sum[min_wait_slot] = wait;
175 return ret;
176}
177
178#define dt_pthread_mutex_unlock(A) dt_pthread_mutex_unlock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
179static inline int dt_pthread_mutex_unlock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
180 const int line, const char *function)
181 RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
182{
183 const double t0 = dt_pthread_get_wtime();
184 const double locked = t0 - mutex->time_locked;
185 mutex->time_sum_locked += locked;
186
187 char *name = mutex->name;
188 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
189 // TODO: have a -d thread debug arg
190 //fprintf(stdout, "Thread lock %s released\n", mutex->name);
191 int min_locked_slot = 0;
192 for(int k = 0; k < TOPN; k++)
193 {
194 if(mutex->top_locked_sum[k] < mutex->top_locked_sum[min_locked_slot]) min_locked_slot = k;
195 if(!strncmp(name, mutex->top_locked_name[k], 256))
196 {
197 mutex->top_locked_sum[k] += locked;
198 min_locked_slot = -1;
199 break;
200 }
201 }
202 if(min_locked_slot >= 0)
203 {
204 g_strlcpy(mutex->top_locked_name[min_locked_slot], name, sizeof(mutex->top_locked_name[min_locked_slot]));
205 mutex->top_locked_sum[min_locked_slot] = locked;
206 }
207
208 // need to unlock last, to shield our internal data.
209 const int ret = pthread_mutex_unlock(&(mutex->mutex));
210 assert(!ret);
211 return ret;
212}
213
214static inline int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
215{
216 return pthread_cond_wait(cond, &(mutex->mutex));
217}
218
219
220static inline int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock,
221 const pthread_rwlockattr_t *attr)
222{
223 memset(lock, 0, sizeof(dt_pthread_rwlock_t));
224 lock->cnt = 0;
225 lock->writer_depth = 0;
226 const int res = pthread_rwlock_init(&lock->lock, attr);
227 assert(!res);
228 return res;
229}
230
231static inline int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
232{
233 if(lock->writer_depth != 0)
234 fprintf(stderr, "ERROR: destroying rwlock still owned by writer (depth=%d cnt=%d last=%s)\n",
235 lock->writer_depth, lock->cnt, lock->name);
236 assert(lock->writer_depth == 0);
237 snprintf(lock->name, sizeof(lock->name), "destroyed with cnt %d", lock->cnt);
238 const int res = pthread_rwlock_destroy(&lock->lock);
239 assert(!res);
240 return res;
241}
242
243static inline pthread_t dt_pthread_rwlock_get_writer(dt_pthread_rwlock_t *lock)
244{
245 return lock->writer;
246}
247
248#define dt_pthread_rwlock_unlock(A) dt_pthread_rwlock_unlock_with_caller(A, __FILE__, __LINE__)
249static inline int dt_pthread_rwlock_unlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
250{
251 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth > 1)
252 {
253 __sync_fetch_and_sub(&(rwlock->cnt), 1);
254 rwlock->writer_depth--;
255 assert(rwlock->writer_depth >= 1);
256 assert(rwlock->cnt >= 0);
257 snprintf(rwlock->name, sizeof(rwlock->name), "nu:%s:%d", file, line);
258 fprintf(stdout, "Warning: thread lock owned by the same thread is unlocked again by %s\n", rwlock->name);
259 return 0;
260 }
261
262 const gboolean writer_was_self = pthread_equal(rwlock->writer, pthread_self());
263 const int res = pthread_rwlock_unlock(&rwlock->lock);
264 assert(!res);
265 __sync_fetch_and_sub(&(rwlock->cnt), 1);
266 assert(rwlock->cnt >= 0);
267 __sync_bool_compare_and_swap(&(rwlock->writer), pthread_self(), 0);
268 if(writer_was_self) rwlock->writer_depth = 0;
269 if(!res) snprintf(rwlock->name, sizeof(rwlock->name), "u:%s:%d", file, line);
270 return res;
271}
272
273#define dt_pthread_rwlock_rdlock(A) dt_pthread_rwlock_rdlock_with_caller(A, __FILE__, __LINE__)
274static inline int dt_pthread_rwlock_rdlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
275{
276 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
277 {
278 __sync_fetch_and_add(&(rwlock->cnt), 1);
279 rwlock->writer_depth++;
280 snprintf(rwlock->name, sizeof(rwlock->name), "wr:%s:%d", file, line);
281 fprintf(stdout, "Warning: thread lock owned by the same thread is locked again by %s\n", rwlock->name);
282 return 0;
283 }
284
285 const int res = pthread_rwlock_rdlock(&rwlock->lock);
286 assert(!res);
287 __sync_fetch_and_add(&(rwlock->cnt), 1);
288 if(!res)
289 snprintf(rwlock->name, sizeof(rwlock->name), "r:%s:%d", file, line);
290 return res;
291}
292#define dt_pthread_rwlock_wrlock(A) dt_pthread_rwlock_wrlock_with_caller(A, __FILE__, __LINE__)
293static inline int dt_pthread_rwlock_wrlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
294{
295 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
296 {
297 __sync_fetch_and_add(&(rwlock->cnt), 1);
298 rwlock->writer_depth++;
299 snprintf(rwlock->name, sizeof(rwlock->name), "ww:%s:%d", file, line);
300 fprintf(stdout, "Warning: thread lock owned by the same thread is locked again by %s\n", rwlock->name);
301 return 0;
302 }
303
304 const int res = pthread_rwlock_wrlock(&rwlock->lock);
305 assert(!res);
306 __sync_fetch_and_add(&(rwlock->cnt), 1);
307 if(!res)
308 {
309 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
310 rwlock->writer_depth = 1;
311 snprintf(rwlock->name, sizeof(rwlock->name), "w:%s:%d", file, line);
312 }
313 return res;
314}
315#define dt_pthread_rwlock_tryrdlock(A) dt_pthread_rwlock_tryrdlock_with_caller(A, __FILE__, __LINE__)
316static inline int dt_pthread_rwlock_tryrdlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
317{
318 // IMPORTANT: try* locks are often used as "is it locked by anyone?" probes (e.g. caches).
319 // Returning success when the current thread already holds the write lock breaks that contract
320 // and can lead to freeing data still in use. Treat it as busy.
321 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
322
323 const int res = pthread_rwlock_tryrdlock(&rwlock->lock);
324 assert(!res || (res == EBUSY));
325 if(!res)
326 {
327 __sync_fetch_and_add(&(rwlock->cnt), 1);
328 snprintf(rwlock->name, sizeof(rwlock->name), "tr:%s:%d", file, line);
329 }
330 return res;
331}
332#define dt_pthread_rwlock_trywrlock(A) dt_pthread_rwlock_trywrlock_with_caller(A, __FILE__, __LINE__)
333static inline int dt_pthread_rwlock_trywrlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
334{
335 // See dt_pthread_rwlock_tryrdlock_with_caller(): don't make trywrlock succeed recursively.
336 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
337
338 const int res = pthread_rwlock_trywrlock(&rwlock->lock);
339 assert(!res || (res == EBUSY));
340 if(!res)
341 {
342 __sync_fetch_and_add(&(rwlock->cnt), 1);
343 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
344 rwlock->writer_depth = 1;
345 snprintf(rwlock->name, sizeof(rwlock->name), "tw:%s:%d", file, line);
346 }
347 return res;
348}
349
350#undef TOPN
351#else
352
353typedef struct CAPABILITY("mutex") dt_pthread_mutex_t
354{
355 pthread_mutex_t mutex;
356} CAPABILITY("mutex") dt_pthread_mutex_t;
357
358// *please* do use these;
359static inline int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
360{
361 return pthread_mutex_init(&mutex->mutex, mutexattr);
362};
363
364static inline int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
365{
366 return pthread_mutex_lock(&mutex->mutex);
367};
368
369static inline int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0, mutex)
370{
371 return pthread_mutex_trylock(&mutex->mutex);
372};
373
374static inline int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
375{
376 return pthread_mutex_unlock(&mutex->mutex);
377};
378
379static inline int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
380{
381 return pthread_mutex_destroy(&mutex->mutex);
382};
383
384static inline int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
385{
386 return pthread_cond_wait(cond, &mutex->mutex);
387};
388
389#define dt_pthread_rwlock_t pthread_rwlock_t
390#define dt_pthread_rwlock_init pthread_rwlock_init
391#define dt_pthread_rwlock_destroy pthread_rwlock_destroy
392#define dt_pthread_rwlock_unlock pthread_rwlock_unlock
393#define dt_pthread_rwlock_rdlock pthread_rwlock_rdlock
394#define dt_pthread_rwlock_wrlock pthread_rwlock_wrlock
395#define dt_pthread_rwlock_tryrdlock pthread_rwlock_tryrdlock
396#define dt_pthread_rwlock_trywrlock pthread_rwlock_trywrlock
397
398#define dt_pthread_rwlock_rdlock_with_caller(A,B,C) pthread_rwlock_rdlock(A)
399#define dt_pthread_rwlock_wrlock_with_caller(A,B,C) pthread_rwlock_wrlock(A)
400#define dt_pthread_rwlock_tryrdlock_with_caller(A,B,C) pthread_rwlock_tryrdlock(A)
401#define dt_pthread_rwlock_trywrlock_with_caller(A,B,C) pthread_rwlock_trywrlock(A)
402
403#endif
404
405// if at all possible, do NOT use.
406static inline int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
407{
408 return pthread_mutex_lock(&mutex->mutex);
409};
410
411static inline int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
412{
413 return pthread_mutex_trylock(&mutex->mutex);
414};
415
416static inline int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
417{
418 return pthread_mutex_unlock(&mutex->mutex);
419};
420
421int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime);
422
423void dt_pthread_setname(const char *name);
424
425// clang-format off
426// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
427// vim: shiftwidth=2 expandtab tabstop=2 cindent
428// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
429// clang-format on
@ ACQUIRE
Definition colormapping.c:94
const float a
Definition colorspaces_inline_conversions.h:1292
char * name
Definition common/metadata.c:61
static int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:406
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:42
struct CAPABILITY("mutex") dt_pthread_mutex_t
Definition dtpthread.h:353
#define dt_pthread_rwlock_destroy
Definition dtpthread.h:391
static int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:411
#define dt_pthread_rwlock_wrlock_with_caller(A, B, C)
Definition dtpthread.h:399
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:416
#define dt_pthread_rwlock_trywrlock_with_caller(A, B, C)
Definition dtpthread.h:401
#define dt_pthread_rwlock_tryrdlock_with_caller(A, B, C)
Definition dtpthread.h:400
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
static int mutex
Definition dtpthread.h:370
#define dt_pthread_rwlock_t
Definition dtpthread.h:389
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:379
void dt_pthread_setname(const char *name)
Definition dtpthread.c:113
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Definition dtpthread.h:384
#define dt_pthread_rwlock_init
Definition dtpthread.h:390
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
#define dt_pthread_rwlock_rdlock_with_caller(A, B, C)
Definition dtpthread.h:398
k
Definition derive_filmic_v6_gamut_mapping.py:67
ret
Definition update_modelines.py:120