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#include <time.h>
41
42#ifdef _DEBUG
43
44// copied from darktable.h so we don't need to include the header
45#include <sys/time.h>
46static inline double dt_pthread_get_wtime()
47{
48 struct timeval time;
49 gettimeofday(&time, NULL);
50 return time.tv_sec - 1290608000 + (1.0 / 1000000.0) * time.tv_usec;
51}
52
53
54#define TOPN 3
55typedef struct CAPABILITY("mutex") dt_pthread_mutex_t
56{
57 pthread_mutex_t mutex;
58 char name[256];
59 double time_locked;
60 double time_sum_wait;
61 double time_sum_locked;
62 char top_locked_name[TOPN][256];
63 double top_locked_sum[TOPN];
64 char top_wait_name[TOPN][256];
65 double top_wait_sum[TOPN];
66} CAPABILITY("mutex") dt_pthread_mutex_t;
67
68typedef struct dt_pthread_rwlock_t
69{
70 pthread_rwlock_t lock;
71 int cnt;
72 pthread_t writer;
73 int writer_depth;
74 char name[256];
76
77static inline int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
78{
79 const int ret = pthread_mutex_destroy(&(mutex->mutex));
80 assert(!ret);
81
82#if 0
83 printf("\n[mutex] stats for mutex `%s':\n", mutex->name);
84 printf("[mutex] total time locked: %.3f secs\n", mutex->time_sum_locked);
85 printf("[mutex] total wait time : %.3f secs\n", mutex->time_sum_wait);
86 printf("[mutex] top %d lockers :\n", TOPN);
87 for(int k=0; k<TOPN; k++) printf("[mutex] %.3f secs : `%s'\n", mutex->top_locked_sum[k],
88 mutex->top_locked_name[k]);
89 printf("[mutex] top %d waiters :\n", TOPN);
90 for(int k=0; k<TOPN; k++) printf("[mutex] %.3f secs : `%s'\n", mutex->top_wait_sum[k],
91 mutex->top_wait_name[k]);
92#endif
93
94 return ret;
95}
96
97#define dt_pthread_mutex_init(A, B) dt_pthread_mutex_init_with_caller(A, B, __FILE__, __LINE__, __FUNCTION__)
98static inline int dt_pthread_mutex_init_with_caller(dt_pthread_mutex_t *mutex,
99 const pthread_mutexattr_t *attr, const char *file,
100 const int line, const char *function)
101{
102 memset(mutex, 0x0, sizeof(dt_pthread_mutex_t));
103 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
104#if defined(__OpenBSD__)
105 if(IS_NULL_PTR(attr))
106 {
107 pthread_mutexattr_t a;
108 pthread_mutexattr_init(&a);
109 pthread_mutexattr_settype(&a, PTHREAD_MUTEX_NORMAL);
110 const int ret = pthread_mutex_init(&(mutex->mutex), &a);
111 pthread_mutexattr_destroy(&a);
112 return ret;
113 }
114#endif
115 const int ret = pthread_mutex_init(&(mutex->mutex), attr);
116 assert(!ret);
117 return ret;
118}
119
120#define dt_pthread_mutex_lock(A) dt_pthread_mutex_lock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
121static inline int dt_pthread_mutex_lock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
122 const int line, const char *function)
123 ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
124{
125 const double t0 = dt_pthread_get_wtime();
126 const int ret = pthread_mutex_lock(&(mutex->mutex));
127 assert(!ret);
128 mutex->time_locked = dt_pthread_get_wtime();
129 double wait = mutex->time_locked - t0;
130 mutex->time_sum_wait += wait;
131 char *name = mutex->name;
132 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
133 // TODO: have a -d thread option
134 //fprintf(stdout, "Thread lock %s acquired\n", mutex->name);
135 int min_wait_slot = 0;
136 for(int k = 0; k < TOPN; k++)
137 {
138 if(mutex->top_wait_sum[k] < mutex->top_wait_sum[min_wait_slot]) min_wait_slot = k;
139 if(!strncmp(name, mutex->top_wait_name[k], 256))
140 {
141 mutex->top_wait_sum[k] += wait;
142 return ret;
143 }
144 }
145 g_strlcpy(mutex->top_wait_name[min_wait_slot], name, sizeof(mutex->top_wait_name[min_wait_slot]));
146 mutex->top_wait_sum[min_wait_slot] = wait;
147 return ret;
148}
149
150#define dt_pthread_mutex_trylock(A) dt_pthread_mutex_trylock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
151static inline int dt_pthread_mutex_trylock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
152 const int line, const char *function)
153 TRY_ACQUIRE(0, mutex)
154{
155 const double t0 = dt_pthread_get_wtime();
156 const int ret = pthread_mutex_trylock(&(mutex->mutex));
157 assert(!ret || (ret == EBUSY));
158 if(ret) return ret;
159 mutex->time_locked = dt_pthread_get_wtime();
160 double wait = mutex->time_locked - t0;
161 mutex->time_sum_wait += wait;
162 char *name = mutex->name;
163 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
164 int min_wait_slot = 0;
165 for(int k = 0; k < TOPN; k++)
166 {
167 if(mutex->top_wait_sum[k] < mutex->top_wait_sum[min_wait_slot]) min_wait_slot = k;
168 if(!strncmp(name, mutex->top_wait_name[k], 256))
169 {
170 mutex->top_wait_sum[k] += wait;
171 return ret;
172 }
173 }
174 g_strlcpy(mutex->top_wait_name[min_wait_slot], name, sizeof(mutex->top_wait_name[min_wait_slot]));
175 mutex->top_wait_sum[min_wait_slot] = wait;
176 return ret;
177}
178
179#define dt_pthread_mutex_unlock(A) dt_pthread_mutex_unlock_with_caller(A, __FILE__, __LINE__, __FUNCTION__)
180static inline int dt_pthread_mutex_unlock_with_caller(dt_pthread_mutex_t *mutex, const char *file,
181 const int line, const char *function)
182 RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
183{
184 const double t0 = dt_pthread_get_wtime();
185 const double locked = t0 - mutex->time_locked;
186 mutex->time_sum_locked += locked;
187
188 char *name = mutex->name;
189 snprintf(mutex->name, sizeof(mutex->name), "%s:%d (%s)", file, line, function);
190 // TODO: have a -d thread debug arg
191 //fprintf(stdout, "Thread lock %s released\n", mutex->name);
192 int min_locked_slot = 0;
193 for(int k = 0; k < TOPN; k++)
194 {
195 if(mutex->top_locked_sum[k] < mutex->top_locked_sum[min_locked_slot]) min_locked_slot = k;
196 if(!strncmp(name, mutex->top_locked_name[k], 256))
197 {
198 mutex->top_locked_sum[k] += locked;
199 min_locked_slot = -1;
200 break;
201 }
202 }
203 if(min_locked_slot >= 0)
204 {
205 g_strlcpy(mutex->top_locked_name[min_locked_slot], name, sizeof(mutex->top_locked_name[min_locked_slot]));
206 mutex->top_locked_sum[min_locked_slot] = locked;
207 }
208
209 // need to unlock last, to shield our internal data.
210 const int ret = pthread_mutex_unlock(&(mutex->mutex));
211 assert(!ret);
212 return ret;
213}
214
215static inline int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
216{
217 return pthread_cond_wait(cond, &(mutex->mutex));
218}
219
220
222 const pthread_rwlockattr_t *attr)
223{
224 memset(lock, 0, sizeof(dt_pthread_rwlock_t));
225 lock->cnt = 0;
226 lock->writer_depth = 0;
227 const int res = pthread_rwlock_init(&lock->lock, attr);
228 assert(!res);
229 return res;
230}
231
232// In this debug variant the name field is diagnostic scratch, overwritten
233// with the caller's file:line on every lock operation; accept the label
234// anyway so callers compile identically in both variants (the release
235// variant uses it to opt into wait-time diagnostics).
236static inline void dt_pthread_rwlock_set_name(dt_pthread_rwlock_t *lock, const char *name)
237{
238 snprintf(lock->name, sizeof(lock->name), "%s", name);
239}
240
242{
243 if(lock->writer_depth != 0)
244 fprintf(stderr, "ERROR: destroying rwlock still owned by writer (depth=%d cnt=%d last=%s)\n",
245 lock->writer_depth, lock->cnt, lock->name);
246 assert(lock->writer_depth == 0);
247 snprintf(lock->name, sizeof(lock->name), "destroyed with cnt %d", lock->cnt);
248 const int res = pthread_rwlock_destroy(&lock->lock);
249 assert(!res);
250 return res;
251}
252
253static inline pthread_t dt_pthread_rwlock_get_writer(dt_pthread_rwlock_t *lock)
254{
255 return lock->writer;
256}
257
258#define dt_pthread_rwlock_unlock(A) dt_pthread_rwlock_unlock_with_caller(A, __FILE__, __LINE__)
259static inline int dt_pthread_rwlock_unlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
260{
261 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth > 1)
262 {
263 __sync_fetch_and_sub(&(rwlock->cnt), 1);
264 rwlock->writer_depth--;
265 assert(rwlock->writer_depth >= 1);
266 assert(rwlock->cnt >= 0);
267 snprintf(rwlock->name, sizeof(rwlock->name), "nu:%s:%d", file, line);
268 fprintf(stdout, "Warning: thread lock owned by the same thread is unlocked again by %s\n", rwlock->name);
269 return 0;
270 }
271
272 const gboolean writer_was_self = pthread_equal(rwlock->writer, pthread_self());
273 const int res = pthread_rwlock_unlock(&rwlock->lock);
274 assert(!res);
275 __sync_fetch_and_sub(&(rwlock->cnt), 1);
276 assert(rwlock->cnt >= 0);
277 __sync_bool_compare_and_swap(&(rwlock->writer), pthread_self(), 0);
278 if(writer_was_self) rwlock->writer_depth = 0;
279 if(!res) snprintf(rwlock->name, sizeof(rwlock->name), "u:%s:%d", file, line);
280 return res;
281}
282
283#define dt_pthread_rwlock_rdlock(A) dt_pthread_rwlock_rdlock_with_caller(A, __FILE__, __LINE__)
284static inline int dt_pthread_rwlock_rdlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
285{
286 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
287 {
288 __sync_fetch_and_add(&(rwlock->cnt), 1);
289 rwlock->writer_depth++;
290 snprintf(rwlock->name, sizeof(rwlock->name), "wr:%s:%d", file, line);
291 fprintf(stdout, "Warning: thread lock owned by the same thread is locked again by %s\n", rwlock->name);
292 return 0;
293 }
294
295 const int res = pthread_rwlock_rdlock(&rwlock->lock);
296 assert(!res);
297 __sync_fetch_and_add(&(rwlock->cnt), 1);
298 if(!res)
299 snprintf(rwlock->name, sizeof(rwlock->name), "r:%s:%d", file, line);
300 return res;
301}
302#define dt_pthread_rwlock_wrlock(A) dt_pthread_rwlock_wrlock_with_caller(A, __FILE__, __LINE__)
303static inline int dt_pthread_rwlock_wrlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
304{
305 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
306 {
307 __sync_fetch_and_add(&(rwlock->cnt), 1);
308 rwlock->writer_depth++;
309 snprintf(rwlock->name, sizeof(rwlock->name), "ww:%s:%d", file, line);
310 fprintf(stdout, "Warning: thread lock owned by the same thread is locked again by %s\n", rwlock->name);
311 return 0;
312 }
313
314 const int res = pthread_rwlock_wrlock(&rwlock->lock);
315 assert(!res);
316 __sync_fetch_and_add(&(rwlock->cnt), 1);
317 if(!res)
318 {
319 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
320 rwlock->writer_depth = 1;
321 snprintf(rwlock->name, sizeof(rwlock->name), "w:%s:%d", file, line);
322 }
323 return res;
324}
325#define dt_pthread_rwlock_tryrdlock(A) dt_pthread_rwlock_tryrdlock_with_caller(A, __FILE__, __LINE__)
326static inline int dt_pthread_rwlock_tryrdlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
327{
328 // IMPORTANT: try* locks are often used as "is it locked by anyone?" probes (e.g. caches).
329 // Returning success when the current thread already holds the write lock breaks that contract
330 // and can lead to freeing data still in use. Treat it as busy.
331 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
332
333 const int res = pthread_rwlock_tryrdlock(&rwlock->lock);
334 assert(!res || (res == EBUSY));
335 if(!res)
336 {
337 __sync_fetch_and_add(&(rwlock->cnt), 1);
338 snprintf(rwlock->name, sizeof(rwlock->name), "tr:%s:%d", file, line);
339 }
340 return res;
341}
342#define dt_pthread_rwlock_trywrlock(A) dt_pthread_rwlock_trywrlock_with_caller(A, __FILE__, __LINE__)
343static inline int dt_pthread_rwlock_trywrlock_with_caller(dt_pthread_rwlock_t *rwlock, const char *file, int line)
344{
345 // See dt_pthread_rwlock_tryrdlock_with_caller(): don't make trywrlock succeed recursively.
346 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
347
348 const int res = pthread_rwlock_trywrlock(&rwlock->lock);
349 assert(!res || (res == EBUSY));
350 if(!res)
351 {
352 __sync_fetch_and_add(&(rwlock->cnt), 1);
353 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
354 rwlock->writer_depth = 1;
355 snprintf(rwlock->name, sizeof(rwlock->name), "tw:%s:%d", file, line);
356 }
357 return res;
358}
359
360#undef TOPN
361#else
362
363typedef struct CAPABILITY("mutex") dt_pthread_mutex_t
364{
365 pthread_mutex_t mutex;
366} CAPABILITY("mutex") dt_pthread_mutex_t;
367
368// *please* do use these;
369static inline int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
370{
371 return pthread_mutex_init(&mutex->mutex, mutexattr);
372};
373
374static inline int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
375{
376 return pthread_mutex_lock(&mutex->mutex);
377};
378
379static inline int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0, mutex)
380{
381 return pthread_mutex_trylock(&mutex->mutex);
382};
383
384static inline int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
385{
386 return pthread_mutex_unlock(&mutex->mutex);
387};
388
389static inline int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
390{
391 return pthread_mutex_destroy(&mutex->mutex);
392};
393
394static inline int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
395{
396 return pthread_cond_wait(cond, &mutex->mutex);
397};
398
399// Same-thread recursive write-lock tracking, mirroring the _DEBUG implementation above.
400// A thread that already holds the write lock cannot race itself: no other thread can be
401// touching the protected data while the write lock is held, so letting that same thread
402// re-enter (as reader or writer) is safe for data validity. Without this, glibc's default
403// PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP policy self-deadlocks such a thread as soon as
404// a second thread is queued waiting for the write lock (new readers, including from the
405// writer's own thread, are blocked once a writer is waiting).
407{
408 pthread_rwlock_t lock;
409 pthread_t writer;
411 // Temporary diagnostic hook (find_history_mutex_blocker): NULL for every lock except the
412 // ones explicitly named via dt_pthread_rwlock_set_name(). Zero overhead for unnamed locks
413 // (one pointer compare). last_holder_tid is best-effort/racy by design -- it's a debug hint
414 // for "who was probably holding this right before I blocked", not a correctness primitive.
415 const char *name;
418 // Tracks the reader holding this lock the longest, to tell apart "the writer is queued
419 // behind a reader that grabbed it a moment ago" from "some reader has been sitting on this
420 // for the whole wait" -- the single last_holder_tid above can't distinguish those, since it
421 // reflects whoever *most recently acquired*, not whoever has been holding it longest.
426
427static inline int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
428{
429 lock->writer = 0;
430 lock->writer_depth = 0;
431 lock->name = NULL;
432 lock->last_holder_tid = 0;
433 lock->last_holder_was_writer = FALSE;
434 lock->active_reader_count = 0;
435 lock->oldest_active_reader_since = 0.0;
436 lock->oldest_active_reader_tid = 0;
437 return pthread_rwlock_init(&lock->lock, attr);
438}
439
440// Opt-in: call once after dt_pthread_rwlock_init() to enable wait-time diagnostics on this
441// specific lock instance. Temporary, for find_history_mutex_blocker -- remove once resolved.
443{
444 lock->name = name;
445}
446
448{
449 return pthread_rwlock_destroy(&lock->lock);
450}
451
453{
454 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth > 1)
455 {
456 rwlock->writer_depth--;
457 return 0;
458 }
459 const gboolean writer_was_self = pthread_equal(rwlock->writer, pthread_self());
460 if(rwlock->name && !writer_was_self)
461 {
462 // A reader unlocking (writer_was_self is only true for a thread that took the write lock;
463 // recursive-writer-as-reader re-entry already returned above via writer_depth).
464 if(__sync_fetch_and_sub(&rwlock->active_reader_count, 1) == 1)
465 {
466 rwlock->oldest_active_reader_since = 0.0;
467 rwlock->oldest_active_reader_tid = 0;
468 }
469 }
470 const int res = pthread_rwlock_unlock(&rwlock->lock);
471 if(writer_was_self)
472 {
473 rwlock->writer_depth = 0;
474 __sync_bool_compare_and_swap(&(rwlock->writer), pthread_self(), 0);
475 }
476 return res;
477}
478
479// Diagnostic-only. This header is included too early in the chain (darktable.h includes it
480// before declaring dt_debug_thread_t/darktable_t/dt_print) for dt_print() to be callable
481// directly from the inline functions below -- so the actual logging is delegated to these two
482// non-inline helpers, implemented in dtpthread.c (which, being a .c file and not part of the
483// header cycle, can include common/darktable.h and call dt_print(DT_DEBUG_HISTORY, ...) there).
484// This makes the traces respect `-d history` like every other diagnostic instead of always
485// firing via a raw fprintf. Only ever called for locks opted in via dt_pthread_rwlock_set_name();
486// zero-cost (one pointer compare) for every other lock. Temporary, for
487// find_history_mutex_blocker -- remove once resolved.
488void _dt_pthread_rwlock_diag_log_rdlock(const char *name, unsigned long tid, double wait_ms,
489 unsigned long prev_holder, gboolean prev_was_writer);
490void _dt_pthread_rwlock_diag_log_wrlock(const char *name, unsigned long tid, double wait_ms,
491 unsigned long prev_holder, gboolean prev_was_writer,
492 int active_readers, unsigned long oldest_reader_tid,
493 double oldest_reader_age_ms);
494
495static inline double _dt_pthread_rwlock_diag_now(void)
496{
497 struct timespec ts;
498 clock_gettime(CLOCK_MONOTONIC, &ts);
499 return ts.tv_sec + ts.tv_nsec / 1e9;
500}
501
503{
504 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
505 {
506 rwlock->writer_depth++;
507 return 0;
508 }
509 if(rwlock->name)
510 {
511 const double _start = _dt_pthread_rwlock_diag_now();
512 const long _prev_holder = rwlock->last_holder_tid;
513 const gboolean _prev_was_writer = rwlock->last_holder_was_writer;
514 const int res = pthread_rwlock_rdlock(&rwlock->lock);
515 const double _wait_ms = (_dt_pthread_rwlock_diag_now() - _start) * 1000.0;
516 if(_wait_ms > 1.0)
517 _dt_pthread_rwlock_diag_log_rdlock(rwlock->name, (unsigned long)pthread_self(), _wait_ms,
518 (unsigned long)_prev_holder, _prev_was_writer);
519 if(!res)
520 {
521 rwlock->last_holder_tid = (long)pthread_self();
523 if(__sync_fetch_and_add(&rwlock->active_reader_count, 1) == 0)
524 {
526 rwlock->oldest_active_reader_tid = (long)pthread_self();
527 }
528 }
529 return res;
530 }
531 return pthread_rwlock_rdlock(&rwlock->lock);
532}
533
535{
536 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1)
537 {
538 rwlock->writer_depth++;
539 return 0;
540 }
541 if(rwlock->name)
542 {
543 const double _start = _dt_pthread_rwlock_diag_now();
544 const long _prev_holder = rwlock->last_holder_tid;
545 const gboolean _prev_was_writer = rwlock->last_holder_was_writer;
546 const int _readers_now = rwlock->active_reader_count;
547 const long _oldest_reader_tid = rwlock->oldest_active_reader_tid;
548 const double _oldest_reader_age_ms
549 = _readers_now > 0 ? (_start - rwlock->oldest_active_reader_since) * 1000.0 : 0.0;
550 const int res = pthread_rwlock_wrlock(&rwlock->lock);
551 const double _wait_ms = (_dt_pthread_rwlock_diag_now() - _start) * 1000.0;
552 if(_wait_ms > 1.0)
553 _dt_pthread_rwlock_diag_log_wrlock(rwlock->name, (unsigned long)pthread_self(), _wait_ms,
554 (unsigned long)_prev_holder, _prev_was_writer, _readers_now,
555 (unsigned long)_oldest_reader_tid, _oldest_reader_age_ms);
556 if(!res)
557 {
558 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
559 rwlock->last_holder_tid = (long)pthread_self();
561 }
562 return res;
563 }
564 const int res = pthread_rwlock_wrlock(&rwlock->lock);
565 if(!res)
566 {
567 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
568 rwlock->writer_depth = 1;
569 }
570 return res;
571}
572
574{
575 // Keep try* locks honest as "is it locked by anyone?" probes: do NOT report success just
576 // because the current thread already holds the write lock (see dt_pthread_rwlock_rdlock
577 // above for the blocking-call recursion, which is the case this is not).
578 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
579 return pthread_rwlock_tryrdlock(&rwlock->lock);
580}
581
583{
584 if(pthread_equal(rwlock->writer, pthread_self()) && rwlock->writer_depth >= 1) return EBUSY;
585 const int res = pthread_rwlock_trywrlock(&rwlock->lock);
586 if(!res)
587 {
588 __sync_lock_test_and_set(&(rwlock->writer), pthread_self());
589 rwlock->writer_depth = 1;
590 }
591 return res;
592}
593
594#define dt_pthread_rwlock_rdlock_with_caller(A,B,C) dt_pthread_rwlock_rdlock(A)
595#define dt_pthread_rwlock_wrlock_with_caller(A,B,C) dt_pthread_rwlock_wrlock(A)
596#define dt_pthread_rwlock_tryrdlock_with_caller(A,B,C) dt_pthread_rwlock_tryrdlock(A)
597#define dt_pthread_rwlock_trywrlock_with_caller(A,B,C) dt_pthread_rwlock_trywrlock(A)
598
599#endif
600
601// if at all possible, do NOT use.
602static inline int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
603{
604 return pthread_mutex_lock(&mutex->mutex);
605};
606
607static inline int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
608{
609 return pthread_mutex_trylock(&mutex->mutex);
610};
611
612static inline int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
613{
614 return pthread_mutex_unlock(&mutex->mutex);
615};
616
617int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime);
618
619void dt_pthread_setname(const char *name);
620
621// clang-format off
622// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
623// vim: shiftwidth=2 expandtab tabstop=2 cindent
624// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
625// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ ACQUIRE
char * name
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static int dt_pthread_mutex_BAD_lock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:602
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:51
struct CAPABILITY("mutex") dt_pthread_mutex_t
Definition dtpthread.h:363
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:132
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_rwlock_tryrdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:573
static int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:607
#define dt_pthread_rwlock_wrlock_with_caller(A, B, C)
Definition dtpthread.h:595
static double _dt_pthread_rwlock_diag_now(void)
Definition dtpthread.h:495
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:612
static void dt_pthread_rwlock_set_name(dt_pthread_rwlock_t *lock, const char *name)
Definition dtpthread.h:442
#define dt_pthread_rwlock_trywrlock_with_caller(A, B, C)
Definition dtpthread.h:597
#define dt_pthread_rwlock_tryrdlock_with_caller(A, B, C)
Definition dtpthread.h:596
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_rwlock_trywrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:582
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:369
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:139
static int mutex
Definition dtpthread.h:380
static int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
Definition dtpthread.h:447
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:389
void dt_pthread_setname(const char *name)
Definition dtpthread.c:151
static int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
Definition dtpthread.h:427
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Definition dtpthread.h:394
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
#define dt_pthread_rwlock_rdlock_with_caller(A, B, C)
Definition dtpthread.h:594
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:534
float *const restrict const size_t k
double oldest_active_reader_since
Definition dtpthread.h:423
const char * name
Definition dtpthread.h:415
long oldest_active_reader_tid
Definition dtpthread.h:424
gboolean last_holder_was_writer
Definition dtpthread.h:417
pthread_rwlock_t lock
Definition dtpthread.h:408
dt_pthread_mutex_t lock
Definition supervisor.c:119