Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
common/cache.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2011-2015 johannes hanika.
4 Copyright (C) 2012 Raphael Manfredi.
5 Copyright (C) 2012 Richard Wonka.
6 Copyright (C) 2012-2014, 2016 Tobias Ellinghaus.
7 Copyright (C) 2013-2016 Roman Lebedev.
8 Copyright (C) 2019 Andreas Schneider.
9 Copyright (C) 2019, 2023, 2025 Aurélien PIERRE.
10 Copyright (C) 2020 Heiko Bauke.
11 Copyright (C) 2020-2021 Pascal Obry.
12 Copyright (C) 2021 Ralf Brown.
13 Copyright (C) 2022 Martin Bařinka.
14 Copyright (C) 2024 Alynx Zhou.
15
16 darktable is free software: you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation, either version 3 of the License, or
19 (at your option) any later version.
20
21 darktable is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with darktable. If not, see <http://www.gnu.org/licenses/>.
28*/
29
30#include "config.h"
31
32#include "common/cache.h"
33#include "common/darktable.h"
34#include "common/dtpthread.h"
35
36#include <assert.h>
37#include <inttypes.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41
42// this implements a concurrent LRU cache
43
45 dt_cache_t *cache,
46 size_t entry_size,
47 size_t cost_quota)
48{
49 cache->cost = 0;
50 cache->lru = 0;
51 cache->entry_size = entry_size;
52 cache->cost_quota = cost_quota;
53 dt_pthread_mutex_init(&cache->lock, 0);
54 cache->allocate = 0;
55 cache->allocate_data = 0;
56 cache->cleanup = 0;
57 cache->cleanup_data = 0;
58 cache->hashtable = g_hash_table_new(0, 0);
59}
60
62{
63 g_hash_table_destroy(cache->hashtable);
64 for(GList *l = cache->lru; l; l = g_list_next(l))
65 {
67
68 if(cache->cleanup)
69 {
70 assert(entry->data_size);
72
73 cache->cleanup(cache->cleanup_data, entry);
74 }
75 else
76 {
77 dt_free_align(entry->data);
78 entry->data = NULL;
79 }
80
82 g_slice_free1(sizeof(*entry), entry);
83 }
84 g_list_free(cache->lru);
85 cache->lru = NULL;
87}
88
89int32_t dt_cache_contains(dt_cache_t *cache, const uint32_t key)
90{
92 int32_t result = g_hash_table_contains(cache->hashtable, GINT_TO_POINTER(key));
94 return result;
95}
96
98 dt_cache_t *cache,
99 int (*process)(const uint32_t key, const void *data, void *user_data),
100 void *user_data)
101{
103 GHashTableIter iter;
104 gpointer key, value;
105
106 g_hash_table_iter_init (&iter, cache->hashtable);
107 while (g_hash_table_iter_next (&iter, &key, &value))
108 {
110 const int err = process(GPOINTER_TO_INT(key), entry->data, user_data);
111 if(err)
112 {
114 return err;
115 }
116 }
118 return 0;
119}
120
121// return read locked bucket, or NULL if it's not already there.
122// never attempt to allocate a new slot.
123dt_cache_entry_t *dt_cache_testget(dt_cache_t *cache, const uint32_t key, char mode)
124{
125 gpointer orig_key, value;
126 gboolean res;
128 res = g_hash_table_lookup_extended(
129 cache->hashtable, GINT_TO_POINTER(key), &orig_key, &value);
130 if(res)
131 {
133 // lock the cache entry
134 const int result
135 = (mode == 'w') ? dt_pthread_rwlock_trywrlock(&entry->lock) : dt_pthread_rwlock_tryrdlock(&entry->lock);
136 if(result)
137 { // need to give up mutex so other threads have a chance to get in between and
138 // free the lock we're trying to acquire:
140 return 0;
141 }
142 // bubble up in lru list:
143 cache->lru = g_list_remove_link(cache->lru, entry->link);
144 cache->lru = g_list_concat(cache->lru, entry->link);
146
147 if(mode == 'w')
148 {
149 assert(entry->data_size);
151 }
152
153 // WARNING: do *NOT* unpoison here. it must be done by the caller!
154
155 return entry;
156 }
158 return 0;
159}
160
161// if found, the data void* is returned. if not, it is set to be
162// the given *data and a new hash table entry is created, which can be
163// found using the given key later on.
164dt_cache_entry_t *dt_cache_get_with_caller(dt_cache_t *cache, const uint32_t key, char mode, const char *file, int line)
165{
166 gpointer orig_key, value;
167 gboolean res;
168 int result;
169restart:
171 res = g_hash_table_lookup_extended(
172 cache->hashtable, GINT_TO_POINTER(key), &orig_key, &value);
173 if(res)
174 { // yay, found. read lock and pass on.
176 if(mode == 'w') result = dt_pthread_rwlock_trywrlock_with_caller(&entry->lock, file, line);
177 else result = dt_pthread_rwlock_tryrdlock_with_caller(&entry->lock, file, line);
178 if(result)
179 { // need to give up mutex so other threads have a chance to get in between and
180 // free the lock we're trying to acquire:
182 g_usleep(5);
183 goto restart;
184 }
185 // bubble up in lru list:
186 cache->lru = g_list_remove_link(cache->lru, entry->link);
187 cache->lru = g_list_concat(cache->lru, entry->link);
189
190#ifdef _DEBUG
191 const pthread_t writer = dt_pthread_rwlock_get_writer(&entry->lock);
192 if(mode == 'w')
193 {
194 assert(pthread_equal(writer, pthread_self()));
195 }
196 else
197 {
198 assert(!pthread_equal(writer, pthread_self()));
199 }
200#endif
201
202 if(mode == 'w')
203 {
204 assert(entry->data_size);
206 }
207
208 // WARNING: do *NOT* unpoison here. it must be done by the caller!
209
210 return entry;
211 }
212
213 // else, not found, need to allocate.
214
215 // first try to clean up.
216 // also wait if we can't free more than the requested fill ratio.
217 if(cache->cost > 0.8f * cache->cost_quota)
218 {
219 // need to roll back all the way to get a consistent lock state:
220 dt_cache_gc(cache, 0.8f);
221 }
222
223 // here dies your 32-bit system:
224 dt_cache_entry_t *entry = (dt_cache_entry_t *)g_slice_alloc(sizeof(dt_cache_entry_t));
225 entry->data = 0;
226 entry->data_size = cache->entry_size;
227 entry->cost = 1;
228 entry->link = g_list_append(0, entry);
229 entry->key = key;
230 entry->_lock_demoting = 0;
231
232 if(cache->allocate)
233 cache->allocate(cache->allocate_data, entry);
234 else
235 entry->data = dt_alloc_align(entry->data_size);
236
237 if(!entry->data)
238 {
239 dt_free(entry);
241 return NULL;
242 }
243
244 int ret = dt_pthread_rwlock_init(&entry->lock, 0);
245 if(ret) fprintf(stderr, "rwlock init: %d\n", ret);
246
247 g_hash_table_insert(cache->hashtable, GINT_TO_POINTER(key), entry);
248 assert(cache->allocate || entry->data_size);
249
250 assert(entry->data_size);
252
253 // if allocate callback is given, always return a write lock
254 const int write = ((mode == 'w') || cache->allocate);
255
256 // write lock in case the caller requests it:
257 if(write) dt_pthread_rwlock_wrlock_with_caller(&entry->lock, file, line);
258 else dt_pthread_rwlock_rdlock_with_caller(&entry->lock, file, line);
259
260 cache->cost += entry->cost;
261
262 // put at end of lru list (most recently used):
263 cache->lru = g_list_concat(cache->lru, entry->link);
264
266 // WARNING: do *NOT* unpoison here. it must be done by the caller!
267
268 return entry;
269}
270
271int dt_cache_remove(dt_cache_t *cache, const uint32_t key)
272{
273 gpointer orig_key, value;
274 gboolean res;
275 int result;
276 dt_cache_entry_t *entry;
277restart:
279
280 res = g_hash_table_lookup_extended(
281 cache->hashtable, GINT_TO_POINTER(key), &orig_key, &value);
282 entry = (dt_cache_entry_t *)value;
283 if(!res)
284 { // not found in cache, not deleting.
286 return 1;
287 }
288 // need write lock to be able to delete:
289 result = dt_pthread_rwlock_trywrlock(&entry->lock);
290 if(result)
291 {
293 g_usleep(5);
294 goto restart;
295 }
296
297 if(entry->_lock_demoting)
298 {
299 // oops, we are currently demoting (rw -> r) lock to this entry in some thread. do not touch!
302 g_usleep(5);
303 goto restart;
304 }
305
306 gboolean removed = g_hash_table_remove(cache->hashtable, GINT_TO_POINTER(key));
307 (void)removed; // make non-assert compile happy
308 assert(removed);
309 cache->lru = g_list_delete_link(cache->lru, entry->link);
310
311 if(cache->cleanup)
312 {
313 assert(entry->data_size);
315
316 cache->cleanup(cache->cleanup_data, entry);
317 }
318 else
319 {
320 dt_free_align(entry->data);
321 entry->data = NULL;
322 }
323
326 cache->cost -= entry->cost;
327 g_slice_free1(sizeof(*entry), entry);
328
330 return 0;
331}
332
333// best-effort garbage collection. never blocks, never fails. well, sometimes it just doesn't free anything.
334void dt_cache_gc(dt_cache_t *cache, const float fill_ratio)
335{
336 GList *l = cache->lru;
337 while(l)
338 {
340 assert(entry->link->data == entry);
341 l = g_list_next(l); // we might remove this element, so walk to the next one while we still have the pointer..
342 if(cache->cost < cache->cost_quota * fill_ratio) break;
343
344 // if still locked by anyone else give up:
345 if(dt_pthread_rwlock_trywrlock(&entry->lock)) continue;
346
347 if(entry->_lock_demoting)
348 {
349 // oops, we are currently demoting (rw -> r) lock to this entry in some thread. do not touch!
351 continue;
352 }
353
354 // delete!
355 g_hash_table_remove(cache->hashtable, GINT_TO_POINTER(entry->key));
356 cache->lru = g_list_delete_link(cache->lru, entry->link);
357 cache->cost -= entry->cost;
358
359 if(cache->cleanup)
360 {
361 assert(entry->data_size);
363
364 cache->cleanup(cache->cleanup_data, entry);
365 }
366 else
367 {
368 dt_free_align(entry->data);
369 entry->data = NULL;
370 }
371
374 g_slice_free1(sizeof(*entry), entry);
375 }
376}
377
378void dt_cache_release_with_caller(dt_cache_t *cache, dt_cache_entry_t *entry, const char *file, int line)
379{
380#if((__has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)) && 1)
381 // yes, this is *HIGHLY* unportable and is accessing implementation details.
382#ifdef _DEBUG
383
384# if defined(HAVE_THREAD_RWLOCK_ARCH_T_READERS)
385 if (entry->lock.lock.__data.__readers <= 1)
386# elif defined(HAVE_THREAD_RWLOCK_ARCH_T_NR_READERS)
387 if (entry->lock.lock.__data.__nr_readers <= 1)
388# else /* HAVE_THREAD_RWLOCK_ARCH_T_(NR_)READERS */
389# error "No valid reader member"
390# endif /* HAVE_THREAD_RWLOCK_ARCH_T_(NR_)READERS */
391
392#else /* _DEBUG */
393
394# if defined(HAVE_THREAD_RWLOCK_ARCH_T_READERS)
395 if (entry->lock.__data.__readers <= 1)
396# elif defined(HAVE_THREAD_RWLOCK_ARCH_T_NR_READERS)
397 if(entry->lock.__data.__nr_readers <= 1)
398# else /* HAVE_THREAD_RWLOCK_ARCH_T_(NR_)READERS */
399# error "No valid reader member"
400# endif /* HAVE_THREAD_RWLOCK_ARCH_T_(NR_)READERS */
401
402#endif /* _DEBUG */
403 {
404 // only if there are no other reades we may poison.
405 assert(entry->data_size);
407 }
408#endif
409
411}
412
413int dt_cache_seed(dt_cache_t *cache, const uint32_t key, const void *data, size_t data_size, size_t cost,
414 gboolean aligned_alloc)
415{
416 if(!cache || !data || data_size == 0) return -1;
417
419 if(g_hash_table_contains(cache->hashtable, GINT_TO_POINTER(key)))
420 {
422 return 1;
423 }
424
425 if(cache->cost > 0.8f * cache->cost_quota)
426 dt_cache_gc(cache, 0.8f);
427
428 dt_cache_entry_t *entry = (dt_cache_entry_t *)g_slice_alloc(sizeof(dt_cache_entry_t));
429 entry->data = 0;
430 entry->data_size = data_size;
431 entry->cost = cost ? cost : data_size;
432 entry->link = g_list_append(0, entry);
433 entry->key = key;
434 entry->_lock_demoting = 0;
435
436 entry->data = aligned_alloc ? dt_alloc_align(entry->data_size) : g_malloc(entry->data_size);
437 if(!entry->data)
438 {
439 g_slice_free1(sizeof(*entry), entry);
441 return -1;
442 }
443
444 memcpy(entry->data, data, entry->data_size);
445
446 int ret = dt_pthread_rwlock_init(&entry->lock, 0);
447 if(ret) fprintf(stderr, "rwlock init: %d\n", ret);
448
449 g_hash_table_insert(cache->hashtable, GINT_TO_POINTER(key), entry);
450 cache->lru = g_list_concat(cache->lru, entry->link);
451 cache->cost += entry->cost;
452
453 assert(entry->data_size);
455
457 return 0;
458}
459
460// clang-format off
461// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
462// vim: shiftwidth=2 expandtab tabstop=2 cindent
463// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
464// clang-format on
int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
Definition ashift.c:3304
int32_t dt_cache_contains(dt_cache_t *cache, const uint32_t key)
Definition common/cache.c:89
int dt_cache_seed(dt_cache_t *cache, const uint32_t key, const void *data, size_t data_size, size_t cost, gboolean aligned_alloc)
Definition common/cache.c:413
void dt_cache_gc(dt_cache_t *cache, const float fill_ratio)
Definition common/cache.c:334
void dt_cache_release_with_caller(dt_cache_t *cache, dt_cache_entry_t *entry, const char *file, int line)
Definition common/cache.c:378
dt_cache_entry_t * dt_cache_testget(dt_cache_t *cache, const uint32_t key, char mode)
Definition common/cache.c:123
void dt_cache_init(dt_cache_t *cache, size_t entry_size, size_t cost_quota)
Definition common/cache.c:44
dt_cache_entry_t * dt_cache_get_with_caller(dt_cache_t *cache, const uint32_t key, char mode, const char *file, int line)
Definition common/cache.c:164
int dt_cache_remove(dt_cache_t *cache, const uint32_t key)
Definition common/cache.c:271
int dt_cache_for_all(dt_cache_t *cache, int(*process)(const uint32_t key, const void *data, void *user_data), void *user_data)
Definition common/cache.c:97
void dt_cache_cleanup(dt_cache_t *cache)
Definition common/cache.c:61
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
Definition common/metadata.c:60
#define ASAN_POISON_MEMORY_REGION(addr, size)
Definition config.cmake.h:98
#define ASAN_UNPOISON_MEMORY_REGION(addr, size)
Definition config.cmake.h:99
void * dt_alloc_align(size_t size)
Definition darktable.c:447
#define dt_free_align(ptr)
Definition darktable.h:405
#define dt_free(ptr)
Definition darktable.h:380
static const dt_aligned_pixel_simd_t value
Definition darktable.h:501
#define dt_pthread_rwlock_tryrdlock
Definition dtpthread.h:395
#define dt_pthread_rwlock_destroy
Definition dtpthread.h:391
#define dt_pthread_rwlock_wrlock_with_caller(A, B, C)
Definition dtpthread.h:399
#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
#define dt_pthread_rwlock_trywrlock
Definition dtpthread.h:396
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
#define dt_pthread_rwlock_init
Definition dtpthread.h:390
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
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
Definition common/cache.h:33
uint32_t key
Definition common/cache.h:40
GList * link
Definition common/cache.h:37
size_t data_size
Definition common/cache.h:35
dt_pthread_rwlock_t lock
Definition common/cache.h:38
int _lock_demoting
Definition common/cache.h:39
void * data
Definition common/cache.h:34
size_t cost
Definition common/cache.h:36
Definition common/cache.h:48
size_t cost
Definition common/cache.h:52
GList * lru
Definition common/cache.h:56
size_t cost_quota
Definition common/cache.h:53
void * allocate_data
Definition common/cache.h:61
GHashTable * hashtable
Definition common/cache.h:55
dt_cache_allocate_t allocate
Definition common/cache.h:59
dt_pthread_mutex_t lock
Definition common/cache.h:49
size_t entry_size
Definition common/cache.h:51
dt_cache_allocate_t cleanup
Definition common/cache.h:60
void * cleanup_data
Definition common/cache.h:62